String interpolation
This commit is contained in:
20
README.md
20
README.md
@@ -10,29 +10,29 @@ Kiss is a work in progress. (See: [Who should use Kiss?](#who-should-use-kiss))
|
||||
|
||||
**Kiss aims to be:**
|
||||
|
||||
- [ ] A statically typed Lisp
|
||||
- [x] A statically typed Lisp
|
||||
- [ ] that runs correctly almost anywhere,
|
||||
- [ ] is usable at any stage of its development,
|
||||
- [x] is usable at any stage of its development,
|
||||
- [ ] doesn't break downstream code when it updates,
|
||||
- [ ] and doesn't require full-time maintenance
|
||||
|
||||
**Main features:**
|
||||
|
||||
- [ ] Traditional Lisp macros
|
||||
- [ ] [Reader macros](https://gist.github.com/chaitanyagupta/9324402)
|
||||
- [ ] Plug-and-play with every pure-Haxe library on Haxelib
|
||||
- [ ] Smooth FFI with any non-Haxe library you can find or write Haxe bindings for
|
||||
- [ ] helpful compiler errors
|
||||
- [x] [Reader macros](https://gist.github.com/chaitanyagupta/9324402)
|
||||
- [x] Plug-and-play with every pure-Haxe library on Haxelib
|
||||
- [x] Smooth FFI with any non-Haxe library you can find or write Haxe bindings for
|
||||
- [x] helpful compiler errors
|
||||
|
||||
**Extra goodies:**
|
||||
|
||||
- [ ] string interpolation
|
||||
- [ ] Rust-style raw string literals
|
||||
- [x] string interpolation
|
||||
- [x] Rust-style raw string literals
|
||||
- [ ] null-safe arrays
|
||||
- [x] negative indexing
|
||||
- [ ] list comprehensions
|
||||
- [x] list comprehensions
|
||||
- [x] immutability by default
|
||||
- [ ] destructuring assignment
|
||||
- [x] destructuring assignment
|
||||
|
||||
## How does it work?
|
||||
|
||||
|
@@ -47,6 +47,10 @@ class Reader {
|
||||
|
||||
readTable["("] = (stream, k) -> CallExp(assertRead(stream, k), readExpArray(stream, ")", k));
|
||||
readTable["["] = (stream, k) -> ListExp(readExpArray(stream, "]", k));
|
||||
// Provides a nice syntactic sugar for (if... {[then block]} {[else block]}),
|
||||
// and also handles string interpolation cases like "${}more"
|
||||
readTable["{"] = (stream:Stream, k) -> CallExp(Symbol("begin").withPos(stream.position()), readExpArray(stream, "}", k));
|
||||
|
||||
readTable['"'] = readString;
|
||||
readTable["#"] = readRawString;
|
||||
|
||||
@@ -96,7 +100,7 @@ class Reader {
|
||||
return readTable;
|
||||
}
|
||||
|
||||
public static final terminators = [")", "]", "/*", "\n", " "];
|
||||
public static final terminators = [")", "]", "}", '"', "/*", "\n", " "];
|
||||
|
||||
public static function nextToken(stream:Stream, expect:String) {
|
||||
var tok = stream.expect(expect, () -> stream.takeUntilOneOf(terminators));
|
||||
@@ -217,19 +221,73 @@ class Reader {
|
||||
}
|
||||
|
||||
static function readString(stream:Stream, k:KissState) {
|
||||
return StrExp(stream.expect("closing \"", () -> stream.takeUntilAndDrop("\"")));
|
||||
var pos = stream.position();
|
||||
var stringParts:Array<ReaderExp> = [];
|
||||
var currentStringPart = "";
|
||||
|
||||
function endCurrentStringPart() {
|
||||
stringParts.push(StrExp(currentStringPart).withPos(pos));
|
||||
currentStringPart = "";
|
||||
}
|
||||
|
||||
do {
|
||||
var next = stream.expect('closing "', () -> stream.takeChars(1));
|
||||
|
||||
switch (next) {
|
||||
case '$':
|
||||
endCurrentStringPart();
|
||||
var wrapInIf = false;
|
||||
var firstAfterDollar = stream.expect('interpolation expression', () -> stream.peekChars(1));
|
||||
if (firstAfterDollar == "?") {
|
||||
wrapInIf = true;
|
||||
stream.dropChars(1);
|
||||
}
|
||||
var interpExpression = assertRead(stream, k);
|
||||
interpExpression = CallExp(Symbol("Std.string").withPos(pos), [interpExpression]).withPos(pos);
|
||||
if (wrapInIf) {
|
||||
interpExpression = CallExp(Symbol("if").withPos(pos), [interpExpression, interpExpression, StrExp("").withPos(pos)]).withPos(pos);
|
||||
}
|
||||
stringParts.push(interpExpression);
|
||||
case '\\':
|
||||
var escapeSequence = stream.expect('valid escape sequence', () -> stream.takeChars(1));
|
||||
switch (escapeSequence) {
|
||||
case '\\':
|
||||
currentStringPart += "\\";
|
||||
case 't':
|
||||
currentStringPart += "\t";
|
||||
case 'n':
|
||||
currentStringPart += "\n";
|
||||
case 'r':
|
||||
currentStringPart += "\r";
|
||||
case '"':
|
||||
currentStringPart += '"';
|
||||
case '$':
|
||||
currentStringPart += '$';
|
||||
default:
|
||||
error(stream, 'unsupported escape sequence \\$escapeSequence');
|
||||
return null;
|
||||
}
|
||||
case '"':
|
||||
endCurrentStringPart();
|
||||
return if (stringParts.length == 1) {
|
||||
stringParts[0].def;
|
||||
} else {
|
||||
CallExp(Symbol("+").withPos(pos), stringParts);
|
||||
};
|
||||
default:
|
||||
currentStringPart += next;
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
|
||||
static function readRawString(stream:Stream, k:KissState) {
|
||||
var terminator = '"#';
|
||||
do {
|
||||
var next = stream.expect('# or "', () -> stream.peekChars(1));
|
||||
var next = stream.expect('# or "', () -> stream.takeChars(1));
|
||||
switch (next) {
|
||||
case "#":
|
||||
terminator += "#";
|
||||
stream.dropChars(1);
|
||||
case '"':
|
||||
stream.dropChars(1);
|
||||
break;
|
||||
default:
|
||||
error(stream, 'Invalid syntax for raw string. Delete $next');
|
||||
@@ -239,7 +297,7 @@ class Reader {
|
||||
return StrExp(stream.expect('closing $terminator', () -> stream.takeUntilAndDrop(terminator)));
|
||||
}
|
||||
|
||||
static function error(stream:Stream, message:String) {
|
||||
public static function error(stream:Stream, message:String) {
|
||||
Sys.stderr().writeString('Kiss reader error!\n');
|
||||
Sys.stderr().writeString(stream.position().toPrint() + ': $message\n');
|
||||
Sys.exit(1);
|
||||
|
@@ -2,6 +2,7 @@ package kiss;
|
||||
|
||||
import sys.io.File;
|
||||
import haxe.ds.Option;
|
||||
import kiss.Reader;
|
||||
|
||||
using StringTools;
|
||||
using Lambda;
|
||||
@@ -124,7 +125,7 @@ class Stream {
|
||||
public function dropString(s:String) {
|
||||
var toDrop = content.substr(0, s.length);
|
||||
if (toDrop != s) {
|
||||
throw 'Expected $s at ${position()}';
|
||||
Reader.error(this, 'Expected $s');
|
||||
}
|
||||
dropChars(s.length);
|
||||
}
|
||||
@@ -167,7 +168,8 @@ class Stream {
|
||||
case Some(s):
|
||||
return s;
|
||||
default:
|
||||
throw 'Expected $whatToExpect at $position';
|
||||
Reader.error(this, 'Expected $whatToExpect');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -277,6 +277,10 @@ class BasicTestCase extends Test {
|
||||
function testRawString() {
|
||||
_testRawString();
|
||||
}
|
||||
|
||||
function testKissStrings() {
|
||||
_testKissStrings();
|
||||
}
|
||||
}
|
||||
|
||||
class BasicObject {
|
||||
|
@@ -458,4 +458,12 @@
|
||||
|
||||
(defun _testRawString []
|
||||
(Assert.equals #| "\\" |# #"\"#)
|
||||
(Assert.equals #| "\"#" |# ##""#"##))
|
||||
(Assert.equals #| "\"#" |# ##""#"##))
|
||||
|
||||
(defun _testKissStrings []
|
||||
(Assert.equals #| "\\\t\r\n\"$" |# "\\\t\r\n\"\$")
|
||||
(let [str "it's"
|
||||
num 3
|
||||
l1 ["a" "b" "c"]
|
||||
l2 [1 2 3]]
|
||||
(Assert.equals "it's 3asy as [a,b,c] [1,2,3]" "$str ${num}asy as $l1 $l2")))
|
Reference in New Issue
Block a user