// Port of Go conf/lex.go — state-machine tokenizer for NATS config files. // Reference: golang/nats-server/conf/lex.go namespace NATS.Server.Configuration; public sealed class NatsConfLexer { private const char Eof = '\0'; private const char MapStartChar = '{'; private const char MapEndChar = '}'; private const char KeySepEqual = '='; private const char KeySepColon = ':'; private const char ArrayStartChar = '['; private const char ArrayEndChar = ']'; private const char ArrayValTerm = ','; private const char MapValTerm = ','; private const char CommentHashStart = '#'; private const char CommentSlashStart = '/'; private const char DqStringStart = '"'; private const char DqStringEnd = '"'; private const char SqStringStart = '\''; private const char SqStringEnd = '\''; private const char OptValTerm = ';'; private const char TopOptStart = '{'; private const char TopOptTerm = '}'; private const char TopOptValTerm = ','; private const char BlockStartChar = '('; private const char BlockEndChar = ')'; private delegate LexState? LexState(NatsConfLexer lx); private readonly string _input; private int _start; private int _pos; private int _width; private int _line; private readonly List _items; private readonly Stack _stack; private readonly List _stringParts; private LexState? _stringStateFn; // Start position of the current line (after newline character). private int _lstart; // Start position of the line from the current item. private int _ilstart; private NatsConfLexer(string input) { _input = input; _start = 0; _pos = 0; _width = 0; _line = 1; _items = []; _stack = new Stack(); _stringParts = []; _lstart = 0; _ilstart = 0; } public static IReadOnlyList Tokenize(string input) { ArgumentNullException.ThrowIfNull(input); var lx = new NatsConfLexer(input); LexState? state = LexTop; while (state is not null) { state = state(lx); } return lx._items; } private void Push(LexState state) => _stack.Push(state); private LexState? Pop() { if (_stack.Count == 0) { return Errorf("BUG in lexer: no states to pop."); } return _stack.Pop(); } private void Emit(TokenType type) { string val; if (_stringParts.Count > 0) { val = string.Concat(_stringParts) + _input[_start.._pos]; _stringParts.Clear(); } else { val = _input[_start.._pos]; } var pos = _pos - _ilstart - val.Length; _items.Add(new Token(type, val, _line, pos)); _start = _pos; _ilstart = _lstart; } private void EmitString() { string finalString; if (_stringParts.Count > 0) { finalString = string.Concat(_stringParts) + _input[_start.._pos]; _stringParts.Clear(); } else { finalString = _input[_start.._pos]; } var pos = _pos - _ilstart - finalString.Length; _items.Add(new Token(TokenType.String, finalString, _line, pos)); _start = _pos; _ilstart = _lstart; } private void AddCurrentStringPart(int offset) { _stringParts.Add(_input[_start..(_pos - offset)]); _start = _pos; } private LexState? AddStringPart(string s) { _stringParts.Add(s); _start = _pos; return _stringStateFn; } private bool HasEscapedParts() => _stringParts.Count > 0; private char Next() { if (_pos >= _input.Length) { _width = 0; return Eof; } if (_input[_pos] == '\n') { _line++; _lstart = _pos; } var c = _input[_pos]; _width = 1; _pos += _width; return c; } private void Ignore() { _start = _pos; _ilstart = _lstart; } private void Backup() { _pos -= _width; if (_pos < _input.Length && _input[_pos] == '\n') { _line--; } } private char Peek() { var r = Next(); Backup(); return r; } private LexState? Errorf(string message) { var pos = _pos - _lstart; _items.Add(new Token(TokenType.Error, message, _line, pos)); return null; } // --- Helper methods --- private static bool IsWhitespace(char c) => c is '\t' or ' '; private static bool IsNL(char c) => c is '\n' or '\r'; private static bool IsKeySeparator(char c) => c is KeySepEqual or KeySepColon; private static bool IsNumberSuffix(char c) => c is 'k' or 'K' or 'm' or 'M' or 'g' or 'G' or 't' or 'T' or 'p' or 'P' or 'e' or 'E'; // --- State functions --- private static LexState? LexTop(NatsConfLexer lx) { var r = lx.Next(); if (char.IsWhiteSpace(r)) { return LexSkip(lx, LexTop); } switch (r) { case TopOptStart: lx.Push(LexTop); return LexSkip(lx, LexBlockStart); case CommentHashStart: lx.Push(LexTop); return LexCommentStart; case CommentSlashStart: { var rn = lx.Next(); if (rn == CommentSlashStart) { lx.Push(LexTop); return LexCommentStart; } lx.Backup(); goto case Eof; } case Eof: if (lx._pos > lx._start) { return lx.Errorf("Unexpected EOF."); } lx.Emit(TokenType.Eof); return null; } // Back up and let the key lexer handle it. lx.Backup(); lx.Push(LexTopValueEnd); return LexKeyStart; } private static LexState? LexTopValueEnd(NatsConfLexer lx) { var r = lx.Next(); switch (r) { case CommentHashStart: lx.Push(LexTop); return LexCommentStart; case CommentSlashStart: { var rn = lx.Next(); if (rn == CommentSlashStart) { lx.Push(LexTop); return LexCommentStart; } lx.Backup(); if (IsWhitespace(r)) { return LexTopValueEnd; } break; } default: if (IsWhitespace(r)) { return LexTopValueEnd; } break; } if (IsNL(r) || r == Eof || r == OptValTerm || r == TopOptValTerm || r == TopOptTerm) { lx.Ignore(); return LexTop; } return lx.Errorf($"Expected a top-level value to end with a new line, comment or EOF, but got '{EscapeSpecial(r)}' instead."); } private static LexState? LexBlockStart(NatsConfLexer lx) { var r = lx.Next(); if (char.IsWhiteSpace(r)) { return LexSkip(lx, LexBlockStart); } switch (r) { case TopOptStart: lx.Push(LexBlockEnd); return LexSkip(lx, LexBlockStart); case TopOptTerm: lx.Ignore(); return lx.Pop(); case CommentHashStart: lx.Push(LexBlockStart); return LexCommentStart; case CommentSlashStart: { var rn = lx.Next(); if (rn == CommentSlashStart) { lx.Push(LexBlockStart); return LexCommentStart; } lx.Backup(); goto case Eof; } case Eof: if (lx._pos > lx._start) { return lx.Errorf("Unexpected EOF."); } lx.Emit(TokenType.Eof); return null; } lx.Backup(); lx.Push(LexBlockValueEnd); return LexKeyStart; } private static LexState? LexBlockValueEnd(NatsConfLexer lx) { var r = lx.Next(); switch (r) { case CommentHashStart: lx.Push(LexBlockValueEnd); return LexCommentStart; case CommentSlashStart: { var rn = lx.Next(); if (rn == CommentSlashStart) { lx.Push(LexBlockValueEnd); return LexCommentStart; } lx.Backup(); if (IsWhitespace(r)) { return LexBlockValueEnd; } break; } default: if (IsWhitespace(r)) { return LexBlockValueEnd; } break; } if (IsNL(r) || r == OptValTerm || r == TopOptValTerm) { lx.Ignore(); return LexBlockStart; } if (r == TopOptTerm) { lx.Backup(); return LexBlockEnd; } return lx.Errorf($"Expected a block-level value to end with a new line, comment or EOF, but got '{EscapeSpecial(r)}' instead."); } private static LexState? LexBlockEnd(NatsConfLexer lx) { var r = lx.Next(); switch (r) { case CommentHashStart: lx.Push(LexBlockStart); return LexCommentStart; case CommentSlashStart: { var rn = lx.Next(); if (rn == CommentSlashStart) { lx.Push(LexBlockStart); return LexCommentStart; } lx.Backup(); if (IsNL(r) || IsWhitespace(r)) { return LexBlockEnd; } break; } default: if (IsNL(r) || IsWhitespace(r)) { return LexBlockEnd; } break; } if (r == OptValTerm || r == TopOptValTerm) { lx.Ignore(); return LexBlockStart; } if (r == TopOptTerm) { lx.Ignore(); return lx.Pop(); } return lx.Errorf($"Expected a block-level to end with a '}}', but got '{EscapeSpecial(r)}' instead."); } private static LexState? LexKeyStart(NatsConfLexer lx) { var r = lx.Peek(); if (IsKeySeparator(r)) { return lx.Errorf($"Unexpected key separator '{r}'"); } if (char.IsWhiteSpace(r)) { lx.Next(); return LexSkip(lx, LexKeyStart); } if (r == DqStringStart) { lx.Next(); return LexSkip(lx, LexDubQuotedKey); } if (r == SqStringStart) { lx.Next(); return LexSkip(lx, LexQuotedKey); } lx.Ignore(); lx.Next(); return LexKey; } private static LexState? LexDubQuotedKey(NatsConfLexer lx) { var r = lx.Peek(); if (r == DqStringEnd) { lx.Emit(TokenType.Key); lx.Next(); return LexSkip(lx, LexKeyEnd); } if (r == Eof) { if (lx._pos > lx._start) { return lx.Errorf("Unexpected EOF."); } lx.Emit(TokenType.Eof); return null; } lx.Next(); return LexDubQuotedKey; } private static LexState? LexQuotedKey(NatsConfLexer lx) { var r = lx.Peek(); if (r == SqStringEnd) { lx.Emit(TokenType.Key); lx.Next(); return LexSkip(lx, LexKeyEnd); } if (r == Eof) { if (lx._pos > lx._start) { return lx.Errorf("Unexpected EOF."); } lx.Emit(TokenType.Eof); return null; } lx.Next(); return LexQuotedKey; } private LexState? KeyCheckKeyword(LexState fallThrough, LexState? push) { var key = _input[_start.._pos].ToLowerInvariant(); if (key == "include") { Ignore(); if (push is not null) { Push(push); } return LexIncludeStart; } Emit(TokenType.Key); return fallThrough; } private static LexState? LexIncludeStart(NatsConfLexer lx) { var r = lx.Next(); if (IsWhitespace(r)) { return LexSkip(lx, LexIncludeStart); } lx.Backup(); return LexInclude; } private static LexState? LexIncludeQuotedString(NatsConfLexer lx) { var r = lx.Next(); if (r == SqStringEnd) { lx.Backup(); lx.Emit(TokenType.Include); lx.Next(); lx.Ignore(); return lx.Pop(); } if (r == Eof) { return lx.Errorf("Unexpected EOF in quoted include"); } return LexIncludeQuotedString; } private static LexState? LexIncludeDubQuotedString(NatsConfLexer lx) { var r = lx.Next(); if (r == DqStringEnd) { lx.Backup(); lx.Emit(TokenType.Include); lx.Next(); lx.Ignore(); return lx.Pop(); } if (r == Eof) { return lx.Errorf("Unexpected EOF in double quoted include"); } return LexIncludeDubQuotedString; } private static LexState? LexIncludeString(NatsConfLexer lx) { var r = lx.Next(); if (IsNL(r) || r == Eof || r == OptValTerm || r == MapEndChar || IsWhitespace(r)) { lx.Backup(); lx.Emit(TokenType.Include); return lx.Pop(); } if (r == SqStringEnd) { lx.Backup(); lx.Emit(TokenType.Include); lx.Next(); lx.Ignore(); return lx.Pop(); } return LexIncludeString; } private static LexState? LexInclude(NatsConfLexer lx) { var r = lx.Next(); switch (r) { case SqStringStart: lx.Ignore(); return LexIncludeQuotedString; case DqStringStart: lx.Ignore(); return LexIncludeDubQuotedString; case ArrayStartChar: return lx.Errorf("Expected include value but found start of an array"); case MapStartChar: return lx.Errorf("Expected include value but found start of a map"); case BlockStartChar: return lx.Errorf("Expected include value but found start of a block"); case '\\': return lx.Errorf("Expected include value but found escape sequence"); } if (char.IsDigit(r) || r == '-') { return lx.Errorf("Expected include value but found start of a number"); } if (IsNL(r)) { return lx.Errorf("Expected include value but found new line"); } lx.Backup(); return LexIncludeString; } private static LexState? LexKey(NatsConfLexer lx) { var r = lx.Peek(); if (char.IsWhiteSpace(r) && !IsNL(r)) { // Spaces signal we could be looking at a keyword, e.g. include. return lx.KeyCheckKeyword(LexKeyEnd, null); } if (IsKeySeparator(r) || r == Eof) { lx.Emit(TokenType.Key); return LexKeyEnd; } if (IsNL(r)) { // Newline after key with no separator — check for keyword. return lx.KeyCheckKeyword(LexKeyEnd, null); } lx.Next(); return LexKey; } private static LexState? LexKeyEnd(NatsConfLexer lx) { var r = lx.Next(); if (char.IsWhiteSpace(r)) { return LexSkip(lx, LexKeyEnd); } if (IsKeySeparator(r)) { return LexSkip(lx, LexValue); } if (r == Eof) { lx.Emit(TokenType.Eof); return null; } // We start the value here. lx.Backup(); return LexValue; } private static LexState? LexValue(NatsConfLexer lx) { var r = lx.Next(); if (IsWhitespace(r)) { return LexSkip(lx, LexValue); } switch (r) { case ArrayStartChar: lx.Ignore(); lx.Emit(TokenType.ArrayStart); return LexArrayValue; case MapStartChar: lx.Ignore(); lx.Emit(TokenType.MapStart); return LexMapKeyStart; case SqStringStart: lx.Ignore(); return LexQuotedString; case DqStringStart: lx.Ignore(); lx._stringStateFn = LexDubQuotedString; return LexDubQuotedString; case '-': return LexNegNumberStart; case BlockStartChar: lx.Ignore(); return LexBlock; case '.': return lx.Errorf("Floats must start with a digit"); } if (char.IsDigit(r)) { lx.Backup(); return LexNumberOrDateOrStringOrIPStart; } if (IsNL(r)) { return lx.Errorf("Expected value but found new line"); } lx.Backup(); lx._stringStateFn = LexString; return LexString; } private static LexState? LexArrayValue(NatsConfLexer lx) { var r = lx.Next(); if (char.IsWhiteSpace(r)) { return LexSkip(lx, LexArrayValue); } switch (r) { case CommentHashStart: lx.Push(LexArrayValue); return LexCommentStart; case CommentSlashStart: { var rn = lx.Next(); if (rn == CommentSlashStart) { lx.Push(LexArrayValue); return LexCommentStart; } lx.Backup(); // fallthrough to ArrayValTerm check if (r == ArrayValTerm) { return lx.Errorf($"Unexpected array value terminator '{ArrayValTerm}'."); } break; } case ArrayValTerm: return lx.Errorf($"Unexpected array value terminator '{ArrayValTerm}'."); case ArrayEndChar: return LexArrayEnd; } lx.Backup(); lx.Push(LexArrayValueEnd); return LexValue; } private static LexState? LexArrayValueEnd(NatsConfLexer lx) { var r = lx.Next(); if (IsWhitespace(r)) { return LexSkip(lx, LexArrayValueEnd); } switch (r) { case CommentHashStart: lx.Push(LexArrayValueEnd); return LexCommentStart; case CommentSlashStart: { var rn = lx.Next(); if (rn == CommentSlashStart) { lx.Push(LexArrayValueEnd); return LexCommentStart; } lx.Backup(); // fallthrough if (r == ArrayValTerm || IsNL(r)) { return LexSkip(lx, LexArrayValue); } break; } case ArrayEndChar: return LexArrayEnd; } if (r == ArrayValTerm || IsNL(r)) { return LexSkip(lx, LexArrayValue); } return lx.Errorf($"Expected an array value terminator ',' or an array terminator ']', but got '{EscapeSpecial(r)}' instead."); } private static LexState? LexArrayEnd(NatsConfLexer lx) { lx.Ignore(); lx.Emit(TokenType.ArrayEnd); return lx.Pop(); } private static LexState? LexMapKeyStart(NatsConfLexer lx) { var r = lx.Peek(); if (IsKeySeparator(r)) { return lx.Errorf($"Unexpected key separator '{r}'."); } if (r == ArrayEndChar) { return lx.Errorf($"Unexpected array end '{r}' processing map."); } if (char.IsWhiteSpace(r)) { lx.Next(); return LexSkip(lx, LexMapKeyStart); } if (r == MapEndChar) { lx.Next(); return LexSkip(lx, LexMapEnd); } if (r == CommentHashStart) { lx.Next(); lx.Push(LexMapKeyStart); return LexCommentStart; } if (r == CommentSlashStart) { lx.Next(); var rn = lx.Next(); if (rn == CommentSlashStart) { lx.Push(LexMapKeyStart); return LexCommentStart; } lx.Backup(); } if (r == SqStringStart) { lx.Next(); return LexSkip(lx, LexMapQuotedKey); } if (r == DqStringStart) { lx.Next(); return LexSkip(lx, LexMapDubQuotedKey); } if (r == Eof) { return lx.Errorf("Unexpected EOF processing map."); } lx.Ignore(); lx.Next(); return LexMapKey; } private static LexState? LexMapQuotedKey(NatsConfLexer lx) { var r = lx.Peek(); if (r == Eof) { return lx.Errorf("Unexpected EOF processing quoted map key."); } if (r == SqStringEnd) { lx.Emit(TokenType.Key); lx.Next(); return LexSkip(lx, LexMapKeyEnd); } lx.Next(); return LexMapQuotedKey; } private static LexState? LexMapDubQuotedKey(NatsConfLexer lx) { var r = lx.Peek(); if (r == Eof) { return lx.Errorf("Unexpected EOF processing double quoted map key."); } if (r == DqStringEnd) { lx.Emit(TokenType.Key); lx.Next(); return LexSkip(lx, LexMapKeyEnd); } lx.Next(); return LexMapDubQuotedKey; } private static LexState? LexMapKey(NatsConfLexer lx) { var r = lx.Peek(); if (r == Eof) { return lx.Errorf("Unexpected EOF processing map key."); } if (char.IsWhiteSpace(r) && !IsNL(r)) { return lx.KeyCheckKeyword(LexMapKeyEnd, LexMapValueEnd); } if (IsNL(r)) { return lx.KeyCheckKeyword(LexMapKeyEnd, LexMapValueEnd); } if (IsKeySeparator(r)) { lx.Emit(TokenType.Key); return LexMapKeyEnd; } lx.Next(); return LexMapKey; } private static LexState? LexMapKeyEnd(NatsConfLexer lx) { var r = lx.Next(); if (char.IsWhiteSpace(r)) { return LexSkip(lx, LexMapKeyEnd); } if (IsKeySeparator(r)) { return LexSkip(lx, LexMapValue); } // We start the value here. lx.Backup(); return LexMapValue; } private static LexState? LexMapValue(NatsConfLexer lx) { var r = lx.Next(); if (char.IsWhiteSpace(r)) { return LexSkip(lx, LexMapValue); } if (r == MapValTerm) { return lx.Errorf($"Unexpected map value terminator '{MapValTerm}'."); } if (r == MapEndChar) { return LexSkip(lx, LexMapEnd); } lx.Backup(); lx.Push(LexMapValueEnd); return LexValue; } private static LexState? LexMapValueEnd(NatsConfLexer lx) { var r = lx.Next(); if (IsWhitespace(r)) { return LexSkip(lx, LexMapValueEnd); } switch (r) { case CommentHashStart: lx.Push(LexMapValueEnd); return LexCommentStart; case CommentSlashStart: { var rn = lx.Next(); if (rn == CommentSlashStart) { lx.Push(LexMapValueEnd); return LexCommentStart; } lx.Backup(); // fallthrough if (r == OptValTerm || r == MapValTerm || IsNL(r)) { return LexSkip(lx, LexMapKeyStart); } break; } } if (r == OptValTerm || r == MapValTerm || IsNL(r)) { return LexSkip(lx, LexMapKeyStart); } if (r == MapEndChar) { return LexSkip(lx, LexMapEnd); } return lx.Errorf($"Expected a map value terminator ',' or a map terminator '}}', but got '{EscapeSpecial(r)}' instead."); } private static LexState? LexMapEnd(NatsConfLexer lx) { lx.Ignore(); lx.Emit(TokenType.MapEnd); return lx.Pop(); } private bool IsBool() { var str = _input[_start.._pos].ToLowerInvariant(); return str is "true" or "false" or "on" or "off" or "yes" or "no"; } private bool IsVariable() { if (_start >= _input.Length) { return false; } if (_input[_start] == '$') { _start += 1; return true; } return false; } private static LexState? LexQuotedString(NatsConfLexer lx) { var r = lx.Next(); if (r == SqStringEnd) { lx.Backup(); lx.Emit(TokenType.String); lx.Next(); lx.Ignore(); return lx.Pop(); } if (r == Eof) { if (lx._pos > lx._start) { return lx.Errorf("Unexpected EOF."); } lx.Emit(TokenType.Eof); return null; } return LexQuotedString; } private static LexState? LexDubQuotedString(NatsConfLexer lx) { var r = lx.Next(); if (r == '\\') { lx.AddCurrentStringPart(1); return LexStringEscape; } if (r == DqStringEnd) { lx.Backup(); lx.EmitString(); lx.Next(); lx.Ignore(); return lx.Pop(); } if (r == Eof) { if (lx._pos > lx._start) { return lx.Errorf("Unexpected EOF."); } lx.Emit(TokenType.Eof); return null; } return LexDubQuotedString; } private static LexState? LexString(NatsConfLexer lx) { var r = lx.Next(); if (r == '\\') { lx.AddCurrentStringPart(1); return LexStringEscape; } // Termination of non-quoted strings. if (IsNL(r) || r == Eof || r == OptValTerm || r == ArrayValTerm || r == ArrayEndChar || r == MapEndChar || IsWhitespace(r)) { lx.Backup(); if (lx.HasEscapedParts()) { lx.EmitString(); } else if (lx.IsBool()) { lx.Emit(TokenType.Bool); } else if (lx.IsVariable()) { lx.Emit(TokenType.Variable); } else { lx.EmitString(); } return lx.Pop(); } if (r == SqStringEnd) { lx.Backup(); lx.EmitString(); lx.Next(); lx.Ignore(); return lx.Pop(); } return LexString; } private static LexState? LexBlock(NatsConfLexer lx) { var r = lx.Next(); if (r == BlockEndChar) { lx.Backup(); lx.Backup(); // Looking for a ')' character on a line by itself. // If the previous character isn't a newline, keep processing. if (lx.Next() != '\n') { lx.Next(); return LexBlock; } lx.Next(); // Make sure the next character is a newline or EOF. var next = lx.Next(); if (next is '\n' or Eof) { lx.Backup(); lx.Backup(); lx.Emit(TokenType.String); lx.Next(); lx.Ignore(); return lx.Pop(); } lx.Backup(); return LexBlock; } if (r == Eof) { return lx.Errorf("Unexpected EOF processing block."); } return LexBlock; } private static LexState? LexStringEscape(NatsConfLexer lx) { var r = lx.Next(); return r switch { 'x' => LexStringBinary(lx), 't' => lx.AddStringPart("\t"), 'n' => lx.AddStringPart("\n"), 'r' => lx.AddStringPart("\r"), '"' => lx.AddStringPart("\""), '\\' => lx.AddStringPart("\\"), _ => lx.Errorf($"Invalid escape character '{EscapeSpecial(r)}'. Only the following escape characters are allowed: \\xXX, \\t, \\n, \\r, \\\", \\\\."), }; } private static LexState? LexStringBinary(NatsConfLexer lx) { var r1 = lx.Next(); if (IsNL(r1) || r1 == Eof) { return lx.Errorf("Expected two hexadecimal digits after '\\x', but hit end of line"); } var r2 = lx.Next(); if (IsNL(r2) || r2 == Eof) { return lx.Errorf("Expected two hexadecimal digits after '\\x', but hit end of line"); } var hexStr = lx._input[(lx._pos - 2)..lx._pos]; try { var bytes = Convert.FromHexString(hexStr); return lx.AddStringPart(System.Text.Encoding.Latin1.GetString(bytes)); } catch (FormatException) { return lx.Errorf($"Expected two hexadecimal digits after '\\x', but got '{hexStr}'"); } } private static LexState? LexNumberOrDateOrStringOrIPStart(NatsConfLexer lx) { var r = lx.Next(); if (!char.IsDigit(r)) { if (r == '.') { return lx.Errorf("Floats must start with a digit, not '.'."); } return lx.Errorf($"Expected a digit but got '{EscapeSpecial(r)}'."); } return LexNumberOrDateOrStringOrIP; } private static LexState? LexNumberOrDateOrStringOrIP(NatsConfLexer lx) { var r = lx.Next(); if (r == '-') { if (lx._pos - lx._start != 5) { return lx.Errorf("All ISO8601 dates must be in full Zulu form."); } return LexDateAfterYear; } if (char.IsDigit(r)) { return LexNumberOrDateOrStringOrIP; } if (r == '.') { return LexFloatStart; } if (IsNumberSuffix(r)) { return LexConvenientNumber; } // Check if this is a terminator or a string character. if (!(IsNL(r) || r == Eof || r == MapEndChar || r == OptValTerm || r == MapValTerm || IsWhitespace(r) || r == ArrayValTerm || r == ArrayEndChar)) { // Treat it as a string value. lx._stringStateFn = LexString; return LexString; } lx.Backup(); lx.Emit(TokenType.Integer); return lx.Pop(); } private static LexState? LexConvenientNumber(NatsConfLexer lx) { var r = lx.Next(); if (r is 'b' or 'B' or 'i' or 'I') { return LexConvenientNumber; } lx.Backup(); if (IsNL(r) || r == Eof || r == MapEndChar || r == OptValTerm || r == MapValTerm || IsWhitespace(r) || char.IsDigit(r) || r == ArrayValTerm || r == ArrayEndChar) { lx.Emit(TokenType.Integer); return lx.Pop(); } // This is not a number, treat as string. lx._stringStateFn = LexString; return LexString; } private static LexState? LexDateAfterYear(NatsConfLexer lx) { // Expected: MM-DDTHH:MM:SSZ char[] formats = [ '0', '0', '-', '0', '0', 'T', '0', '0', ':', '0', '0', ':', '0', '0', 'Z', ]; foreach (var f in formats) { var r = lx.Next(); if (f == '0') { if (!char.IsDigit(r)) { return lx.Errorf($"Expected digit in ISO8601 datetime, but found '{EscapeSpecial(r)}' instead."); } } else if (f != r) { return lx.Errorf($"Expected '{f}' in ISO8601 datetime, but found '{EscapeSpecial(r)}' instead."); } } lx.Emit(TokenType.DateTime); return lx.Pop(); } private static LexState? LexNegNumberStart(NatsConfLexer lx) { var r = lx.Next(); if (!char.IsDigit(r)) { if (r == '.') { return lx.Errorf("Floats must start with a digit, not '.'."); } return lx.Errorf($"Expected a digit but got '{EscapeSpecial(r)}'."); } return LexNegNumber; } private static LexState? LexNegNumber(NatsConfLexer lx) { var r = lx.Next(); if (char.IsDigit(r)) { return LexNegNumber; } if (r == '.') { return LexFloatStart; } if (IsNumberSuffix(r)) { return LexConvenientNumber; } lx.Backup(); lx.Emit(TokenType.Integer); return lx.Pop(); } private static LexState? LexFloatStart(NatsConfLexer lx) { var r = lx.Next(); if (!char.IsDigit(r)) { return lx.Errorf($"Floats must have a digit after the '.', but got '{EscapeSpecial(r)}' instead."); } return LexFloat; } private static LexState? LexFloat(NatsConfLexer lx) { var r = lx.Next(); if (char.IsDigit(r)) { return LexFloat; } // Not a digit; if it's another '.', this might be an IP address. if (r == '.') { return LexIPAddr; } lx.Backup(); lx.Emit(TokenType.Float); return lx.Pop(); } private static LexState? LexIPAddr(NatsConfLexer lx) { var r = lx.Next(); if (char.IsDigit(r) || r is '.' or ':' or '-') { return LexIPAddr; } lx.Backup(); lx.Emit(TokenType.String); return lx.Pop(); } private static LexState? LexCommentStart(NatsConfLexer lx) { lx.Ignore(); lx.Emit(TokenType.Comment); return LexComment; } private static LexState? LexComment(NatsConfLexer lx) { var r = lx.Peek(); if (IsNL(r) || r == Eof) { // Consume the comment text but don't emit it as a user-visible token. // Just ignore it and pop back. lx.Ignore(); return lx.Pop(); } lx.Next(); return LexComment; } private static LexState LexSkip(NatsConfLexer lx, LexState nextState) { lx.Ignore(); return nextState; } private static string EscapeSpecial(char c) => c switch { '\n' => "\\n", '\r' => "\\r", '\t' => "\\t", Eof => "EOF", _ => c.ToString(), }; }