126 lines
3.4 KiB
Plaintext
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
|
|
}
|