deimos-lang/sketching/april_2025/string.dm

126 lines
3.4 KiB
Plaintext

pub int Copy {
fn copy() -> Self
}
pub int Display {
fn toString() -> String
}
pub int LazyDisplay {
ref fn toLazyString() -> DString
}
decl pub class String : Copy, Display {
decl impl fn toString() -> String
decl impl fn copy() -> Self
}
pub int DString : Display {
parts: Array<DStringPart>
}
pub enum DStringPart {
Constant(String),
Display(Display),
ReadClosure(ReadClosure<Display>),
ReadRefClosure(ReadRefClosure<Display>)
}
#internal
class DStringImpl(parts) : DString {
mut fld cachedEval: Option<String>
mut fld canCache: Option<Boolean>
fld cachedReads: Map<&ReadClosure ref self, String> = HashMap()
fn calcCanCache() -> Boolean {
for part in parts {
if part is ReadRefClosure {
return false
}
}
true
}
impl Display {
fn toString() -> String {
if cachedEval is Some(s) {
return s
}
if canCache is None {
canCache = Some(calcCanCache())
}
let canCache = canCache.unwrap()
if canCache {
cachedEval = Some(parts.reduce('') { acc, part ->
acc + match part {
Constant(s) => s,
Display(d) => d,
ReadClosure(c) => c(),
ReadRefClosure(c) => throw DumbProgrammerException(
'Cannot cache when parts contains a ReadRefClosure'
)
}
})
return cachedEval.unwrap()
} else {
return parts.reduce('') { acc, part ->
acc + match part {
Constant(s) => s,
Display(d) => d,
ReadClosure(c) => {
if let Some(s) = cachedReads.get(&c) {
s
} else {
let s = c()
cachedReads.put(&c, s)
s
}
},
ReadRefClosure(c) => c()
}
}
}
}
}
}
class Foo(mut bar: Bar) {}
class Bar(mut y: Int) {}
fn stringDemo() {
let plain: String = 'Hello, World!'
let x = 42
let withDisplay: String = "Hello! x is $x".
let withReadClosure: String = "Hello! x is ${ x + 42 }".
let mut foo = Foo(Bar(16))
let withReadRefClosure: String = "Hello! y is ${ foo.bar.y }" // "Hello!, y is 16"
let lazyDString = `Lazy: y is ${ -> foo.bar.y }` // type is DString, not auto String
let eval0: String = lazyDString.toString() // Lazy: y is 16
foo.bar.y = 64
let eval1: String = lazyDString.toString() // Lazy: y is 64
}
fn badDStringClosures() {
let x = 16
let c0 = {
x // Int implements copy, so nothing consumed
} // ReadClosure
let foo = Foo(Bar(8))
let c1 = {
foo.bar.y
} // Read closure
let c2_e0 = {
foo.bar
} // Error! foo.bar cannot be moved out
let c2_e1 = {
&foo.bar
} // Error! reference to foo.bar moved out; closure must declared ref
let c2 = {
&foo.bar
} ref foo // OK, captures foo and return value is tied to lifetime of foo
drop(foo)
c2() // Error! c2 outlives foo
}