-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add basic string interpolation / string templates (#743)
Fixes #722 and adds basic support for string templates. There are a few caveats to keep in mind with the current initial implementation: - All arguments already have to be strings. It is basically just syntactic sugar so that you can write `"${a.show} and ${b.show}"` instead of `a.show ++ " and " ++ b.show` where `a` and `b` are some 'showable' values. Internally, however, this is just how it is desugared. - String templates cannot be used to match string literals in pattern matches - The generated JS code looks a bit weird: ``` const v_r_897 = $effekt.println(((((((((("GET ") + (domain_0)))) + (((("/users/") + (user_0))))))) + (((("/resource/") + (('' + resourceId_0)))))))); ``` + If one forgets to call `show` on arguments which are not already of type string, the error messages are hideous and possibly confusing: <details> <summary>error</summary> ``` -There are multiple overloads, which all fail to check: -Possible overload: effekt::println of type Bool => Unit - Expected String but got Int. - Expected Bool but got String. - -Possible overload: list::println of type List[Bool] => Unit - Expected String but got Int. - Expected List[Bool] but got String. - -Possible overload: array::println of type Array[Int] => Unit - Expected String but got Int. - Expected Array[Int] but got String. - -Possible overload: effekt::println of type String => Unit - Expected String but got Int. - -Possible overload: list::println of type List[Int] => Unit - Expected String but got Int. - Expected List[Int] but got String. - -Possible overload: effekt::println of type Byte => Unit - Expected String but got Int. - Expected Byte but got String. - -Possible overload: list::println of type List[String] => Unit - Expected String but got Int. - Expected List[String] but got String. - -Possible overload: list::println of type List[Double] => Unit - Expected String but got Int. - Expected List[Double] but got String. - -Possible overload: array::println of type Array[String] => Unit - Expected String but got Int. - Expected Array[String] but got String. - -Possible overload: effekt::println of type Ordering => Unit - Expected String but got Int. - Expected Ordering but got String. - -Possible overload: array::println of type Array[Bool] => Unit - Expected String but got Int. - Expected Array[Bool] but got String. - -Possible overload: effekt::println of type Unit => Unit - Expected String but got Int. - Expected Unit but got String. - -Possible overload: option::println of type Option[Int] => Unit - Expected String but got Int. - Expected Option[Int] but got String. - -Possible overload: option::println of type Option[Double] => Unit - Expected String but got Int. - Expected Option[Double] but got String. - -Possible overload: array::println of type Array[Double] => Unit - Expected String but got Int. - Expected Array[Double] but got String. - -Possible overload: effekt::println of type Double => Unit - Expected String but got Int. - Expected Double but got String. - -Possible overload: effekt::println of type Int => Unit - Expected String but got Int. - Expected Int but got String. - -Possible overload: option::println of type Option[Bool] => Unit - Expected String but got Int. - Expected Option[Bool] but got String. - - println("GET ${domain}/users/${user}/resource/${resourceId}") ``` </details> Apart from that, the changes are really lightweight because of the early desugaring in the parser. I am looking forward to your feedback and ideas. --------- Co-authored-by: Jiří Beneš <mail@jiribenes.com> Co-authored-by: Marvin <git@marvinborner.de> Co-authored-by: Philipp Schuster <philipp.schuster@uni-tuebingen.de> Co-authored-by: Jonathan Brachthäuser <jonathan@b-studios.de>
- Loading branch information
1 parent
f92d229
commit 6b2b191
Showing
10 changed files
with
180 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
GET https://api.effekt-lang.org/users/effekt/resource/42 | ||
Fix point combinator: \ f -> (\ x -> f x x) \ x -> f x x |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import stringbuffer | ||
|
||
type Expr { | ||
Var(id: String) | ||
Abs(param: String, body: Expr) | ||
App(fn: Expr, arg: Expr) | ||
} | ||
|
||
def pretty { prog: () => Unit / {literal, 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(()) | ||
} | ||
} | ||
|
||
def main() = { | ||
val domain = "https://api.effekt-lang.org" | ||
val user = "effekt" | ||
val resourceId = 42 | ||
println("GET ${domain}/users/${user}/resource/${resourceId.show}") | ||
|
||
val fixpoint = Abs("f", App(Abs("x", App(Var("f"), App(Var("x"), Var("x")))), Abs("x", App(Var("f"), App(Var("x"), Var("x")))))) | ||
println(pretty"Fix point combinator: ${fixpoint}") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
import map | ||
|
||
import bytearray | ||
|
||
def counter(words: List[String]): Map[String, Int] = { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
import set | ||
|
||
import bytearray | ||
|
||
def unique(words: List[String]): Set[String] = { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
module stringbuffer | ||
|
||
import bytearray | ||
|
||
interface StringBuffer { | ||
def write(str: String): Unit | ||
def flush(): String | ||
} | ||
|
||
def stringBuffer[A] { prog: => A / StringBuffer }: A = { | ||
val initialCapacity = 128 | ||
var buffer = bytearray::allocate(initialCapacity) | ||
// next free index to write to | ||
var pos = 0 | ||
|
||
def ensureCapacity(sizeToAdd: Int): Unit = { | ||
val cap = buffer.size - pos + 1 | ||
if (sizeToAdd <= cap) () | ||
else { | ||
// Double the capacity while ensuring the required capacity | ||
val newSize = max(buffer.size * 2, buffer.size + sizeToAdd) | ||
buffer = buffer.resize(newSize) | ||
} | ||
} | ||
|
||
try { prog() } | ||
with StringBuffer { | ||
def write(str) = { | ||
val bytes = fromString(str) | ||
ensureCapacity(bytes.size) | ||
bytes.foreach { b => | ||
buffer.unsafeSet(pos, b) | ||
pos = pos + 1 | ||
} | ||
resume(()) | ||
} | ||
def flush() = { | ||
// resize buffer to strip trailing zeros that otherwise would be converted into 0x00 characters | ||
val str = bytearray::resize(buffer, pos).toString() | ||
// after flushing, the stringbuffer should be empty again | ||
buffer = bytearray::allocate(initialCapacity) | ||
resume(str) | ||
} | ||
} | ||
} | ||
|
||
/// Handler for string interpolation using a string buffer | ||
def s { prog: () => Unit / { literal, splice[String] } }: String = | ||
stringBuffer { | ||
try { prog(); do flush() } | ||
with splice[String] { x => resume(do write(x)) } | ||
with literal { s => resume(do write(s)) } | ||
} | ||
|
||
namespace examples { | ||
def main() = { | ||
with stringBuffer | ||
do write("hello") | ||
do write(", world") | ||
// prints `hello, world` | ||
println(do flush()) | ||
// prints the empty string | ||
println(do flush()) | ||
} | ||
} |