From 053d08849ef3ee7ceb4f3f28a47fb66bf8d2e8bb Mon Sep 17 00:00:00 2001 From: Jesse Brault Date: Mon, 10 Nov 2025 16:32:19 -0600 Subject: [PATCH 1/2] Sketching out rail and json. --- sketching/november_2025/json.dm | 114 ++++++++++++++++++++++++++++ sketching/november_2025/rail.dm | 128 ++++++++++++++++++++++++++++++++ 2 files changed, 242 insertions(+) create mode 100644 sketching/november_2025/json.dm create mode 100644 sketching/november_2025/rail.dm diff --git a/sketching/november_2025/json.dm b/sketching/november_2025/json.dm new file mode 100644 index 0000000..17e4b0c --- /dev/null +++ b/sketching/november_2025/json.dm @@ -0,0 +1,114 @@ +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) + Success(o) => o, + Failure(err) => exit(err) + end + assertEq!(o.a.b.c[0], 42) +end + +fn parseJsonObj(s: String) -> obj + let lexer = JsonLexer(s) + let parser = JsonParser(lexer) + parser.object() +end + +enum JsonToken + LBrace, + RBrace, + LSquare, + RSquare, + Comma, + String(String), + Number(Number), + Boolean(Boolean) +end + +class String + pub fn matchAt(startIndex: Int, matchers: Map R), opts: obj { + ignoreWhitespace: boolean, + endIndex: Int + }) -> Option + todo!() + end +end + +class JsonLexer(val source: String) + var index: Int + + pub fn nextToken() -> Option + let token: Option = source.matchAt(index, [ + /\{/ => JsonToken::LBrace, + /}/ => JsonToken::RBrace, + /\[/ => JsonToken::LSquare, + /]/ => JsonToken::RSquare, + /[0-9]+/ => { rawNumber: String => JsonToken::Number(number::parse(rawNumber))) } + /true/ => JsonToken::Boolean(true), + /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 + end +end + +class JsonParser(val lexer: JsonLexer) + + macro expect!($tokenType) + match lexer.nextToken() + Some(JsonToken::$tokenType) => do end, + _ => panic! "Expected ${$tokenType}" + end + end + + pub fn object() -> obj + expect!(LBrace) + let result = obj {} + while keyValue() is Some((key, value)) do + result[key] = value + end + expect!(RBrace) + result + end + + fn keyValue() -> Option<(String, Any)> + let key = match lexer.nextToken() + Some(JsonToken::String(s)) => s, + _ => return None + end + let value = value() + Some((key, value)) + end + + fn value() -> Any + match lexer.nextToken() + Some(token) => match token + JsonToken::String(s) => s, + JsonToken::Number(n) => n, + JsonToken::Boolean(b) => b, + JsonToken::LBrace => object(), + JsonToken::LSquare => array() + end, + None => panic! "Expected LBrace, LSquare, String, Number, or Boolean" + end + end + + fn array() -> Array + todo!() + end + +end \ No newline at end of file diff --git a/sketching/november_2025/rail.dm b/sketching/november_2025/rail.dm new file mode 100644 index 0000000..11dbdf1 --- /dev/null +++ b/sketching/november_2025/rail.dm @@ -0,0 +1,128 @@ +ns nr::train + +use object::{assign, Props, props!} + +pub int Train + name: String + primaryColor: Color + + fld destination: String + fld volume: Int + + fn blowHorn() -> IO +end + +pub enum Color + White, + Blue, + Red +end + +pub type Printer = fn (message: String) -> IO + +pub fn create(props: obj Props + { printer: Printer }) -> Train = + SimpleTrain(props).with { + volume = 80 + } + +class SimpleTrain : Train + props!::() + printer: Printer + + ctor(props: obj Props + { printer: Printer }) + assign(self, props) + end + + impl fn blowHorn() -> IO + printer("Train ${name} is blowing its horn at volume ${volume}!") + end +end + +pub fn createViaObj(props: obj Props, printer: Printer) -> obj Train + obj { + ...props, + printer, + blowHorn: fn () -> IO + printer("Dynamic object Train ${train} is blowing its horn at volume ${self.volume}!") + end + } +end + +// props lib file +ns std::core::object + +pub fn assign(target: obj, source: obj) + for [key, value] source.props() do + target[key] = value + end +end + +pub type Props = obj { + [K in keyof T]: T[K] +} + +pub macro fn props!(_: TokenStream) -> TokenStream + let result = TokenStream() + for [key, type] in std::intrinsics::propsAndTypes::() do + result << quote! { + #key: #type + } + end + result +end + +// io lib file +ns std::core + +pub enum IO : fn () -> IO + Success(stream: S) + impl () + try + doCall(stream) + catch (e: E) + Failure(e) + end + end + end, + Failure(_) + impl () + self + end + end; + + int + fn doCall(stream: S) -> IO + end +end + +// main example file +ns nr::example + +use nr::train::{create, createViaObj, Train, Color} + +fn main() + let train = create(obj { + name: 'Test Train', + primaryColor: Color::Blue, + destination: 'Nonavosa Central', + printer: println + }) + train.blowHorn() + train.volume = 100 + train.blowHorn() + + let objTrain: obj Train = createViaObj(obj { + name: 'Obj Train', + primaryColor: Color::Red, + destination: 'Durandana', + printer: println + }) + // Since it's an obj, we can dynamically add properties and override methods, similar to JS + objTrain.secondaryColor = Color::White + let originalBlowHorn = objTrain.blowHorn + objTrain.blowHorn = fn () + originalBlowHorn() + println "Hello again! The train's colors are ${self.primaryColor} and ${self.secondaryColor}!" + end + objTrain.blowHorn() +end \ No newline at end of file From 803b95c5dcaa780838bf7c094d355d9e95ea7a53 Mon Sep 17 00:00:00 2001 From: Jesse Brault Date: Mon, 10 Nov 2025 22:08:09 -0600 Subject: [PATCH 2/2] More json sketching. --- sketching/november_2025/json.dm | 177 ++++++++++++++++++++++++-------- 1 file changed, 137 insertions(+), 40 deletions(-) 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