More json sketching.

This commit is contained in:
Jesse Brault 2025-11-10 22:08:09 -06:00
parent 053d08849e
commit 803b95c5dc

View File

@ -1,21 +1,21 @@
ns jsonexample ns jsonexample
fn main() fn main()
let rawJson: String = match io::readFile("myDoc.json") let o: obj = match parseJsonObj('{ "a": { "b": { "c": [42] }} }')
Success(s) => s,
Failure(err) => exit(err)
end
let o: obj = match parseJsonObj(rawJson)
Success(o) => o, Success(o) => o,
Failure(err) => exit(err) Failure(err) => panic!("There was an error during Json parsing: ${err}")
end end
assertEq!(o.a.b.c[0], 42) assertEq!(o.a.b.c[0], 42)
end end
fn parseJsonObj(s: String) -> obj fn parseJsonObj(s: String) -> Result<obj, JsonParseError>
let lexer = JsonLexer(s) let lexer = JsonLexer(s)
let parser = JsonParser(lexer) let parser = JsonParser(lexer)
parser.object() try
parser.object()
catch (parseError: JsonParseError)
Failure(parseError)
end
end end
enum JsonToken enum JsonToken
@ -29,20 +29,58 @@ enum JsonToken
Boolean(Boolean) Boolean(Boolean)
end end
class String platform class String
pub fn <R> matchAt(startIndex: Int, matchers: Map<Regex, R | fn (match: String) -> R), opts: obj { pub fn <R> matchAt(startIndex: Int, matchers: Map<Regex, R | fn (match: String) -> R), opts: obj + { ignoreWhitespace: Boolean }) -> Option<R>
ignoreWhitespace: boolean, let slice: String = if opts.ignoreWhitespace then
endIndex: Int /[\t\n\r ]*(<rest>.*)/.match(self[startIndex..]).rest as String
}) -> Option<R> else
todo!() self[startIndex..]
end
for [regex, result] in matchers do
if regex.test(slice) is Some(match) then
if result is Fn then
Some(result(match))
else
Some(result)
end
end
end
None
end end
end end
platform class Regex
int RegexMatchBase
val match: String
val groups: [String],
val namedGroups: Map<String, String>
end
pub int RegexMatch
props!::<RegexMatchBase>()
end
platform fn doMatch(s) -> Option<RegexMatchBase>
pub fn match(s: String) -> Option<obj : RegexMatch>
doMatch(s).map { base ->
dyn obj {
...base,
propertyMissing: { name: String ->
base.namedGroups.get(name).unwrap()
}
}
}
end
end
class JsonLexer(val source: String) class JsonLexer(val source: String)
var index: Int var index: Int
pub fn nextToken() -> Option<JsonToken> fn matchCurrent() -> Option<JsonToken>
let token: Option<JsonToken> = source.matchAt(index, [ source.matchAt(index, [
/\{/ => JsonToken::LBrace, /\{/ => JsonToken::LBrace,
/}/ => JsonToken::RBrace, /}/ => JsonToken::RBrace,
/\[/ => JsonToken::LSquare, /\[/ => JsonToken::LSquare,
@ -52,26 +90,49 @@ class JsonLexer(val source: String)
/false/ => JsonToken::False(false), /false/ => JsonToken::False(false),
// The following is not a good way for matching strings but whatever // The following is not a good way for matching strings but whatever
/"%w*"/ => { stringWithQuotes => JsonToken::String(stringWithQuotes[1..-1]) } /"%w*"/ => { stringWithQuotes => JsonToken::String(stringWithQuotes[1..-1]) }
], ignoreWhitespace: true) ], obj { ignoreWhitespace: true })
match token?
JsonToken::LBrace | JsonToken::RBrace | JsonToken::LSquare | JsonToken::RSquare | JsonToken::Comma => do index++ end
JsonToken::String(s) => index += s.len() + 2,
JsonToken::Number(n) => index += n.toString().len(),
JsonToken::Boolean(b) => match b
true => do index += 4 end
false => do index += 5 end
end
end
token
end end
pub fn nextToken() -> Option<JsonToken>
matchCurrent().ifSome { token ->
match token
JsonToken::LBrace | JsonToken::RBrace | JsonToken::LSquare | JsonToken::RSquare | JsonToken::Comma => index++,
JsonToken::String(s) => index += s.len() + 2,
JsonToken::Number(n) => index += n.toString().len(),
JsonToken::Boolean(b) => match b
true => index += 4,
false => index += 5,
end
end
}
end
pub fn peek() -> Option<JsonToken> alias matchCurrent
end end
class JsonParser(val lexer: JsonLexer) class JsonParser(val lexer: JsonLexer)
macro parseError!($message)
throw JsonParserError($message)
end
macro expect!($tokenType) macro expect!($tokenType)
match lexer.nextToken() match lexer.nextToken()
Some(JsonToken::$tokenType) => do end, Some(token) => match token
_ => panic! "Expected ${$tokenType}" JsonToken::$tokenType => do end,
_ => parseError!("Expected ${$tokenType}")
end,
_ => parseError!("Expected ${$tokenType}")
end
end
macro peek!($tokenType)
match lexer.peek()
Some(token) => match token
JsonToken::$tokenType => true,
_ => false
end,
None => false
end end
end end
@ -86,29 +147,65 @@ class JsonParser(val lexer: JsonLexer)
end end
fn keyValue() -> Option<(String, Any)> fn keyValue() -> Option<(String, Any)>
let key = match lexer.nextToken() let key = match lexer.peek()
Some(JsonToken::String(s)) => s, Some(token) => do
_ => return None match token
JsonToken::String(s) => s,
_ => parseError!("Expected String")
end
end,
_ => do return None end
end end
let value = value() let value = value()
Some((key, value)) Some((key, value))
end end
fn value() -> Any fn value() -> Any
match lexer.nextToken() match lexer.peek()
Some(token) => match token Some(token) => match token
JsonToken::String(s) => s,
JsonToken::Number(n) => n,
JsonToken::Boolean(b) => b,
JsonToken::LBrace => object(), JsonToken::LBrace => object(),
JsonToken::LSquare => array() JsonToken::LSquare => array(),
_ => do
match lexer.nextToken()
JsonToken::String(s) => s,
JsonToken::Number(n) => n,
JsonToken::Boolean(b) => b,
_ => parseError!("Expected String, Number, or Boolean")
end
end
end, end,
None => panic! "Expected LBrace, LSquare, String, Number, or Boolean" None => parseError!("Expected LBrace, LSquare, String, Number, or Boolean")
end end
end end
fn array() -> Array<Any> fn array() -> [Any]
todo!() expect!(LSquare)
let results: List<Any> = []
while true do
match lexer.peek()
Some(token) => match token
JsonToken::LBrace => results << object(),
JsonToken::LSquare => results << array(),
_ => do
match lexer.nextToken()
JsonToken::String(s) => results << s,
JsonToken::Number(n) => results << n,
JsonToken::Boolean(b) => results << b,
_ => parseError!("Expected String, Number, or Boolean")
end
end
end,
None => panic! "Expected LBrace, LSquare, String, Number, or Boolean"
end
match lexer.nextToken()
Some(token) => match token
JsonToken::Comma => continue,
JsonToken::RSquare => break
end
_ => panic! "Expected Comma or RSquare"
end
end
expect!(RSquare)
end end
end end