Start of line reader macros
This commit is contained in:
@@ -60,7 +60,7 @@ class EmbeddedScript {
|
|||||||
classFields.push(field);
|
classFields.push(field);
|
||||||
}
|
}
|
||||||
|
|
||||||
Reader.readAndProcess(new Stream(scriptFile), k.readTable, (nextExp) -> {
|
Reader.readAndProcess(new Stream(scriptFile), k, (nextExp) -> {
|
||||||
var field = Kiss.readerExpToField(nextExp, k, false);
|
var field = Kiss.readerExpToField(nextExp, k, false);
|
||||||
if (field != null) {
|
if (field != null) {
|
||||||
classFields.push(field);
|
classFields.push(field);
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ class Helpers {
|
|||||||
public static function defAlias(k:KissState, whenItsThis:String, makeItThisInstead:ReaderExpDef) {
|
public static function defAlias(k:KissState, whenItsThis:String, makeItThisInstead:ReaderExpDef) {
|
||||||
// The alias has to be followed by a terminator to count!
|
// The alias has to be followed by a terminator to count!
|
||||||
for (terminator in Reader.terminators) {
|
for (terminator in Reader.terminators) {
|
||||||
k.readTable[whenItsThis + terminator] = (s:Stream) -> {
|
k.readTable[whenItsThis + terminator] = (s:Stream, k) -> {
|
||||||
s.putBackString(terminator);
|
s.putBackString(terminator);
|
||||||
makeItThisInstead;
|
makeItThisInstead;
|
||||||
}
|
}
|
||||||
@@ -207,8 +207,8 @@ class Helpers {
|
|||||||
var parser = new Parser();
|
var parser = new Parser();
|
||||||
if (interps.length == 0) {
|
if (interps.length == 0) {
|
||||||
var interp = new Interp();
|
var interp = new Interp();
|
||||||
interp.variables.set("read", Reader.assertRead.bind(_, k.readTable));
|
interp.variables.set("read", Reader.assertRead.bind(_, k));
|
||||||
interp.variables.set("readExpArray", Reader.readExpArray.bind(_, _, k.readTable));
|
interp.variables.set("readExpArray", Reader.readExpArray.bind(_, _, k));
|
||||||
interp.variables.set("ReaderExp", ReaderExpDef);
|
interp.variables.set("ReaderExp", ReaderExpDef);
|
||||||
interp.variables.set("nextToken", Reader.nextToken.bind(_, "a token"));
|
interp.variables.set("nextToken", Reader.nextToken.bind(_, "a token"));
|
||||||
interp.variables.set("kiss", {
|
interp.variables.set("kiss", {
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ typedef ExprConversion = (ReaderExp) -> Expr;
|
|||||||
|
|
||||||
typedef KissState = {
|
typedef KissState = {
|
||||||
className:String,
|
className:String,
|
||||||
readTable:Map<String, ReadFunction>,
|
readTable:ReadTable,
|
||||||
|
startOfLineReadTable:ReadTable,
|
||||||
fieldForms:Map<String, FieldFormFunction>,
|
fieldForms:Map<String, FieldFormFunction>,
|
||||||
specialForms:Map<String, SpecialFormFunction>,
|
specialForms:Map<String, SpecialFormFunction>,
|
||||||
macros:Map<String, MacroFunction>,
|
macros:Map<String, MacroFunction>,
|
||||||
@@ -35,6 +36,7 @@ class Kiss {
|
|||||||
var k = {
|
var k = {
|
||||||
className: className,
|
className: className,
|
||||||
readTable: Reader.builtins(),
|
readTable: Reader.builtins(),
|
||||||
|
startOfLineReadTable: new ReadTable(),
|
||||||
fieldForms: FieldForms.builtins(),
|
fieldForms: FieldForms.builtins(),
|
||||||
specialForms: SpecialForms.builtins(),
|
specialForms: SpecialForms.builtins(),
|
||||||
macros: Macros.builtins(),
|
macros: Macros.builtins(),
|
||||||
@@ -70,7 +72,7 @@ class Kiss {
|
|||||||
if (k == null)
|
if (k == null)
|
||||||
k = defaultKissState();
|
k = defaultKissState();
|
||||||
|
|
||||||
Reader.readAndProcess(stream, k.readTable, (nextExp) -> {
|
Reader.readAndProcess(stream, k, (nextExp) -> {
|
||||||
#if test
|
#if test
|
||||||
Sys.println(nextExp.def.toString());
|
Sys.println(nextExp.def.toString());
|
||||||
#end
|
#end
|
||||||
|
|||||||
@@ -178,12 +178,8 @@ class Macros {
|
|||||||
]).withPosOf(wholeExp);
|
]).withPosOf(wholeExp);
|
||||||
};
|
};
|
||||||
|
|
||||||
macros["defreadermacro"] = (wholeExp:ReaderExp, exps:Array<ReaderExp>, k:KissState) -> {
|
function stringsThatMatch(exp:ReaderExp) {
|
||||||
wholeExp.checkNumArgs(3, null, '(defreadermacro ["[startingString]" or [startingStrings...]] [[streamArgName]] [body...])');
|
return switch (exp.def) {
|
||||||
|
|
||||||
// reader macros can define a list of strings that will trigger the macro. When there are multiple,
|
|
||||||
// the macro will put back the initiating string into the stream so you can check which one it was
|
|
||||||
var stringsThatMatch = switch (exps[0].def) {
|
|
||||||
case StrExp(s):
|
case StrExp(s):
|
||||||
[s];
|
[s];
|
||||||
case ListExp(strings):
|
case ListExp(strings):
|
||||||
@@ -197,14 +193,31 @@ class Macros {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
default:
|
default:
|
||||||
throw CompileError.fromExp(exps[0], 'first argument to defreadermacro should be a String or list of strings');
|
throw CompileError.fromExp(exp, 'first argument to defreadermacro should be a String or list of strings');
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
for (s in stringsThatMatch) {
|
macros["defreadermacro"] = (wholeExp:ReaderExp, exps:Array<ReaderExp>, k:KissState) -> {
|
||||||
|
wholeExp.checkNumArgs(3, null, '(defreadermacro ["[startingString]" or [startingStrings...]] [[streamArgName]] [body...])');
|
||||||
|
|
||||||
|
// reader macros declared in the form (defreadermacro &start ...) will only be applied
|
||||||
|
// at the beginning of lines
|
||||||
|
var table = k.readTable;
|
||||||
|
|
||||||
|
// reader macros can define a list of strings that will trigger the macro. When there are multiple,
|
||||||
|
// the macro will put back the initiating string into the stream so you can check which one it was
|
||||||
|
var strings = switch (exps[0].def) {
|
||||||
|
case MetaExp("start", stringsExp):
|
||||||
|
table = k.startOfLineReadTable;
|
||||||
|
stringsThatMatch(stringsExp);
|
||||||
|
default:
|
||||||
|
stringsThatMatch(exps[0]);
|
||||||
|
};
|
||||||
|
for (s in strings) {
|
||||||
switch (exps[1].def) {
|
switch (exps[1].def) {
|
||||||
case ListExp([{pos: _, def: Symbol(streamArgName)}]):
|
case ListExp([{pos: _, def: Symbol(streamArgName)}]):
|
||||||
k.readTable[s] = (stream) -> {
|
table[s] = (stream, k) -> {
|
||||||
if (stringsThatMatch.length > 1) {
|
if (strings.length > 1) {
|
||||||
stream.putBackString(s);
|
stream.putBackString(s);
|
||||||
}
|
}
|
||||||
var body = CallExp(Symbol("begin").withPos(stream.position()), exps.slice(2)).withPos(stream.position());
|
var body = CallExp(Symbol("begin").withPos(stream.position()), exps.slice(2)).withPos(stream.position());
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package kiss;
|
|||||||
|
|
||||||
import haxe.ds.Option;
|
import haxe.ds.Option;
|
||||||
import kiss.Stream;
|
import kiss.Stream;
|
||||||
|
import kiss.Kiss;
|
||||||
|
|
||||||
using kiss.Reader;
|
using kiss.Reader;
|
||||||
|
|
||||||
@@ -35,58 +36,58 @@ enum ReaderExpDef {
|
|||||||
UnquoteList(exp:ReaderExp); // ,@[exp]
|
UnquoteList(exp:ReaderExp); // ,@[exp]
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef ReadFunction = (Stream) -> Null<ReaderExpDef>;
|
typedef ReadFunction = (Stream, KissState) -> Null<ReaderExpDef>;
|
||||||
|
typedef ReadTable = Map<String, ReadFunction>;
|
||||||
|
|
||||||
class Reader {
|
class Reader {
|
||||||
// The built-in readtable
|
// The built-in readtable
|
||||||
public static function builtins() {
|
public static function builtins() {
|
||||||
var readTable:Map<String, ReadFunction> = [];
|
var readTable:ReadTable = [];
|
||||||
|
|
||||||
readTable["("] = (stream) -> CallExp(assertRead(stream, readTable), readExpArray(stream, ")", readTable));
|
readTable["("] = (stream, k) -> CallExp(assertRead(stream, k), readExpArray(stream, ")", k));
|
||||||
readTable["["] = (stream) -> ListExp(readExpArray(stream, "]", readTable));
|
readTable["["] = (stream, k) -> ListExp(readExpArray(stream, "]", k));
|
||||||
readTable["\""] = (stream) -> StrExp(stream.expect("closing \"", () -> stream.takeUntilAndDrop("\"")));
|
readTable["\""] = (stream, k) -> StrExp(stream.expect("closing \"", () -> stream.takeUntilAndDrop("\"")));
|
||||||
readTable["/*"] = (stream) -> {
|
readTable["/*"] = (stream, k) -> {
|
||||||
stream.dropUntil("*/");
|
stream.takeUntilAndDrop("*/");
|
||||||
stream.dropString("*/");
|
|
||||||
null;
|
null;
|
||||||
};
|
};
|
||||||
readTable["//"] = (stream) -> {
|
readTable["//"] = (stream, k) -> {
|
||||||
stream.dropUntil("\n");
|
stream.takeUntilAndDrop("\n");
|
||||||
null;
|
null;
|
||||||
};
|
};
|
||||||
readTable["#|"] = (stream) -> RawHaxe(stream.expect("closing |#", () -> stream.takeUntilAndDrop("|#")));
|
readTable["#|"] = (stream, k) -> RawHaxe(stream.expect("closing |#", () -> stream.takeUntilAndDrop("|#")));
|
||||||
// For defmacrofuns, unquoting with , is syntactic sugar for calling a Quote (Void->T)
|
// For defmacrofuns, unquoting with , is syntactic sugar for calling a Quote (Void->T)
|
||||||
|
|
||||||
readTable[":"] = (stream) -> TypedExp(nextToken(stream, "a type path"), assertRead(stream, readTable));
|
readTable[":"] = (stream, k) -> TypedExp(nextToken(stream, "a type path"), assertRead(stream, k));
|
||||||
|
|
||||||
readTable["&"] = (stream) -> MetaExp(nextToken(stream, "a meta symbol like mut, optional, rest"), assertRead(stream, readTable));
|
readTable["&"] = (stream, k) -> MetaExp(nextToken(stream, "a meta symbol like mut, optional, rest"), assertRead(stream, k));
|
||||||
|
|
||||||
readTable["!"] = (stream:Stream) -> CallExp(Symbol("not").withPos(stream.position()), [assertRead(stream, readTable)]);
|
readTable["!"] = (stream:Stream, k) -> CallExp(Symbol("not").withPos(stream.position()), [assertRead(stream, k)]);
|
||||||
|
|
||||||
// Helpful for defining predicates to pass to Haxe functions:
|
// Helpful for defining predicates to pass to Haxe functions:
|
||||||
readTable["?"] = (stream:Stream) -> CallExp(Symbol("Prelude.truthy").withPos(stream.position()), [assertRead(stream, readTable)]);
|
readTable["?"] = (stream:Stream, k) -> CallExp(Symbol("Prelude.truthy").withPos(stream.position()), [assertRead(stream, k)]);
|
||||||
|
|
||||||
// Lets you dot-access a function result without binding it to a name
|
// Lets you dot-access a function result without binding it to a name
|
||||||
readTable["."] = (stream:Stream) -> FieldExp(nextToken(stream, "a field name"), assertRead(stream, readTable));
|
readTable["."] = (stream, k) -> FieldExp(nextToken(stream, "a field name"), assertRead(stream, k));
|
||||||
|
|
||||||
// Lets you construct key-value pairs for map literals or for-loops
|
// Lets you construct key-value pairs for map literals or for-loops
|
||||||
readTable["=>"] = (stream:Stream) -> KeyValueExp(assertRead(stream, readTable), assertRead(stream, readTable));
|
readTable["=>"] = (stream, k) -> KeyValueExp(assertRead(stream, k), assertRead(stream, k));
|
||||||
|
|
||||||
readTable[")"] = (stream:Stream) -> {
|
readTable[")"] = (stream, k) -> {
|
||||||
stream.putBackString(")");
|
stream.putBackString(")");
|
||||||
throw new UnmatchedBracketSignal(")", stream.position());
|
throw new UnmatchedBracketSignal(")", stream.position());
|
||||||
};
|
};
|
||||||
readTable["]"] = (stream:Stream) -> {
|
readTable["]"] = (stream, k) -> {
|
||||||
stream.putBackString("]");
|
stream.putBackString("]");
|
||||||
throw new UnmatchedBracketSignal("]", stream.position());
|
throw new UnmatchedBracketSignal("]", stream.position());
|
||||||
};
|
};
|
||||||
|
|
||||||
readTable["`"] = (stream) -> Quasiquote(assertRead(stream, readTable));
|
readTable["`"] = (stream, k) -> Quasiquote(assertRead(stream, k));
|
||||||
readTable[","] = (stream) -> Unquote(assertRead(stream, readTable));
|
readTable[","] = (stream, k) -> Unquote(assertRead(stream, k));
|
||||||
readTable[",@"] = (stream) -> UnquoteList(assertRead(stream, readTable));
|
readTable[",@"] = (stream, k) -> UnquoteList(assertRead(stream, k));
|
||||||
|
|
||||||
// Because macro keys are sorted by length and peekChars(0) returns "", this will be used as the default reader macro:
|
// Because macro keys are sorted by length and peekChars(0) returns "", this will be used as the default reader macro:
|
||||||
readTable[""] = (stream) -> Symbol(nextToken(stream, "a symbol name"));
|
readTable[""] = (stream, k) -> Symbol(nextToken(stream, "a symbol name"));
|
||||||
|
|
||||||
return readTable;
|
return readTable;
|
||||||
}
|
}
|
||||||
@@ -101,9 +102,9 @@ class Reader {
|
|||||||
return tok;
|
return tok;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function assertRead(stream:Stream, readTable:Map<String, ReadFunction>):ReaderExp {
|
public static function assertRead(stream:Stream, k:KissState):ReaderExp {
|
||||||
var position = stream.position();
|
var position = stream.position();
|
||||||
return switch (read(stream, readTable)) {
|
return switch (read(stream, k)) {
|
||||||
case Some(exp):
|
case Some(exp):
|
||||||
exp;
|
exp;
|
||||||
case None:
|
case None:
|
||||||
@@ -111,40 +112,54 @@ class Reader {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function read(stream:Stream, readTable:Map<String, ReadFunction>):Option<ReaderExp> {
|
static function chooseReadFunction(stream:Stream, readTable:ReadTable):Null<ReadFunction> {
|
||||||
|
var readTableKeys = [for (key in readTable.keys()) key];
|
||||||
|
readTableKeys.sort((a, b) -> b.length - a.length);
|
||||||
|
|
||||||
|
for (key in readTableKeys) {
|
||||||
|
switch (stream.peekChars(key.length)) {
|
||||||
|
case Some(ky) if (ky == key):
|
||||||
|
stream.dropString(key);
|
||||||
|
return readTable[key];
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function read(stream:Stream, k:KissState):Option<ReaderExp> {
|
||||||
|
var readTable = k.readTable;
|
||||||
stream.dropWhitespace();
|
stream.dropWhitespace();
|
||||||
|
|
||||||
if (stream.isEmpty())
|
if (stream.isEmpty())
|
||||||
return None;
|
return None;
|
||||||
|
|
||||||
var position = stream.position();
|
var position = stream.position();
|
||||||
var readTableKeys = [for (key in readTable.keys()) key];
|
|
||||||
readTableKeys.sort((a, b) -> b.length - a.length);
|
|
||||||
|
|
||||||
for (key in readTableKeys) {
|
var readFunction = null;
|
||||||
switch (stream.peekChars(key.length)) {
|
if (stream.startOfLine)
|
||||||
case Some(k) if (k == key):
|
readFunction = chooseReadFunction(stream, k.startOfLineReadTable);
|
||||||
stream.dropString(key);
|
if (readFunction == null)
|
||||||
var expOrNull = readTable[key](stream);
|
readFunction = chooseReadFunction(stream, k.readTable);
|
||||||
return if (expOrNull != null) {
|
if (readFunction == null)
|
||||||
Some(expOrNull.withPos(position));
|
throw 'No macro to read next expression';
|
||||||
} else {
|
|
||||||
read(stream, readTable);
|
var expOrNull = readFunction(stream, k);
|
||||||
}
|
return if (expOrNull != null) {
|
||||||
default:
|
Some(expOrNull.withPos(position));
|
||||||
}
|
} else {
|
||||||
|
read(stream, k);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw 'No macro to read next expression';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function readExpArray(stream:Stream, end:String, readTable:Map<String, ReadFunction>):Array<ReaderExp> {
|
public static function readExpArray(stream:Stream, end:String, k:KissState):Array<ReaderExp> {
|
||||||
var array = [];
|
var array = [];
|
||||||
while (!stream.startsWith(end)) {
|
while (!stream.startsWith(end)) {
|
||||||
stream.dropWhitespace();
|
stream.dropWhitespace();
|
||||||
if (!stream.startsWith(end)) {
|
if (!stream.startsWith(end)) {
|
||||||
try {
|
try {
|
||||||
array.push(assertRead(stream, readTable));
|
array.push(assertRead(stream, k));
|
||||||
} catch (s:UnmatchedBracketSignal) {
|
} catch (s:UnmatchedBracketSignal) {
|
||||||
if (s.type == end)
|
if (s.type == end)
|
||||||
break;
|
break;
|
||||||
@@ -160,13 +175,13 @@ class Reader {
|
|||||||
/**
|
/**
|
||||||
Read all the expressions in the given stream, processing them one by one while reading.
|
Read all the expressions in the given stream, processing them one by one while reading.
|
||||||
**/
|
**/
|
||||||
public static function readAndProcess(stream:Stream, readTable:Map<String, ReadFunction>, process:(ReaderExp) -> Void) {
|
public static function readAndProcess(stream:Stream, k:KissState, process:(ReaderExp) -> Void) {
|
||||||
while (true) {
|
while (true) {
|
||||||
stream.dropWhitespace();
|
stream.dropWhitespace();
|
||||||
if (stream.isEmpty())
|
if (stream.isEmpty())
|
||||||
break;
|
break;
|
||||||
var position = stream.position();
|
var position = stream.position();
|
||||||
var nextExp = Reader.read(stream, readTable);
|
var nextExp = Reader.read(stream, k);
|
||||||
// The last expression might be a comment, in which case None will be returned
|
// The last expression might be a comment, in which case None will be returned
|
||||||
switch (nextExp) {
|
switch (nextExp) {
|
||||||
case Some(nextExp):
|
case Some(nextExp):
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ class Stream {
|
|||||||
|
|
||||||
var absolutePerNewline = 1;
|
var absolutePerNewline = 1;
|
||||||
|
|
||||||
|
public var startOfLine(default, null) = true;
|
||||||
|
|
||||||
public function new(file:String) {
|
public function new(file:String) {
|
||||||
// Banish ye Windows line-endings
|
// Banish ye Windows line-endings
|
||||||
content = File.getContent(file);
|
content = File.getContent(file);
|
||||||
@@ -82,9 +84,14 @@ class Stream {
|
|||||||
line += 1;
|
line += 1;
|
||||||
lineLengths.push(column);
|
lineLengths.push(column);
|
||||||
column = 1;
|
column = 1;
|
||||||
|
startOfLine = true;
|
||||||
|
case c if (c.trim() == ""):
|
||||||
|
absoluteChar += 1;
|
||||||
|
column += 1;
|
||||||
default:
|
default:
|
||||||
absoluteChar += 1;
|
absoluteChar += 1;
|
||||||
column += 1;
|
column += 1;
|
||||||
|
startOfLine = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
content = content.substr(count);
|
content = content.substr(count);
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ class ReaderMacroTestCase extends Test {
|
|||||||
Assert.equals("String that takes the rest of the line", ReaderMacroTestCase.myLine());
|
Assert.equals("String that takes the rest of the line", ReaderMacroTestCase.myLine());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function testReadNot() {
|
||||||
|
Assert.isTrue(ReaderMacroTestCase.myBool());
|
||||||
|
}
|
||||||
|
|
||||||
function testDefAlias() {
|
function testDefAlias() {
|
||||||
Assert.equals(9, ReaderMacroTestCase.mySum);
|
Assert.equals(9, ReaderMacroTestCase.mySum);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
(defreadermacro "!" [stream]
|
(defreadermacro &start "!" [stream]
|
||||||
(let [line (stream.expect "a string line" (lambda [] (stream.takeLine)))]
|
(let [line (stream.expect "a string line" (lambda [] (stream.takeLine)))]
|
||||||
(ReaderExp.StrExp line)))
|
(ReaderExp.StrExp line)))
|
||||||
|
|
||||||
@@ -6,6 +6,9 @@
|
|||||||
!String that takes the rest of the line
|
!String that takes the rest of the line
|
||||||
)
|
)
|
||||||
|
|
||||||
|
(defun myBool []
|
||||||
|
(begin !false))
|
||||||
|
|
||||||
(defalias pluppers +)
|
(defalias pluppers +)
|
||||||
(defalias fluffers 5)
|
(defalias fluffers 5)
|
||||||
(defalias buffers 4)
|
(defalias buffers 4)
|
||||||
|
|||||||
Reference in New Issue
Block a user