@@ -770,170 +770,132 @@ define method pattern-to-stream
770770 end ;
771771end 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.
780778define 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 ;
0 commit comments