Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion effekt/jvm/src/test/scala/effekt/LSPTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -811,7 +811,7 @@ class LSPTests extends FunSuite {
raw"""effect raise(): Unit
|
|↑
|def foo() = { do raise(); 5 }
|def boo() = { do raise(); 5 }
| ↑
|
|↑
Expand Down
4 changes: 2 additions & 2 deletions effekt/shared/src/main/scala/effekt/Parser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1350,7 +1350,7 @@ class Parser(tokens: Seq[Token], source: Source) {
backtrack(idRef()) ~ template() match {
// We do not need to apply any transformation if there are no splices _and_ no custom handler id is given
case Maybe(None, _) ~ SpannedTemplate(str :: Nil, Nil) => StringLit(str.unspan, str.span)
// s"a${x}b${y}" ~> s { do literal("a"); do splice(x); do literal("b"); do splice(y); return () }
// s"a${x}b${y}" ~> s { do write("a"); do splice(x); do write("b"); do splice(y); return () }
case Maybe(id, range) ~ SpannedTemplate(strs, args) =>
val target = id match {
case Some(id) => id
Expand All @@ -1362,7 +1362,7 @@ class Parser(tokens: Seq[Token], source: Source) {
}
val doLits = strs.map { s =>
Do(
IdRef(Nil, "literal", s.span.synthesized),
IdRef(Nil, "write", s.span.synthesized),
Nil,
List(ValueArg.Unnamed(StringLit(s.unspan, s.span))),
Nil,
Expand Down
17 changes: 6 additions & 11 deletions examples/benchmarks/other/nbe.effekt
Original file line number Diff line number Diff line change
Expand Up @@ -94,19 +94,14 @@ def parse(): Term / { read[Bool], stop } = {
}

/// helper function for pretty string interpolation of terms
def pretty { s: () => Unit / { literal, splice[Term] } }: String = {
def pretty { stream: () => Unit / { write, splice[Term] } }: String = {
with stringBuffer

try { s(); do flush() }
with literal { l => resume(do write(l)) }
with splice[Term] { t =>
t match {
case Abs(n, t) => do write("λ" ++ n.show ++ pretty".${t}")
case App(a, b) => do write(pretty"(${a} ${b})")
case Var(v) => do write(v.show)
}
resume(())
with splicing[Term] {
case Abs(n, t) => do write("λ" ++ n.show ++ pretty".${t}")
case App(a, b) => do write(pretty"(${a} ${b})")
case Var(v) => do write(v.show)
}
stream()
}

/// convert char stream to bit stream, skipping non-bit chars
Expand Down
33 changes: 13 additions & 20 deletions examples/pos/string_interpolation.effekt
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,26 @@ type Expr {
App(fn: Expr, arg: Expr)
}

def pretty { prog: () => Unit / {literal, splice[Expr]} }: String = {
def pretty { prog: () => Unit / {write, splice[Expr]} }: String = {
with stringBuffer
try {
prog()
do flush()
} with literal { s =>
resume(do write(s))
} with splice[Expr] { expr =>
expr match {
case Var(id) =>
do write(id)
case App(Abs(param, body), arg) =>
do write(pretty"(${Abs(param, body)}) ${arg}")
case App(fn, arg) =>
do write(pretty"${fn} ${arg}")
case Abs(param, body) =>
do write(s"\\ ${param} -> " ++ pretty"${body}")
}
resume(())
with splicing[Expr] {
case Var(id) =>
do write(id)
case App(Abs(param, body), arg) =>
do write(pretty"(${Abs(param, body)}) ${arg}")
case App(fn, arg) =>
do write(pretty"${fn} ${arg}")
case Abs(param, body) =>
do write(s"\\ ${param} -> " ++ pretty"${body}")
}
prog()
}

def len { prog: () => Unit / {literal} }: Int = {
def len { prog: () => Unit / write }: Int = {
try {
prog()
0
} with literal { s =>
} with write { s =>
s.length
}
}
Expand Down
12 changes: 6 additions & 6 deletions examples/pos/string_interpolation_literal.effekt
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
effect log(msg: String): Unit

def error { body: => Unit / literal }: Unit / log =
try body() with literal { x => do log("[ERROR] " ++ x); resume(()) }
def error { body: => Unit / write }: Unit / log =
try body() with write { x => do log("[ERROR] " ++ x); resume(()) }

def warn { body: => Unit / literal }: Unit / log =
try body() with literal { x => do log("[WARNING] " ++ x); resume(()) }
def warn { body: => Unit / write }: Unit / log =
try body() with write { x => do log("[WARNING] " ++ x); resume(()) }

def doc { body: => Unit / {literal} }: Unit =
try body() with literal { _ => resume(()) }
def doc { body: => Unit / write }: Unit =
try body() with write { _ => resume(()) }

def main() = try {
doc"This is the doc string for my main function!"
Expand Down
7 changes: 4 additions & 3 deletions libraries/common/effekt.effekt
Original file line number Diff line number Diff line change
Expand Up @@ -790,17 +790,18 @@ def locally[R] { p: => R } : R = p()
//
// "foo ${42} bar ${43}"
//
// to the following stream of `literal`s and `splice[Int]`s:
// to the following stream of `write`s and `splice[Int]`s:
//
// do literal("foo "); do splice(42); do literal(" bar "); do splice(43)
// do write("foo "); do splice(42); do write(" bar "); do splice(43)
//
// The stream is wrapped into a handler function, which defaults to `stringbuffer::s`
//
// s { ... }
//
// but can be specified by prefixing the string interpolation `custom"..."`.

effect literal(s: String): Unit
effect write(s: String): Unit

effect splice[A](x: A): Unit


62 changes: 30 additions & 32 deletions libraries/common/stringbuffer.effekt
Original file line number Diff line number Diff line change
Expand Up @@ -3,60 +3,58 @@ module stringbuffer
import effekt
import bytearray

interface StringBuffer {
def write(str: String): Unit
def flush(): String
}
effect flush(): String

def stringBuffer { program: () => Unit / write }: String =
flushableStringBuffer { program(); do flush() }

def stringBuffer[A] { prog: => A / StringBuffer }: A = {
def flushableStringBuffer[A] { program: () => A / { write, flush } }: A = {
val initialCapacity = 128
var buffer = bytearray::allocate(initialCapacity)
// next free index to write to
var pos = 0
var position = 0

def ensureCapacity(sizeToAdd: Int): Unit = {
val cap = buffer.size - pos
if (sizeToAdd <= cap) ()
def ensureCapacity(required: Int): Unit = {
val capacity = buffer.size - position
if (required <= capacity) ()
else {
// Double the capacity while ensuring the required capacity
val newSize = max(buffer.size * 2, buffer.size + sizeToAdd)
val newSize = max(buffer.size * 2, buffer.size + required)
buffer = buffer.resize(newSize)
}
}

try { prog() }
with StringBuffer {
def write(str) = {
val bytes = fromString(str)
ensureCapacity(bytes.size)
bytes.foreach { b =>
buffer.set(pos, b)
pos = pos + 1
}
resume(())
}
def flush() = {
// resize (& copy) buffer to strip trailing zeros that otherwise would be converted into 0x00 characters
val str = bytearray::resize(buffer, pos).toString()
// NOTE: Keep the `buffer` as-is (no wipe, no realloc),
// just reset the `pos` in case we want to use it again.
pos = 0
resume(str)
try {
program()
} with write { string =>
val bytes = fromString(string)
ensureCapacity(bytes.size)
bytes.foreach { b =>
buffer.set(position, b)
position = position + 1
}
resume(())
} with flush {
val string = buffer.resize(position).toString()
position = 0
resume(string)
}
}

def splicing[T] { writer: T => Unit } { program: () => Unit / splice[T] }: Unit =
try { program() } with splice[T] { t => resume(writer(t)) }


/// Handler for string interpolation using a string buffer
def s { prog: () => Unit / { literal, splice[String] } }: String =
def s { program: () => Unit / { write, splice[String] } }: String =
stringBuffer {
try { prog(); do flush() }
try { program() }
with splice[String] { x => resume(do write(x)) }
with literal { s => resume(do write(s)) }
}

namespace examples {
def main() = {
with stringBuffer
with flushableStringBuffer

do write("hello")
do write(", world")
Expand Down
Loading