Merge remote-tracking branch 'origin/grammar-overhaul' into grammar-overhaul
This commit is contained in:
commit
f656d0d4d4
211
sketching/november_2025/json.dm
Normal file
211
sketching/november_2025/json.dm
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
ns jsonexample
|
||||||
|
|
||||||
|
fn main()
|
||||||
|
let o: obj = match parseJsonObj('{ "a": { "b": { "c": [42] }} }')
|
||||||
|
Success(o) => o,
|
||||||
|
Failure(err) => panic!("There was an error during Json parsing: ${err}")
|
||||||
|
end
|
||||||
|
assertEq!(o.a.b.c[0], 42)
|
||||||
|
end
|
||||||
|
|
||||||
|
fn parseJsonObj(s: String) -> Result<obj, JsonParseError>
|
||||||
|
let lexer = JsonLexer(s)
|
||||||
|
let parser = JsonParser(lexer)
|
||||||
|
try
|
||||||
|
parser.object()
|
||||||
|
catch (parseError: JsonParseError)
|
||||||
|
Failure(parseError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
enum JsonToken
|
||||||
|
LBrace,
|
||||||
|
RBrace,
|
||||||
|
LSquare,
|
||||||
|
RSquare,
|
||||||
|
Comma,
|
||||||
|
String(String),
|
||||||
|
Number(Number),
|
||||||
|
Boolean(Boolean)
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
fn matchCurrent() -> Option<JsonToken>
|
||||||
|
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]) }
|
||||||
|
], 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(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
|
||||||
|
|
||||||
|
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.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.peek()
|
||||||
|
Some(token) => match token
|
||||||
|
JsonToken::LBrace => object(),
|
||||||
|
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 => parseError!("Expected LBrace, LSquare, String, Number, or Boolean")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
128
sketching/november_2025/rail.dm
Normal file
128
sketching/november_2025/rail.dm
Normal file
@ -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<Train> + { printer: Printer }) -> Train =
|
||||||
|
SimpleTrain(props).with {
|
||||||
|
volume = 80
|
||||||
|
}
|
||||||
|
|
||||||
|
class SimpleTrain : Train
|
||||||
|
props!::<Train>()
|
||||||
|
printer: Printer
|
||||||
|
|
||||||
|
ctor(props: obj Props<Train> + { 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<Train>, 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<T> = obj {
|
||||||
|
[K in keyof T]: T[K]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub macro fn <T> props!(_: TokenStream) -> TokenStream
|
||||||
|
let result = TokenStream()
|
||||||
|
for [key, type] in std::intrinsics::propsAndTypes::<T>() do
|
||||||
|
result << quote! {
|
||||||
|
#key: #type
|
||||||
|
}
|
||||||
|
end
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
|
// io lib file
|
||||||
|
ns std::core
|
||||||
|
|
||||||
|
pub enum IO<S : Stream = Stream, E = Error> : fn () -> IO<S, E>
|
||||||
|
Success(stream: S)
|
||||||
|
impl ()
|
||||||
|
try
|
||||||
|
doCall(stream)
|
||||||
|
catch (e: E)
|
||||||
|
Failure(e)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
Failure(_)
|
||||||
|
impl ()
|
||||||
|
self
|
||||||
|
end
|
||||||
|
end;
|
||||||
|
|
||||||
|
int
|
||||||
|
fn <Z : Stream = S> doCall(stream: S) -> IO<Z, E>
|
||||||
|
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
|
||||||
Loading…
Reference in New Issue
Block a user