Skip to content

Commit 07bd38f

Browse files
authored
Merge pull request #40 from cgay/dev
Fixes in anticipation of Unicode strings
2 parents 31010e7 + 6f4ed66 commit 07bd38f

2 files changed

Lines changed: 114 additions & 152 deletions

File tree

logging.dylan

Lines changed: 113 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -770,170 +770,132 @@ define method pattern-to-stream
770770
end;
771771
end method pattern-to-stream;
772772

773-
// Parse a string of the form "%{r} blah %{m} ..." into a list of functions
774-
// and/or strings. The functions can be called with no arguments and return
775-
// strings. The concatenation of all the resulting strings is the log message.
776-
// (The concatenation needn't ever be done if writing to a stream, but I do
777-
// wonder which would be faster, concatenation or multiple stream writes.
778-
// Might be worth benchmarking at some point.)
773+
// Parse a string of the form "%{r} blah %{m} ..." into a list of functions and/or
774+
// strings. The functions can be called with no arguments and return strings.
779775
//
776+
// This function could be a lot simpler. It's done this way to avoid dependencies on the
777+
// regular-expressions and strings libraries.
780778
define method parse-formatter-pattern
781779
(pattern :: <string>)
782780
=> (parsed :: <sequence>)
783781
let result :: <stretchy-vector> = make(<stretchy-vector>);
784782
block (exit)
785-
let dispatch-char :: <byte-character> = '%';
786-
let index :: <integer> = 0;
787-
let control-size :: <integer> = pattern.size;
788-
local method next-char () => (char :: <character>)
789-
if (index >= control-size)
790-
logging-error("Log format control string ended prematurely: %s",
791-
pattern);
783+
let (state, limit, next-state, finished-state?, ignored-current-key, current-element)
784+
= forward-iteration-protocol(pattern);
785+
local
786+
method peek () => (char :: false-or(<character>))
787+
if (~finished-state?(pattern, state, limit))
788+
current-element(pattern, state)
789+
end
790+
end method,
791+
method consume () => (char :: <character>)
792+
if (finished-state?(pattern, state, limit))
793+
logging-error("Log format control string ended prematurely: %s", pattern);
794+
end;
795+
let char = current-element(pattern, state);
796+
state := next-state(pattern, state);
797+
char
798+
end method,
799+
method read-until (fn :: <function>, #key error?)
800+
let buf = make(<stretchy-vector>);
801+
iterate loop (ch = peek())
802+
if (~ch & error?)
803+
logging-error("format control string ended prematurely: %s", pattern);
804+
end;
805+
if (~ch | fn(ch))
806+
ch & consume();
807+
values(as(<string>, buf), ch)
808+
else
809+
add!(buf, consume());
810+
loop(peek())
811+
end
812+
end
813+
end method;
814+
while (~finished-state?(pattern, state, limit))
815+
let directive-arg = #f;
816+
let width :: <integer> = 0;
817+
let align = #"right";
818+
local
819+
method pad (string :: <string>)
820+
let len :: <integer> = string.size;
821+
if (width <= len)
822+
string
823+
else
824+
let buf = make(<stretchy-vector>);
825+
if (align == #"left")
826+
concatenate!(buf, string);
827+
for (i from 1 to width - len) add!(buf, ' ') end;
792828
else
793-
let char = pattern[index];
794-
index := index + 1;
795-
char
796-
end
797-
end method;
798-
local method peek-char () => (char :: false-or(<character>))
799-
if (index < control-size)
800-
pattern[index]
801-
end
829+
for (i from 1 to width - len) add!(buf, ' ') end;
830+
concatenate!(buf, string);
831+
end;
832+
as(<string>, buf)
833+
end
834+
end,
835+
method %%date (#rest ignore)
836+
pad(if (directive-arg)
837+
format-date(directive-arg, current-date())
838+
else
839+
as-iso8601-string(current-date())
840+
end)
841+
end,
842+
method %%severity (level, target, object, args)
843+
// Would be nice to do this padding at compile time since the severity level is
844+
// explicit in the log-info etc call. Just pass the level to this function
845+
// (parse-formatter-pattern).
846+
pad(level-short-name(level))
847+
end,
848+
method %%message (level, target, object, args)
849+
write-message(target, object, args);
850+
#f
851+
end,
852+
method %%process (#rest args)
853+
pad(integer-to-string(current-process-id()));
854+
end,
855+
method %%milliseconds (#rest args)
856+
pad(number-to-string(elapsed-milliseconds()));
857+
end,
858+
method %%thread (#rest args)
859+
pad(thread-name(current-thread())
860+
| number-to-string(current-thread-id()));
861+
end,
862+
method parse-long-format-control ()
863+
let (word, ch) = read-until(method (c) c == ':' | c == '}' end, error?: #t);
864+
if (ch == ':')
865+
directive-arg := read-until(method (c) c == '}' end);
802866
end;
803-
while (index < control-size)
804-
// Skip to dispatch char.
805-
for (i :: <integer> = index then (i + 1),
806-
until: ((i == control-size)
807-
| (pattern[i] == dispatch-char)))
808-
finally
809-
if (i ~== index)
810-
add!(result, copy-sequence(pattern, start: index, end: i));
867+
select (word by \=)
868+
"date" => %%date;
869+
"level" => %%severity; // deprecated, use "severity"
870+
"severity" => %%severity;
871+
"message" => %%message;
872+
"pid" => %%process;
873+
"millis" => %%milliseconds;
874+
"thread" => %%thread;
875+
end select
811876
end;
812-
if (i == control-size)
813-
exit();
814-
else
815-
index := i + 1;
816-
end;
817-
end for;
818-
let start :: <integer> = index;
819-
let align :: <symbol> = #"right";
820-
let width :: <integer> = 0;
821-
let char = next-char();
822-
if (char == '-')
877+
let (text, ch) = read-until(method (c) c == '%' end);
878+
add!(result, text);
879+
ch | exit();
880+
if (peek() == '-')
881+
consume();
823882
align := #"left";
824-
char := next-char();
825883
end;
826-
if (member?(char, "0123456789"))
827-
let (wid, idx) = string-to-integer(pattern, start: index - 1);
828-
width := wid;
829-
index := idx;
830-
char := next-char();
884+
while (peek() & member?(peek(), "0123456789"))
885+
let digit-value = as(<integer>, consume()) - as(<integer>, '0');
886+
width := width * 10 + digit-value;
831887
end;
832-
local method pad (string :: <string>)
833-
let len :: <integer> = string.size;
834-
if (width <= len)
835-
string
836-
else
837-
let fill :: <string> = make(<string>, size: width - len, fill: ' ');
838-
if (align == #"left")
839-
concatenate(string, fill)
840-
else
841-
concatenate(fill, string)
842-
end
843-
end
844-
end method;
845-
local method parse-long-format-control ()
846-
let bpos = index;
847-
while (~member?(peek-char(), ":}")) next-char() end;
848-
let word = copy-sequence(pattern, start: bpos, end: index);
849-
let arg = #f;
850-
if (pattern[index] == ':')
851-
next-char();
852-
let start = index;
853-
while(peek-char() ~= '}') next-char() end;
854-
arg := copy-sequence(pattern, start: start, end: index);
855-
end;
856-
next-char(); // eat '}'
857-
select (word by \=)
858-
"date" =>
859-
method (#rest args)
860-
pad(if (arg)
861-
format-date(arg, current-date())
862-
else
863-
as-iso8601-string(current-date())
864-
end)
865-
end;
866-
"level" => // deprecated, use "severity"
867-
method (level, target, object, args)
868-
pad(level-name(level))
869-
end;
870-
"severity" =>
871-
// Would be nice to do this padding at compile time since the severity
872-
// level is explicit in the log-info etc call. Just pass the level to
873-
// this function (parse-formatter-pattern).
874-
method (level, target, object, args)
875-
pad(level-name(level))
876-
end;
877-
"message" =>
878-
method (level, target, object, args)
879-
write-message(target, object, args);
880-
#f
881-
end;
882-
"pid" =>
883-
method (#rest args)
884-
pad(integer-to-string(current-process-id()));
885-
end;
886-
"millis" =>
887-
method (#rest args)
888-
pad(number-to-string(elapsed-milliseconds()));
889-
end;
890-
"thread" =>
891-
method (#rest args)
892-
pad(thread-name(current-thread())
893-
| number-to-string(current-thread-id()));
894-
end;
895-
otherwise =>
896-
// Unknown control string. Just output the text we've seen...
897-
copy-sequence(pattern, start: start, end: index);
898-
end select;
899-
end method;
900888
add!(result,
901-
select (char)
902-
'{' => parse-long-format-control();
903-
'd' =>
904-
method (#rest args)
905-
pad(as-iso8601-string(current-date()));
906-
end;
907-
'l', 'L' =>
908-
method (level, target, object, args)
909-
pad(level-name(level))
910-
end;
911-
'm' =>
912-
method (level, target, object, args)
913-
write-message(target, object, args);
914-
#f
915-
end;
916-
'p' =>
917-
method (#rest args)
918-
pad(integer-to-string(current-process-id()));
919-
end;
920-
'r' =>
921-
method (#rest args)
922-
pad(number-to-string(elapsed-milliseconds()));
923-
end;
924-
's' =>
925-
method (level, target, object, args)
926-
pad(level-short-name(level))
927-
end;
928-
't' =>
929-
method (#rest args)
930-
pad(thread-name(current-thread())
931-
| number-to-string(current-thread-id()));
932-
end;
933-
'%' => pad("%");
934-
otherwise =>
935-
// Unknown control char. Just output the text we've seen...
936-
copy-sequence(pattern, start: start, end: index);
889+
select (consume())
890+
'{' => parse-long-format-control();
891+
'd' => %%date;
892+
'l', 'L' => %%severity;
893+
'm' => %%message;
894+
'p' => %%process;
895+
'r' => %%milliseconds;
896+
's' => %%severity;
897+
't' => %%thread;
898+
'%' => pad("%");
937899
end);
938900
end while;
939901
end block;

tests/logging-test-suite.dylan

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ define test test-format-severity ()
145145
log-warning("x");
146146
log-error("x");
147147
end;
148-
assert-equal("T TRACE\nD DEBUG\nI INFO\nW WARNING\nE ERROR\n",
148+
assert-equal("T T\nD D\nI I\nW W\nE E\n",
149149
stream-contents(target.target-stream))
150150
end test;
151151

0 commit comments

Comments
 (0)