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