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:**
|
**Kiss aims to be:**
|
||||||
|
|
||||||
- [ ] A statically typed Lisp
|
- [x] A statically typed Lisp
|
||||||
- [ ] that runs correctly almost anywhere,
|
- [ ] 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,
|
- [ ] doesn't break downstream code when it updates,
|
||||||
- [ ] and doesn't require full-time maintenance
|
- [ ] and doesn't require full-time maintenance
|
||||||
|
|
||||||
**Main features:**
|
**Main features:**
|
||||||
|
|
||||||
- [ ] Traditional Lisp macros
|
- [ ] Traditional Lisp macros
|
||||||
- [ ] [Reader macros](https://gist.github.com/chaitanyagupta/9324402)
|
- [x] [Reader macros](https://gist.github.com/chaitanyagupta/9324402)
|
||||||
- [ ] Plug-and-play with every pure-Haxe library on Haxelib
|
- [x] 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
|
- [x] Smooth FFI with any non-Haxe library you can find or write Haxe bindings for
|
||||||
- [ ] helpful compiler errors
|
- [x] helpful compiler errors
|
||||||
|
|
||||||
**Extra goodies:**
|
**Extra goodies:**
|
||||||
|
|
||||||
- [ ] string interpolation
|
- [x] string interpolation
|
||||||
- [ ] Rust-style raw string literals
|
- [x] Rust-style raw string literals
|
||||||
- [ ] null-safe arrays
|
- [ ] null-safe arrays
|
||||||
- [x] negative indexing
|
- [x] negative indexing
|
||||||
- [ ] list comprehensions
|
- [x] list comprehensions
|
||||||
- [x] immutability by default
|
- [x] immutability by default
|
||||||
- [ ] destructuring assignment
|
- [x] destructuring assignment
|
||||||
|
|
||||||
## How does it work?
|
## How does it work?
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ class Reader {
|
|||||||
|
|
||||||
readTable["("] = (stream, k) -> CallExp(assertRead(stream, k), readExpArray(stream, ")", k));
|
readTable["("] = (stream, k) -> CallExp(assertRead(stream, k), readExpArray(stream, ")", k));
|
||||||
readTable["["] = (stream, k) -> ListExp(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['"'] = readString;
|
||||||
readTable["#"] = readRawString;
|
readTable["#"] = readRawString;
|
||||||
|
|
||||||
@@ -96,7 +100,7 @@ class Reader {
|
|||||||
return readTable;
|
return readTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final terminators = [")", "]", "/*", "\n", " "];
|
public static final terminators = [")", "]", "}", '"', "/*", "\n", " "];
|
||||||
|
|
||||||
public static function nextToken(stream:Stream, expect:String) {
|
public static function nextToken(stream:Stream, expect:String) {
|
||||||
var tok = stream.expect(expect, () -> stream.takeUntilOneOf(terminators));
|
var tok = stream.expect(expect, () -> stream.takeUntilOneOf(terminators));
|
||||||
@@ -217,19 +221,73 @@ class Reader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static function readString(stream:Stream, k:KissState) {
|
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) {
|
static function readRawString(stream:Stream, k:KissState) {
|
||||||
var terminator = '"#';
|
var terminator = '"#';
|
||||||
do {
|
do {
|
||||||
var next = stream.expect('# or "', () -> stream.peekChars(1));
|
var next = stream.expect('# or "', () -> stream.takeChars(1));
|
||||||
switch (next) {
|
switch (next) {
|
||||||
case "#":
|
case "#":
|
||||||
terminator += "#";
|
terminator += "#";
|
||||||
stream.dropChars(1);
|
|
||||||
case '"':
|
case '"':
|
||||||
stream.dropChars(1);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
error(stream, 'Invalid syntax for raw string. Delete $next');
|
error(stream, 'Invalid syntax for raw string. Delete $next');
|
||||||
@@ -239,7 +297,7 @@ class Reader {
|
|||||||
return StrExp(stream.expect('closing $terminator', () -> stream.takeUntilAndDrop(terminator)));
|
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('Kiss reader error!\n');
|
||||||
Sys.stderr().writeString(stream.position().toPrint() + ': $message\n');
|
Sys.stderr().writeString(stream.position().toPrint() + ': $message\n');
|
||||||
Sys.exit(1);
|
Sys.exit(1);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package kiss;
|
|||||||
|
|
||||||
import sys.io.File;
|
import sys.io.File;
|
||||||
import haxe.ds.Option;
|
import haxe.ds.Option;
|
||||||
|
import kiss.Reader;
|
||||||
|
|
||||||
using StringTools;
|
using StringTools;
|
||||||
using Lambda;
|
using Lambda;
|
||||||
@@ -124,7 +125,7 @@ class Stream {
|
|||||||
public function dropString(s:String) {
|
public function dropString(s:String) {
|
||||||
var toDrop = content.substr(0, s.length);
|
var toDrop = content.substr(0, s.length);
|
||||||
if (toDrop != s) {
|
if (toDrop != s) {
|
||||||
throw 'Expected $s at ${position()}';
|
Reader.error(this, 'Expected $s');
|
||||||
}
|
}
|
||||||
dropChars(s.length);
|
dropChars(s.length);
|
||||||
}
|
}
|
||||||
@@ -167,7 +168,8 @@ class Stream {
|
|||||||
case Some(s):
|
case Some(s):
|
||||||
return s;
|
return s;
|
||||||
default:
|
default:
|
||||||
throw 'Expected $whatToExpect at $position';
|
Reader.error(this, 'Expected $whatToExpect');
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -277,6 +277,10 @@ class BasicTestCase extends Test {
|
|||||||
function testRawString() {
|
function testRawString() {
|
||||||
_testRawString();
|
_testRawString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function testKissStrings() {
|
||||||
|
_testKissStrings();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class BasicObject {
|
class BasicObject {
|
||||||
|
|||||||
@@ -459,3 +459,11 @@
|
|||||||
(defun _testRawString []
|
(defun _testRawString []
|
||||||
(Assert.equals #| "\\" |# #"\"#)
|
(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