More json sketching.
This commit is contained in:
parent
053d08849e
commit
803b95c5dc
@ -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<obj, JsonParseError>
|
||||
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 <R> matchAt(startIndex: Int, matchers: Map<Regex, R | fn (match: String) -> R), opts: obj {
|
||||
ignoreWhitespace: boolean,
|
||||
endIndex: Int
|
||||
}) -> Option<R>
|
||||
todo!()
|
||||
platform class String
|
||||
pub fn <R> matchAt(startIndex: Int, matchers: Map<Regex, R | fn (match: String) -> R), opts: obj + { ignoreWhitespace: Boolean }) -> Option<R>
|
||||
let slice: String = if opts.ignoreWhitespace then
|
||||
/[\t\n\r ]*(<rest>.*)/.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<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)
|
||||
var index: Int
|
||||
|
||||
pub fn nextToken() -> Option<JsonToken>
|
||||
let token: Option<JsonToken> = source.matchAt(index, [
|
||||
fn matchCurrent() -> Option<JsonToken>
|
||||
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<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
|
||||
|
||||
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<Any>
|
||||
todo!()
|
||||
fn array() -> [Any]
|
||||
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
|
||||
Loading…
Reference in New Issue
Block a user