2018-12-17 23:01:06 +00:00
|
|
|
import
|
2019-11-04 18:42:34 +00:00
|
|
|
unicode,
|
2020-05-05 17:28:44 +00:00
|
|
|
faststreams/inputs,
|
2019-01-21 17:40:14 +00:00
|
|
|
types
|
2018-12-17 23:01:06 +00:00
|
|
|
|
|
|
|
export
|
2020-05-05 17:28:44 +00:00
|
|
|
inputs
|
2018-12-17 23:01:06 +00:00
|
|
|
|
|
|
|
type
|
|
|
|
TokKind* = enum
|
|
|
|
tkError,
|
|
|
|
tkEof,
|
|
|
|
tkString,
|
|
|
|
tkInt,
|
2020-03-24 18:08:35 +00:00
|
|
|
tkNegativeInt,
|
2018-12-17 23:01:06 +00:00
|
|
|
tkFloat,
|
|
|
|
tkTrue,
|
|
|
|
tkFalse,
|
|
|
|
tkNull,
|
|
|
|
tkCurlyLe,
|
|
|
|
tkCurlyRi,
|
|
|
|
tkBracketLe,
|
|
|
|
tkBracketRi,
|
|
|
|
tkColon,
|
|
|
|
tkComma
|
|
|
|
|
2019-03-19 23:54:03 +00:00
|
|
|
JsonErrorKind* = enum
|
2018-12-17 23:01:06 +00:00
|
|
|
errNone = "no error",
|
|
|
|
errHexCharExpected = "hex char expected (part of escape sequence)",
|
|
|
|
errStringExpected = "string expected",
|
|
|
|
errColonExpected = "':' expected",
|
|
|
|
errCommaExpected = "',' expected",
|
|
|
|
errBracketRiExpected = "']' expected",
|
|
|
|
errCurlyRiExpected = "'}' expected",
|
|
|
|
errQuoteExpected = "'\"' or \"'\" expected",
|
|
|
|
errNumberExpected = "number expected",
|
|
|
|
errExponentTooLarge = "exponent too large",
|
|
|
|
errUnexpectedEof = "unexpected end of file",
|
|
|
|
errCommentExpected = "comment expected"
|
|
|
|
errOrphanSurrogate = "unicode surrogates must be followed by another unicode character"
|
2019-01-21 17:40:14 +00:00
|
|
|
errNonPortableInt = "number is outside the range of portable values"
|
2018-12-17 23:01:06 +00:00
|
|
|
|
|
|
|
JsonLexer* = object
|
2020-07-17 20:58:02 +00:00
|
|
|
stream*: InputStream
|
2019-01-21 17:40:14 +00:00
|
|
|
mode*: JsonMode
|
2018-12-17 23:01:06 +00:00
|
|
|
|
|
|
|
line*: int
|
|
|
|
lineStartPos: int
|
|
|
|
tokenStart: int
|
|
|
|
|
|
|
|
tok*: TokKind
|
2019-03-19 23:54:03 +00:00
|
|
|
err*: JsonErrorKind
|
2018-12-17 23:01:06 +00:00
|
|
|
|
2020-03-24 18:08:35 +00:00
|
|
|
absIntVal*: uint64 # BEWARE: negative integers will have tok == tkNegativeInt
|
2018-12-17 23:01:06 +00:00
|
|
|
floatVal*: float
|
|
|
|
strVal*: string
|
|
|
|
|
|
|
|
const
|
|
|
|
powersOfTen = [1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9,
|
|
|
|
1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19,
|
|
|
|
1e20, 1e21, 1e22] # TODO: this table should be much larger
|
|
|
|
# The largest JSON number value is 1E308
|
|
|
|
|
2020-07-17 20:58:02 +00:00
|
|
|
proc renderTok*(s: JsonLexer): string =
|
|
|
|
case s.tok
|
|
|
|
of tkError, tkEof: ""
|
|
|
|
of tkString: ""
|
|
|
|
of tkInt: ""
|
|
|
|
of tkNegativeInt: ""
|
|
|
|
of tkFloat: ""
|
|
|
|
of tkTrue: "true"
|
|
|
|
of tkFalse: "false"
|
|
|
|
of tkNull: "null"
|
|
|
|
of tkCurlyLe: "{"
|
|
|
|
of tkCurlyRi: "}"
|
|
|
|
of tkBracketLe: "["
|
|
|
|
of tkBracketRi: "]"
|
|
|
|
of tkColon: ":"
|
|
|
|
of tkComma: ","
|
|
|
|
|
2020-04-13 14:01:49 +00:00
|
|
|
template peek(s: InputStream): char =
|
2020-05-05 17:28:44 +00:00
|
|
|
char inputs.peek(s)
|
2020-04-13 14:01:49 +00:00
|
|
|
|
|
|
|
template read(s: InputStream): char =
|
2020-05-05 17:28:44 +00:00
|
|
|
char inputs.read(s)
|
2020-04-13 14:01:49 +00:00
|
|
|
|
2018-12-17 23:01:06 +00:00
|
|
|
proc hexCharValue(c: char): int {.inline.} =
|
|
|
|
case c
|
|
|
|
of '0'..'9': ord(c) - ord('0')
|
|
|
|
of 'a'..'f': ord(c) - ord('a') + 10
|
|
|
|
of 'A'..'F': ord(c) - ord('A') + 10
|
|
|
|
else: -1
|
|
|
|
|
|
|
|
proc isDigit(c: char): bool {.inline.} =
|
|
|
|
return (c >= '0' and c <= '9')
|
|
|
|
|
|
|
|
proc col*(lexer: JsonLexer): int =
|
2020-04-10 13:46:12 +00:00
|
|
|
lexer.stream.pos - lexer.lineStartPos
|
2018-12-17 23:01:06 +00:00
|
|
|
|
2019-07-21 14:12:48 +00:00
|
|
|
proc tokenStartCol*(lexer: JsonLexer): int =
|
|
|
|
1 + lexer.tokenStart - lexer.lineStartPos
|
|
|
|
|
2020-04-13 14:01:49 +00:00
|
|
|
proc init*(T: type JsonLexer, stream: InputStream, mode = defaultJsonMode): T =
|
2018-12-17 23:01:06 +00:00
|
|
|
T(stream: stream,
|
2019-01-21 17:40:14 +00:00
|
|
|
mode: mode,
|
2019-07-21 14:12:48 +00:00
|
|
|
line: 1,
|
2018-12-17 23:01:06 +00:00
|
|
|
lineStartPos: 0,
|
|
|
|
tokenStart: -1,
|
|
|
|
tok: tkError,
|
|
|
|
err: errNone,
|
2020-03-24 18:08:35 +00:00
|
|
|
absIntVal: uint64 0,
|
2018-12-17 23:01:06 +00:00
|
|
|
floatVal: 0'f,
|
|
|
|
strVal: "")
|
|
|
|
|
2019-03-19 23:54:03 +00:00
|
|
|
template error(error: JsonErrorKind) {.dirty.} =
|
2018-12-17 23:01:06 +00:00
|
|
|
lexer.err = error
|
|
|
|
lexer.tok = tkError
|
|
|
|
return
|
|
|
|
|
|
|
|
template checkForUnexpectedEof {.dirty.} =
|
2020-04-14 14:07:51 +00:00
|
|
|
if not lexer.stream.readable:
|
|
|
|
error errUnexpectedEof
|
2018-12-17 23:01:06 +00:00
|
|
|
|
|
|
|
template requireNextChar(): char =
|
|
|
|
checkForUnexpectedEof()
|
2020-04-10 13:46:12 +00:00
|
|
|
lexer.stream.read()
|
2018-12-17 23:01:06 +00:00
|
|
|
|
2019-01-21 17:40:14 +00:00
|
|
|
template checkForNonPortableInt(val: uint64) =
|
|
|
|
if lexer.mode == Portable and val > uint64(maxPortableInt):
|
|
|
|
error errNonPortableInt
|
|
|
|
|
2018-12-17 23:01:06 +00:00
|
|
|
proc scanHexRune(lexer: var JsonLexer): int =
|
|
|
|
for i in 0..3:
|
|
|
|
let hexValue = hexCharValue requireNextChar()
|
|
|
|
if hexValue == -1: error errHexCharExpected
|
|
|
|
result = (result shl 4) or hexValue
|
|
|
|
|
|
|
|
proc scanString(lexer: var JsonLexer) =
|
|
|
|
lexer.tok = tkString
|
|
|
|
lexer.strVal.setLen 0
|
2020-04-10 13:46:12 +00:00
|
|
|
lexer.tokenStart = lexer.stream.pos
|
2018-12-17 23:01:06 +00:00
|
|
|
|
2020-04-10 13:46:12 +00:00
|
|
|
advance lexer.stream
|
2018-12-17 23:01:06 +00:00
|
|
|
|
|
|
|
while true:
|
|
|
|
var c = requireNextChar()
|
|
|
|
case c
|
|
|
|
of '"':
|
|
|
|
break
|
|
|
|
of '\\':
|
|
|
|
c = requireNextChar()
|
|
|
|
case c
|
|
|
|
of '\\', '"', '\'', '/':
|
|
|
|
lexer.strVal.add c
|
|
|
|
of 'b':
|
|
|
|
lexer.strVal.add '\b'
|
|
|
|
of 'f':
|
|
|
|
lexer.strVal.add '\f'
|
|
|
|
of 'n':
|
|
|
|
lexer.strVal.add '\n'
|
|
|
|
of 'r':
|
|
|
|
lexer.strVal.add '\r'
|
|
|
|
of 't':
|
|
|
|
lexer.strVal.add '\t'
|
|
|
|
of 'v':
|
|
|
|
lexer.strVal.add '\x0B'
|
|
|
|
of '0':
|
|
|
|
lexer.strVal.add '\x00'
|
|
|
|
of 'u':
|
|
|
|
var rune = lexer.scanHexRune()
|
|
|
|
if lexer.tok == tkError: return
|
|
|
|
# Deal with surrogates
|
|
|
|
if (rune and 0xfc00) == 0xd800:
|
|
|
|
if requireNextChar() != '\\': error errOrphanSurrogate
|
|
|
|
if requireNextChar() != 'u': error errOrphanSurrogate
|
|
|
|
let nextRune = lexer.scanHexRune()
|
|
|
|
if lexer.tok == tkError: return
|
|
|
|
if (nextRune and 0xfc00) == 0xdc00:
|
|
|
|
rune = 0x10000 + (((rune - 0xd800) shl 10) or (nextRune - 0xdc00))
|
|
|
|
lexer.strVal.add toUTF8(Rune(rune))
|
|
|
|
else:
|
|
|
|
# don't bother with the error
|
|
|
|
lexer.strVal.add c
|
|
|
|
of '\r', '\n':
|
|
|
|
error errQuoteExpected
|
|
|
|
else:
|
|
|
|
lexer.strVal.add c
|
|
|
|
|
|
|
|
proc handleLF(lexer: var JsonLexer) {.inline.} =
|
2020-04-10 13:46:12 +00:00
|
|
|
advance lexer.stream
|
2018-12-17 23:01:06 +00:00
|
|
|
lexer.line += 1
|
2020-04-10 13:46:12 +00:00
|
|
|
lexer.lineStartPos = lexer.stream.pos
|
2018-12-17 23:01:06 +00:00
|
|
|
|
|
|
|
proc skipWhitespace(lexer: var JsonLexer) =
|
|
|
|
template handleCR =
|
|
|
|
# Beware: this is a template, because the return
|
|
|
|
# statement has to exit `skipWhitespace`.
|
2020-04-10 13:46:12 +00:00
|
|
|
advance lexer.stream
|
2020-04-14 14:07:51 +00:00
|
|
|
if not lexer.stream.readable: return
|
2020-04-10 13:46:12 +00:00
|
|
|
if lexer.stream.peek() == '\n': advance lexer.stream
|
2018-12-17 23:01:06 +00:00
|
|
|
lexer.line += 1
|
2020-04-10 13:46:12 +00:00
|
|
|
lexer.lineStartPos = lexer.stream.pos
|
2018-12-17 23:01:06 +00:00
|
|
|
|
2020-04-14 14:07:51 +00:00
|
|
|
while lexer.stream.readable:
|
2020-04-10 13:46:12 +00:00
|
|
|
case lexer.stream.peek()
|
2018-12-17 23:01:06 +00:00
|
|
|
of '/':
|
2020-04-10 13:46:12 +00:00
|
|
|
advance lexer.stream
|
2018-12-17 23:01:06 +00:00
|
|
|
checkForUnexpectedEof()
|
2020-04-10 13:46:12 +00:00
|
|
|
case lexer.stream.peek()
|
2018-12-17 23:01:06 +00:00
|
|
|
of '/':
|
|
|
|
while true:
|
2020-04-10 13:46:12 +00:00
|
|
|
advance lexer.stream
|
2020-04-14 14:07:51 +00:00
|
|
|
if not lexer.stream.readable: return
|
2020-04-10 13:46:12 +00:00
|
|
|
case lexer.stream.peek()
|
2018-12-17 23:01:06 +00:00
|
|
|
of '\r':
|
|
|
|
handleCR()
|
|
|
|
of '\n':
|
|
|
|
lexer.handleLF()
|
|
|
|
else:
|
|
|
|
discard
|
|
|
|
of '*':
|
|
|
|
while true:
|
2020-04-10 13:46:12 +00:00
|
|
|
advance lexer.stream
|
2020-04-14 14:07:51 +00:00
|
|
|
if not lexer.stream.readable: return
|
2020-04-10 13:46:12 +00:00
|
|
|
case lexer.stream.peek()
|
2018-12-17 23:01:06 +00:00
|
|
|
of '\r':
|
|
|
|
handleCR()
|
|
|
|
of '\n':
|
|
|
|
lexer.handleLF()
|
|
|
|
of '*':
|
2020-04-10 13:46:12 +00:00
|
|
|
advance lexer.stream
|
2018-12-17 23:01:06 +00:00
|
|
|
checkForUnexpectedEof()
|
2020-04-10 13:46:12 +00:00
|
|
|
if lexer.stream.peek() == '/':
|
|
|
|
advance lexer.stream
|
2018-12-17 23:01:06 +00:00
|
|
|
return
|
|
|
|
else:
|
|
|
|
discard
|
|
|
|
else:
|
|
|
|
error errCommentExpected
|
|
|
|
of ' ', '\t':
|
2020-04-10 13:46:12 +00:00
|
|
|
advance lexer.stream
|
2018-12-17 23:01:06 +00:00
|
|
|
of '\r':
|
|
|
|
handleCR()
|
|
|
|
of '\n':
|
|
|
|
lexer.handleLF()
|
|
|
|
else:
|
|
|
|
break
|
|
|
|
|
|
|
|
template requireMoreNumberChars(elseClause) =
|
2020-04-14 14:07:51 +00:00
|
|
|
if not lexer.stream.readable:
|
2018-12-17 23:01:06 +00:00
|
|
|
elseClause
|
|
|
|
error errNumberExpected
|
|
|
|
|
|
|
|
template eatDigitAndPeek: char =
|
2020-04-10 13:46:12 +00:00
|
|
|
advance lexer.stream
|
2020-04-14 14:07:51 +00:00
|
|
|
if not lexer.stream.readable: return
|
2020-04-10 13:46:12 +00:00
|
|
|
lexer.stream.peek()
|
2018-12-17 23:01:06 +00:00
|
|
|
|
|
|
|
proc scanSign(lexer: var JsonLexer): int =
|
|
|
|
# Returns +1 or -1
|
|
|
|
# If a sign character is present, it must be followed
|
|
|
|
# by more characters representing the number. If this
|
|
|
|
# is not the case, the return value will be 0.
|
2020-04-10 13:46:12 +00:00
|
|
|
let c = lexer.stream.peek()
|
2018-12-17 23:01:06 +00:00
|
|
|
if c == '-':
|
|
|
|
requireMoreNumberChars: result = 0
|
2020-04-10 13:46:12 +00:00
|
|
|
advance lexer.stream
|
2018-12-17 23:01:06 +00:00
|
|
|
return -1
|
|
|
|
elif c == '+':
|
|
|
|
requireMoreNumberChars: result = 0
|
2020-04-10 13:46:12 +00:00
|
|
|
advance lexer.stream
|
2018-12-17 23:01:06 +00:00
|
|
|
return 1
|
|
|
|
|
2019-01-21 17:40:14 +00:00
|
|
|
proc scanInt(lexer: var JsonLexer): uint64 =
|
2020-04-10 13:46:12 +00:00
|
|
|
var c = lexer.stream.peek()
|
2019-01-21 17:40:14 +00:00
|
|
|
result = uint64(ord(c) - ord('0'))
|
2018-12-17 23:01:06 +00:00
|
|
|
|
|
|
|
c = eatDigitAndPeek()
|
|
|
|
while c.isDigit:
|
2019-01-21 17:40:14 +00:00
|
|
|
result = result * 10 + uint64(ord(c) - ord('0'))
|
2018-12-17 23:01:06 +00:00
|
|
|
c = eatDigitAndPeek()
|
|
|
|
|
|
|
|
proc scanNumber(lexer: var JsonLexer) =
|
|
|
|
var sign = lexer.scanSign()
|
|
|
|
if sign == 0: return
|
2020-04-10 13:46:12 +00:00
|
|
|
var c = lexer.stream.peek()
|
2018-12-17 23:01:06 +00:00
|
|
|
|
|
|
|
if c == '.':
|
2020-04-10 13:46:12 +00:00
|
|
|
advance lexer.stream
|
2018-12-17 23:01:06 +00:00
|
|
|
requireMoreNumberChars: discard
|
|
|
|
lexer.tok = tkFloat
|
2020-04-10 13:46:12 +00:00
|
|
|
c = lexer.stream.peek()
|
2018-12-17 23:01:06 +00:00
|
|
|
elif c.isDigit:
|
2020-03-24 18:08:35 +00:00
|
|
|
lexer.tok = if sign > 0: tkInt
|
|
|
|
else: tkNegativeInt
|
2019-01-21 17:40:14 +00:00
|
|
|
let scannedValue = lexer.scanInt()
|
|
|
|
checkForNonPortableInt scannedValue
|
2020-03-24 18:08:35 +00:00
|
|
|
lexer.absIntVal = scannedValue
|
2020-04-14 14:07:51 +00:00
|
|
|
if not lexer.stream.readable: return
|
2020-04-10 13:46:12 +00:00
|
|
|
c = lexer.stream.peek()
|
2018-12-17 23:01:06 +00:00
|
|
|
if c == '.':
|
|
|
|
lexer.tok = tkFloat
|
2020-03-24 18:08:35 +00:00
|
|
|
lexer.floatVal = float(lexer.absIntVal) * float(sign)
|
2018-12-17 23:01:06 +00:00
|
|
|
c = eatDigitAndPeek()
|
|
|
|
else:
|
|
|
|
error errNumberExpected
|
|
|
|
|
|
|
|
var fraction = 0.1'f
|
|
|
|
while c.isDigit:
|
|
|
|
lexer.floatVal += fraction * float(ord(c) - ord('0'))
|
|
|
|
fraction *= 0.1'f
|
|
|
|
c = eatDigitAndPeek()
|
|
|
|
|
|
|
|
if c in {'E', 'e'}:
|
2020-04-10 13:46:12 +00:00
|
|
|
advance lexer.stream
|
2018-12-17 23:01:06 +00:00
|
|
|
requireMoreNumberChars: discard
|
|
|
|
let sign = lexer.scanSign()
|
|
|
|
if sign == 0: return
|
2020-04-10 13:46:12 +00:00
|
|
|
if not isDigit lexer.stream.peek():
|
2018-12-17 23:01:06 +00:00
|
|
|
error errNumberExpected
|
|
|
|
|
|
|
|
let exponent = lexer.scanInt()
|
2019-01-21 17:40:14 +00:00
|
|
|
if exponent >= uint64(len(powersOfTen)):
|
2018-12-17 23:01:06 +00:00
|
|
|
error errExponentTooLarge
|
|
|
|
|
|
|
|
if sign > 0:
|
|
|
|
lexer.floatVal = lexer.floatVal * powersOfTen[exponent]
|
|
|
|
else:
|
|
|
|
lexer.floatVal = lexer.floatVal / powersOfTen[exponent]
|
|
|
|
|
|
|
|
proc scanIdentifier(lexer: var JsonLexer,
|
|
|
|
expectedIdent: string, expectedTok: TokKind) =
|
|
|
|
for c in expectedIdent:
|
2020-04-10 13:46:12 +00:00
|
|
|
if c != lexer.stream.read():
|
2018-12-17 23:01:06 +00:00
|
|
|
lexer.tok = tkError
|
|
|
|
return
|
|
|
|
lexer.tok = expectedTok
|
|
|
|
|
|
|
|
proc next*(lexer: var JsonLexer) =
|
|
|
|
lexer.skipWhitespace()
|
|
|
|
|
2020-04-14 14:07:51 +00:00
|
|
|
if not lexer.stream.readable:
|
2018-12-17 23:01:06 +00:00
|
|
|
lexer.tok = tkEof
|
|
|
|
return
|
|
|
|
|
2020-04-10 13:46:12 +00:00
|
|
|
let c = lexer.stream.peek()
|
2018-12-17 23:01:06 +00:00
|
|
|
case c
|
|
|
|
of '+', '-', '.', '0'..'9':
|
|
|
|
lexer.scanNumber()
|
|
|
|
of '"':
|
|
|
|
lexer.scanString()
|
|
|
|
of '[':
|
2020-04-10 13:46:12 +00:00
|
|
|
advance lexer.stream
|
2018-12-17 23:01:06 +00:00
|
|
|
lexer.tok = tkBracketLe
|
|
|
|
of '{':
|
2020-04-10 13:46:12 +00:00
|
|
|
advance lexer.stream
|
2018-12-17 23:01:06 +00:00
|
|
|
lexer.tok = tkCurlyLe
|
|
|
|
of ']':
|
2020-04-10 13:46:12 +00:00
|
|
|
advance lexer.stream
|
2018-12-17 23:01:06 +00:00
|
|
|
lexer.tok = tkBracketRi
|
|
|
|
of '}':
|
2020-04-10 13:46:12 +00:00
|
|
|
advance lexer.stream
|
2018-12-17 23:01:06 +00:00
|
|
|
lexer.tok = tkCurlyRi
|
|
|
|
of ',':
|
2020-04-10 13:46:12 +00:00
|
|
|
advance lexer.stream
|
2018-12-17 23:01:06 +00:00
|
|
|
lexer.tok = tkComma
|
|
|
|
of ':':
|
2020-04-10 13:46:12 +00:00
|
|
|
advance lexer.stream
|
2018-12-17 23:01:06 +00:00
|
|
|
lexer.tok = tkColon
|
|
|
|
of '\0':
|
|
|
|
lexer.tok = tkEof
|
|
|
|
of 'n': lexer.scanIdentifier("null", tkNull)
|
|
|
|
of 't': lexer.scanIdentifier("true", tkTrue)
|
|
|
|
of 'f': lexer.scanIdentifier("false", tkFalse)
|
|
|
|
else:
|
2020-04-10 13:46:12 +00:00
|
|
|
advance lexer.stream
|
2018-12-17 23:01:06 +00:00
|
|
|
lexer.tok = tkError
|
|
|
|
|