Fix tab/space format
This commit is contained in:
5
hxformat.json
Normal file
5
hxformat.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"indentation": {
|
||||
"character": " "
|
||||
}
|
||||
}
|
||||
@@ -11,89 +11,89 @@ using StringTools;
|
||||
typedef FieldFormFunction = (position:String, args:Array<ReaderExp>, convert:ExprConversion) -> Field;
|
||||
|
||||
class FieldForms {
|
||||
public static function builtins() {
|
||||
var map:Map<String, FieldFormFunction> = [];
|
||||
public static function builtins() {
|
||||
var map:Map<String, FieldFormFunction> = [];
|
||||
|
||||
map["defvar"] = varOrProperty.bind("defvar");
|
||||
map["defprop"] = varOrProperty.bind("defprop");
|
||||
map["defvar"] = varOrProperty.bind("defvar");
|
||||
map["defprop"] = varOrProperty.bind("defprop");
|
||||
|
||||
map["defun"] = funcOrMethod.bind("defun");
|
||||
map["defmethod"] = funcOrMethod.bind("defmethod");
|
||||
map["defun"] = funcOrMethod.bind("defun");
|
||||
map["defmethod"] = funcOrMethod.bind("defmethod");
|
||||
|
||||
return map;
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
static function fieldAccess(formName:String, fieldName:String) {
|
||||
var access = [];
|
||||
if (formName == "defvar" || formName == "defun") {
|
||||
access.push(AStatic);
|
||||
}
|
||||
access.push(if (fieldName.startsWith("_")) APrivate else APublic);
|
||||
return access;
|
||||
}
|
||||
static function fieldAccess(formName:String, fieldName:String) {
|
||||
var access = [];
|
||||
if (formName == "defvar" || formName == "defun") {
|
||||
access.push(AStatic);
|
||||
}
|
||||
access.push(if (fieldName.startsWith("_")) APrivate else APublic);
|
||||
return access;
|
||||
}
|
||||
|
||||
static function fieldName(formName:String, position:String, nameExp:ReaderExp) {
|
||||
return switch (nameExp) {
|
||||
case Symbol(name):
|
||||
name;
|
||||
default:
|
||||
throw 'The first argument to $formName at $position should be a variable name';
|
||||
};
|
||||
}
|
||||
static function fieldName(formName:String, position:String, nameExp:ReaderExp) {
|
||||
return switch (nameExp) {
|
||||
case Symbol(name):
|
||||
name;
|
||||
default:
|
||||
throw 'The first argument to $formName at $position should be a variable name';
|
||||
};
|
||||
}
|
||||
|
||||
static function varOrProperty(formName:String, position:String, args:Array<ReaderExp>, convert:ExprConversion):Field {
|
||||
if (args.length != 2) {
|
||||
throw '$formName with $args at $position is not a valid field definition';
|
||||
}
|
||||
static function varOrProperty(formName:String, position:String, args:Array<ReaderExp>, convert:ExprConversion):Field {
|
||||
if (args.length != 2) {
|
||||
throw '$formName with $args at $position is not a valid field definition';
|
||||
}
|
||||
|
||||
var name = fieldName(formName, position, args[0]);
|
||||
var access = fieldAccess(formName, name);
|
||||
var name = fieldName(formName, position, args[0]);
|
||||
var access = fieldAccess(formName, name);
|
||||
|
||||
return {
|
||||
name: name,
|
||||
access: access,
|
||||
kind: FVar(null, // TODO allow type anotations
|
||||
convert(args[1])),
|
||||
pos: Context.currentPos()
|
||||
};
|
||||
}
|
||||
return {
|
||||
name: name,
|
||||
access: access,
|
||||
kind: FVar(null, // TODO allow type anotations
|
||||
convert(args[1])),
|
||||
pos: Context.currentPos()
|
||||
};
|
||||
}
|
||||
|
||||
static function funcOrMethod(formName:String, position:String, args:Array<ReaderExp>, convert:ExprConversion):Field {
|
||||
if (args.length <= 2) {
|
||||
throw '$formName with $args is not a valid function/method definition';
|
||||
}
|
||||
static function funcOrMethod(formName:String, position:String, args:Array<ReaderExp>, convert:ExprConversion):Field {
|
||||
if (args.length <= 2) {
|
||||
throw '$formName with $args is not a valid function/method definition';
|
||||
}
|
||||
|
||||
var name = fieldName(formName, position, args[0]);
|
||||
var access = fieldAccess(formName, name);
|
||||
var name = fieldName(formName, position, args[0]);
|
||||
var access = fieldAccess(formName, name);
|
||||
|
||||
return {
|
||||
name: name,
|
||||
access: access,
|
||||
kind: FFun({
|
||||
args: switch (args[1]) {
|
||||
case ListExp(funcArgs):
|
||||
[
|
||||
for (funcArg in funcArgs)
|
||||
{
|
||||
name: switch (funcArg) {
|
||||
case Symbol(name):
|
||||
name;
|
||||
default:
|
||||
throw '$funcArg should be a symbol for a function argument';
|
||||
},
|
||||
type: null
|
||||
}
|
||||
];
|
||||
default:
|
||||
throw '$args[1] should be an argument list';
|
||||
},
|
||||
ret: null,
|
||||
expr: {
|
||||
pos: Context.currentPos(),
|
||||
expr: EReturn(convert(CallExp(Symbol("begin"), args.slice(2))))
|
||||
}
|
||||
}),
|
||||
pos: Context.currentPos()
|
||||
};
|
||||
}
|
||||
return {
|
||||
name: name,
|
||||
access: access,
|
||||
kind: FFun({
|
||||
args: switch (args[1]) {
|
||||
case ListExp(funcArgs):
|
||||
[
|
||||
for (funcArg in funcArgs)
|
||||
{
|
||||
name: switch (funcArg) {
|
||||
case Symbol(name):
|
||||
name;
|
||||
default:
|
||||
throw '$funcArg should be a symbol for a function argument';
|
||||
},
|
||||
type: null
|
||||
}
|
||||
];
|
||||
default:
|
||||
throw '$args[1] should be an argument list';
|
||||
},
|
||||
ret: null,
|
||||
expr: {
|
||||
pos: Context.currentPos(),
|
||||
expr: EReturn(convert(CallExp(Symbol("begin"), args.slice(2))))
|
||||
}
|
||||
}),
|
||||
pos: Context.currentPos()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
162
src/kiss/Kiss.hx
162
src/kiss/Kiss.hx
@@ -9,91 +9,91 @@ import kiss.SpecialForms;
|
||||
import kiss.Macros;
|
||||
|
||||
class Kiss {
|
||||
/**
|
||||
Build a Haxe class from a corresponding .kiss file
|
||||
**/
|
||||
macro static public function build(kissFile:String):Array<Field> {
|
||||
var classFields = Context.getBuildFields();
|
||||
/**
|
||||
Build a Haxe class from a corresponding .kiss file
|
||||
**/
|
||||
macro static public function build(kissFile:String):Array<Field> {
|
||||
var classFields = Context.getBuildFields();
|
||||
|
||||
var stream = new Stream(kissFile);
|
||||
var stream = new Stream(kissFile);
|
||||
|
||||
var readTable = Reader.builtins();
|
||||
var fieldForms = FieldForms.builtins();
|
||||
var specialForms = SpecialForms.builtins();
|
||||
var macros = Macros.builtins();
|
||||
var readTable = Reader.builtins();
|
||||
var fieldForms = FieldForms.builtins();
|
||||
var specialForms = SpecialForms.builtins();
|
||||
var macros = Macros.builtins();
|
||||
|
||||
while (true) {
|
||||
stream.dropWhitespace();
|
||||
if (stream.isEmpty())
|
||||
break;
|
||||
var position = stream.position();
|
||||
var nextExp = Reader.read(stream, readTable);
|
||||
#if test
|
||||
trace(nextExp);
|
||||
#end
|
||||
// The last expression might be a comment, in which case None will be returned
|
||||
switch (nextExp) {
|
||||
case Some(nextExp):
|
||||
classFields.push(readerExpToField(nextExp, position, fieldForms, macros, specialForms));
|
||||
case None:
|
||||
stream.dropWhitespace(); // If there was a comment, drop whitespace that comes after
|
||||
}
|
||||
}
|
||||
while (true) {
|
||||
stream.dropWhitespace();
|
||||
if (stream.isEmpty())
|
||||
break;
|
||||
var position = stream.position();
|
||||
var nextExp = Reader.read(stream, readTable);
|
||||
#if test
|
||||
trace(nextExp);
|
||||
#end
|
||||
// The last expression might be a comment, in which case None will be returned
|
||||
switch (nextExp) {
|
||||
case Some(nextExp):
|
||||
classFields.push(readerExpToField(nextExp, position, fieldForms, macros, specialForms));
|
||||
case None:
|
||||
stream.dropWhitespace(); // If there was a comment, drop whitespace that comes after
|
||||
}
|
||||
}
|
||||
|
||||
return classFields;
|
||||
}
|
||||
return classFields;
|
||||
}
|
||||
|
||||
static function readerExpToField(exp:ReaderExp, position:String, fieldForms:Map<String, FieldFormFunction>, macros:Map<String, MacroFunction>,
|
||||
specialForms:Map<String, SpecialFormFunction>):Field {
|
||||
return switch (exp) {
|
||||
case CallExp(Symbol(formName), args) if (fieldForms.exists(formName)):
|
||||
fieldForms[formName](position, args, readerExpToHaxeExpr.bind(_, macros, specialForms));
|
||||
default:
|
||||
throw '$exp at $position is not a valid field form';
|
||||
};
|
||||
}
|
||||
static function readerExpToField(exp:ReaderExp, position:String, fieldForms:Map<String, FieldFormFunction>, macros:Map<String, MacroFunction>,
|
||||
specialForms:Map<String, SpecialFormFunction>):Field {
|
||||
return switch (exp) {
|
||||
case CallExp(Symbol(formName), args) if (fieldForms.exists(formName)):
|
||||
fieldForms[formName](position, args, readerExpToHaxeExpr.bind(_, macros, specialForms));
|
||||
default:
|
||||
throw '$exp at $position is not a valid field form';
|
||||
};
|
||||
}
|
||||
|
||||
static function readerExpToHaxeExpr(exp:ReaderExp, macros:Map<String, MacroFunction>, specialForms:Map<String, SpecialFormFunction>):Expr {
|
||||
// Bind the table arguments of this function for easy recursive calling/passing
|
||||
var convert = readerExpToHaxeExpr.bind(_, macros, specialForms);
|
||||
var expr = switch (exp) {
|
||||
case Symbol(name):
|
||||
Context.parse(name, Context.currentPos());
|
||||
case StrExp(s):
|
||||
{
|
||||
pos: Context.currentPos(),
|
||||
expr: EConst(CString(s))
|
||||
};
|
||||
case CallExp(Symbol(mac), args) if (macros.exists(mac)):
|
||||
convert(macros[mac](args));
|
||||
case CallExp(Symbol(specialForm), args) if (specialForms.exists(specialForm)):
|
||||
specialForms[specialForm](args, convert);
|
||||
case CallExp(func, body):
|
||||
{
|
||||
pos: Context.currentPos(),
|
||||
expr: ECall(readerExpToHaxeExpr(func, macros, specialForms), [for (bodyExp in body) readerExpToHaxeExpr(bodyExp, macros, specialForms)])
|
||||
};
|
||||
case ListExp(elements):
|
||||
{
|
||||
pos: Context.currentPos(),
|
||||
expr: ENew({
|
||||
pack: ["kiss"],
|
||||
name: "List"
|
||||
}, [
|
||||
{
|
||||
pos: Context.currentPos(),
|
||||
expr: EArrayDecl([for (elementExp in elements) convert(elementExp)])
|
||||
}
|
||||
])
|
||||
}
|
||||
case RawHaxe(code):
|
||||
Context.parse(code, Context.currentPos());
|
||||
default:
|
||||
throw 'cannot convert $exp yet';
|
||||
};
|
||||
#if test
|
||||
trace(expr.expr);
|
||||
#end
|
||||
return expr;
|
||||
}
|
||||
static function readerExpToHaxeExpr(exp:ReaderExp, macros:Map<String, MacroFunction>, specialForms:Map<String, SpecialFormFunction>):Expr {
|
||||
// Bind the table arguments of this function for easy recursive calling/passing
|
||||
var convert = readerExpToHaxeExpr.bind(_, macros, specialForms);
|
||||
var expr = switch (exp) {
|
||||
case Symbol(name):
|
||||
Context.parse(name, Context.currentPos());
|
||||
case StrExp(s):
|
||||
{
|
||||
pos: Context.currentPos(),
|
||||
expr: EConst(CString(s))
|
||||
};
|
||||
case CallExp(Symbol(mac), args) if (macros.exists(mac)):
|
||||
convert(macros[mac](args));
|
||||
case CallExp(Symbol(specialForm), args) if (specialForms.exists(specialForm)):
|
||||
specialForms[specialForm](args, convert);
|
||||
case CallExp(func, body):
|
||||
{
|
||||
pos: Context.currentPos(),
|
||||
expr: ECall(readerExpToHaxeExpr(func, macros, specialForms), [for (bodyExp in body) readerExpToHaxeExpr(bodyExp, macros, specialForms)])
|
||||
};
|
||||
case ListExp(elements):
|
||||
{
|
||||
pos: Context.currentPos(),
|
||||
expr: ENew({
|
||||
pack: ["kiss"],
|
||||
name: "List"
|
||||
}, [
|
||||
{
|
||||
pos: Context.currentPos(),
|
||||
expr: EArrayDecl([for (elementExp in elements) convert(elementExp)])
|
||||
}
|
||||
])
|
||||
}
|
||||
case RawHaxe(code):
|
||||
Context.parse(code, Context.currentPos());
|
||||
default:
|
||||
throw 'cannot convert $exp yet';
|
||||
};
|
||||
#if test
|
||||
trace(expr.expr);
|
||||
#end
|
||||
return expr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,52 +1,52 @@
|
||||
package kiss;
|
||||
|
||||
@:forward(length, concat, contains, copy, filter, indexOf, // insert is re-implemented with negative index support
|
||||
iterator, join, keyValueIterator,
|
||||
lastIndexOf, map, pop, push, remove, resize, reverse, shift, // slice is re-implemented with negative index support
|
||||
sort,
|
||||
// splice is re-implemented with negative index support,
|
||||
toString, unshift)
|
||||
iterator, join, keyValueIterator,
|
||||
lastIndexOf, map, pop, push, remove, resize, reverse, shift, // slice is re-implemented with negative index support
|
||||
sort,
|
||||
// splice is re-implemented with negative index support,
|
||||
toString, unshift)
|
||||
abstract List<T>(Array<T>) from Array<T> to Array<T> {
|
||||
public inline function new(a:Array<T>) {
|
||||
this = a;
|
||||
}
|
||||
public inline function new(a:Array<T>) {
|
||||
this = a;
|
||||
}
|
||||
|
||||
@:from
|
||||
static public function fromArray<T>(a:Array<T>) {
|
||||
return new List<T>(a);
|
||||
}
|
||||
@:from
|
||||
static public function fromArray<T>(a:Array<T>) {
|
||||
return new List<T>(a);
|
||||
}
|
||||
|
||||
@:to
|
||||
public function toArray<T>() {
|
||||
return this;
|
||||
}
|
||||
@:to
|
||||
public function toArray<T>() {
|
||||
return this;
|
||||
}
|
||||
|
||||
inline function realIndex(idx:Int) {
|
||||
return if (idx < 0) this.length + idx else idx;
|
||||
}
|
||||
inline function realIndex(idx:Int) {
|
||||
return if (idx < 0) this.length + idx else idx;
|
||||
}
|
||||
|
||||
@:arrayAccess
|
||||
public inline function get<T>(idx:Int):Null<T> {
|
||||
return this[realIndex(idx)];
|
||||
}
|
||||
@:arrayAccess
|
||||
public inline function get<T>(idx:Int):Null<T> {
|
||||
return this[realIndex(idx)];
|
||||
}
|
||||
|
||||
@:arrayAccess
|
||||
public inline function arrayWrite(idx:Int, v:T):T {
|
||||
this[realIndex(idx)] = v;
|
||||
return v;
|
||||
}
|
||||
@:arrayAccess
|
||||
public inline function arrayWrite(idx:Int, v:T):T {
|
||||
this[realIndex(idx)] = v;
|
||||
return v;
|
||||
}
|
||||
|
||||
public function insert(idx:Int, v:T) {
|
||||
this.insert(realIndex(idx), v);
|
||||
}
|
||||
public function insert(idx:Int, v:T) {
|
||||
this.insert(realIndex(idx), v);
|
||||
}
|
||||
|
||||
public function slice(start:Int, ?end:Int) {
|
||||
if (end == null)
|
||||
end = this.length;
|
||||
return this.slice(realIndex(start), realIndex(end));
|
||||
}
|
||||
public function slice(start:Int, ?end:Int) {
|
||||
if (end == null)
|
||||
end = this.length;
|
||||
return this.slice(realIndex(start), realIndex(end));
|
||||
}
|
||||
|
||||
public function splice(start:Int, len:Int) {
|
||||
return this.splice(realIndex(start), len);
|
||||
}
|
||||
public function splice(start:Int, len:Int) {
|
||||
return this.splice(realIndex(start), len);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,37 +6,37 @@ import kiss.Reader;
|
||||
typedef MacroFunction = (Array<ReaderExp>) -> ReaderExp;
|
||||
|
||||
class Macros {
|
||||
public static function builtins() {
|
||||
var macros:Map<String, MacroFunction> = [];
|
||||
public static function builtins() {
|
||||
var macros:Map<String, MacroFunction> = [];
|
||||
|
||||
macros["+"] = foldMacro("Prelude.add");
|
||||
macros["+"] = foldMacro("Prelude.add");
|
||||
|
||||
macros["-"] = foldMacro("Prelude.subtract");
|
||||
macros["-"] = foldMacro("Prelude.subtract");
|
||||
|
||||
macros["*"] = foldMacro("Prelude.multiply");
|
||||
macros["*"] = foldMacro("Prelude.multiply");
|
||||
|
||||
macros["/"] = foldMacro("Prelude.divide");
|
||||
macros["/"] = foldMacro("Prelude.divide");
|
||||
|
||||
macros["%"] = (exps:Array<ReaderExp>) -> {
|
||||
if (exps.length != 2) {
|
||||
throw 'Got ${exps.length} arguments for % instead of 2.';
|
||||
}
|
||||
CallExp(Symbol("Prelude.mod"), [exps[1], exps[0]]);
|
||||
};
|
||||
macros["%"] = (exps:Array<ReaderExp>) -> {
|
||||
if (exps.length != 2) {
|
||||
throw 'Got ${exps.length} arguments for % instead of 2.';
|
||||
}
|
||||
CallExp(Symbol("Prelude.mod"), [exps[1], exps[0]]);
|
||||
};
|
||||
|
||||
macros["^"] = (exps:Array<ReaderExp>) -> {
|
||||
if (exps.length != 2) {
|
||||
throw 'Got ${exps.length} arguments for ^ instead of 2.';
|
||||
}
|
||||
CallExp(Symbol("Prelude.pow"), [exps[1], exps[0]]);
|
||||
};
|
||||
macros["^"] = (exps:Array<ReaderExp>) -> {
|
||||
if (exps.length != 2) {
|
||||
throw 'Got ${exps.length} arguments for ^ instead of 2.';
|
||||
}
|
||||
CallExp(Symbol("Prelude.pow"), [exps[1], exps[0]]);
|
||||
};
|
||||
|
||||
return macros;
|
||||
}
|
||||
return macros;
|
||||
}
|
||||
|
||||
static function foldMacro(func:String):MacroFunction {
|
||||
return (exps) -> {
|
||||
CallExp(Symbol("Lambda.fold"), [ListExp(exps.slice(1)), Symbol(func), exps[0]]);
|
||||
};
|
||||
}
|
||||
static function foldMacro(func:String):MacroFunction {
|
||||
return (exps) -> {
|
||||
CallExp(Symbol("Lambda.fold"), [ListExp(exps.slice(1)), Symbol(func), exps[0]]);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
package kiss;
|
||||
|
||||
class Prelude {
|
||||
public static function add(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
public static function add(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
public static function subtract(val, from) {
|
||||
return from - val;
|
||||
}
|
||||
public static function subtract(val, from) {
|
||||
return from - val;
|
||||
}
|
||||
|
||||
public static function multiply(a, b) {
|
||||
return a * b;
|
||||
}
|
||||
public static function multiply(a, b) {
|
||||
return a * b;
|
||||
}
|
||||
|
||||
public static function divide(bottom:Float, top:Float) {
|
||||
return top / bottom;
|
||||
}
|
||||
public static function divide(bottom:Float, top:Float) {
|
||||
return top / bottom;
|
||||
}
|
||||
|
||||
public static function mod(bottom, top) {
|
||||
return top % bottom;
|
||||
}
|
||||
public static function mod(bottom, top) {
|
||||
return top % bottom;
|
||||
}
|
||||
|
||||
public static function pow(exponent, base) {
|
||||
return Math.pow(base, exponent);
|
||||
}
|
||||
public static function pow(exponent, base) {
|
||||
return Math.pow(base, exponent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,76 +4,76 @@ import haxe.ds.Option;
|
||||
import kiss.Stream;
|
||||
|
||||
enum ReaderExp {
|
||||
CallExp(func:ReaderExp, args:Array<ReaderExp>); // (f a1 a2...)
|
||||
ListExp(exps:Array<ReaderExp>); // [v1 v2 v3]
|
||||
StrExp(s:String); // "literal"
|
||||
Symbol(name:String); // s
|
||||
RawHaxe(code:String);
|
||||
CallExp(func:ReaderExp, args:Array<ReaderExp>); // (f a1 a2...)
|
||||
ListExp(exps:Array<ReaderExp>); // [v1 v2 v3]
|
||||
StrExp(s:String); // "literal"
|
||||
Symbol(name:String); // s
|
||||
RawHaxe(code:String);
|
||||
}
|
||||
|
||||
typedef ReadFunction = (Stream) -> Null<ReaderExp>;
|
||||
|
||||
class Reader {
|
||||
// The built-in readtable
|
||||
public static function builtins() {
|
||||
var readTable:Map<String, ReadFunction> = [];
|
||||
// The built-in readtable
|
||||
public static function builtins() {
|
||||
var readTable:Map<String, ReadFunction> = [];
|
||||
|
||||
readTable["("] = (stream) -> CallExp(assertRead(stream, readTable), readExpArray(stream, ")", readTable));
|
||||
readTable["["] = (stream) -> ListExp(readExpArray(stream, "]", readTable));
|
||||
readTable["\""] = (stream) -> StrExp(stream.expect("closing \"", () -> stream.takeUntilAndDrop("\"")));
|
||||
readTable["/*"] = (stream) -> {
|
||||
stream.dropUntil("*/");
|
||||
stream.dropString("*/");
|
||||
null;
|
||||
};
|
||||
readTable["//"] = (stream) -> {
|
||||
stream.dropUntil("\n");
|
||||
null;
|
||||
};
|
||||
readTable["#|"] = (stream) -> RawHaxe(stream.expect("closing |", () -> stream.takeUntilAndDrop("|#")));
|
||||
readTable["("] = (stream) -> CallExp(assertRead(stream, readTable), readExpArray(stream, ")", readTable));
|
||||
readTable["["] = (stream) -> ListExp(readExpArray(stream, "]", readTable));
|
||||
readTable["\""] = (stream) -> StrExp(stream.expect("closing \"", () -> stream.takeUntilAndDrop("\"")));
|
||||
readTable["/*"] = (stream) -> {
|
||||
stream.dropUntil("*/");
|
||||
stream.dropString("*/");
|
||||
null;
|
||||
};
|
||||
readTable["//"] = (stream) -> {
|
||||
stream.dropUntil("\n");
|
||||
null;
|
||||
};
|
||||
readTable["#|"] = (stream) -> RawHaxe(stream.expect("closing |", () -> stream.takeUntilAndDrop("|#")));
|
||||
|
||||
return readTable;
|
||||
}
|
||||
return readTable;
|
||||
}
|
||||
|
||||
public static function assertRead(stream:Stream, readTable:Map<String, ReadFunction>):ReaderExp {
|
||||
var position = stream.position();
|
||||
return switch (read(stream, readTable)) {
|
||||
case Some(exp):
|
||||
exp;
|
||||
case None:
|
||||
throw 'There were no expressions left in the stream at $position';
|
||||
};
|
||||
}
|
||||
public static function assertRead(stream:Stream, readTable:Map<String, ReadFunction>):ReaderExp {
|
||||
var position = stream.position();
|
||||
return switch (read(stream, readTable)) {
|
||||
case Some(exp):
|
||||
exp;
|
||||
case None:
|
||||
throw 'There were no expressions left in the stream at $position';
|
||||
};
|
||||
}
|
||||
|
||||
public static function read(stream:Stream, readTable:Map<String, ReadFunction>):Option<ReaderExp> {
|
||||
stream.dropWhitespace();
|
||||
public static function read(stream:Stream, readTable:Map<String, ReadFunction>):Option<ReaderExp> {
|
||||
stream.dropWhitespace();
|
||||
|
||||
if (stream.isEmpty())
|
||||
return None;
|
||||
if (stream.isEmpty())
|
||||
return None;
|
||||
|
||||
var readTableKeys = [for (key in readTable.keys()) key];
|
||||
readTableKeys.sort((a, b) -> b.length - a.length);
|
||||
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(k) if (k == key):
|
||||
stream.dropString(key);
|
||||
var expOrNull = readTable[key](stream);
|
||||
return if (expOrNull != null) Some(expOrNull) else read(stream, readTable);
|
||||
default:
|
||||
}
|
||||
}
|
||||
for (key in readTableKeys) {
|
||||
switch (stream.peekChars(key.length)) {
|
||||
case Some(k) if (k == key):
|
||||
stream.dropString(key);
|
||||
var expOrNull = readTable[key](stream);
|
||||
return if (expOrNull != null) Some(expOrNull) else read(stream, readTable);
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
return Some(Symbol(stream.expect("a symbol name", () -> stream.takeUntilOneOf([")", "]", "/*", "\n", " "]))));
|
||||
}
|
||||
return Some(Symbol(stream.expect("a symbol name", () -> stream.takeUntilOneOf([")", "]", "/*", "\n", " "]))));
|
||||
}
|
||||
|
||||
public static function readExpArray(stream:Stream, end:String, readTable:Map<String, ReadFunction>):Array<ReaderExp> {
|
||||
var array = [];
|
||||
while (stream.expect('$end to terminate list', () -> stream.peekChars(end.length)) != end) {
|
||||
stream.dropWhitespace();
|
||||
array.push(assertRead(stream, readTable));
|
||||
}
|
||||
stream.dropString(end);
|
||||
return array;
|
||||
}
|
||||
public static function readExpArray(stream:Stream, end:String, readTable:Map<String, ReadFunction>):Array<ReaderExp> {
|
||||
var array = [];
|
||||
while (stream.expect('$end to terminate list', () -> stream.peekChars(end.length)) != end) {
|
||||
stream.dropWhitespace();
|
||||
array.push(assertRead(stream, readTable));
|
||||
}
|
||||
stream.dropString(end);
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,19 +9,19 @@ import kiss.Types;
|
||||
typedef SpecialFormFunction = (args:Array<ReaderExp>, convert:ExprConversion) -> Expr;
|
||||
|
||||
class SpecialForms {
|
||||
public static function builtins() {
|
||||
var map:Map<String, SpecialFormFunction> = [];
|
||||
public static function builtins() {
|
||||
var map:Map<String, SpecialFormFunction> = [];
|
||||
|
||||
map["begin"] = (args:Array<ReaderExp>, convert:ExprConversion) -> {
|
||||
pos: Context.currentPos(),
|
||||
expr: EBlock([for (bodyExp in args) convert(bodyExp)])
|
||||
};
|
||||
map["begin"] = (args:Array<ReaderExp>, convert:ExprConversion) -> {
|
||||
pos: Context.currentPos(),
|
||||
expr: EBlock([for (bodyExp in args) convert(bodyExp)])
|
||||
};
|
||||
|
||||
map["nth"] = (args:Array<ReaderExp>, convert:ExprConversion) -> {
|
||||
pos: Context.currentPos(),
|
||||
expr: EArray(convert(args[0]), convert(args[1]))
|
||||
};
|
||||
map["nth"] = (args:Array<ReaderExp>, convert:ExprConversion) -> {
|
||||
pos: Context.currentPos(),
|
||||
expr: EArray(convert(args[0]), convert(args[1]))
|
||||
};
|
||||
|
||||
return map;
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,102 +7,102 @@ using StringTools;
|
||||
using Lambda;
|
||||
|
||||
class Stream {
|
||||
var content:String;
|
||||
var file:String;
|
||||
var line:Int;
|
||||
var column:Int;
|
||||
var content:String;
|
||||
var file:String;
|
||||
var line:Int;
|
||||
var column:Int;
|
||||
|
||||
public function new(file:String) {
|
||||
// Banish ye Windows line-endings
|
||||
content = File.getContent(file).replace('\r', '');
|
||||
// Life is easier with a trailing newline
|
||||
if (content.charAt(content.length - 1) != "\n")
|
||||
content += "\n";
|
||||
public function new(file:String) {
|
||||
// Banish ye Windows line-endings
|
||||
content = File.getContent(file).replace('\r', '');
|
||||
// Life is easier with a trailing newline
|
||||
if (content.charAt(content.length - 1) != "\n")
|
||||
content += "\n";
|
||||
|
||||
this.file = file;
|
||||
line = 1;
|
||||
column = 1;
|
||||
}
|
||||
this.file = file;
|
||||
line = 1;
|
||||
column = 1;
|
||||
}
|
||||
|
||||
public function peekChars(chars:Int):Option<String> {
|
||||
if (content.length < chars)
|
||||
return None;
|
||||
return Some(content.substr(0, chars));
|
||||
}
|
||||
public function peekChars(chars:Int):Option<String> {
|
||||
if (content.length < chars)
|
||||
return None;
|
||||
return Some(content.substr(0, chars));
|
||||
}
|
||||
|
||||
public function isEmpty() {
|
||||
return content.length == 0;
|
||||
}
|
||||
public function isEmpty() {
|
||||
return content.length == 0;
|
||||
}
|
||||
|
||||
public function position() {
|
||||
return '$file:$line:$column';
|
||||
}
|
||||
public function position() {
|
||||
return '$file:$line:$column';
|
||||
}
|
||||
|
||||
/** Every drop call should end up calling dropChars() or the position tracker will be wrong. **/
|
||||
private function dropChars(count:Int) {
|
||||
for (idx in 0...count) {
|
||||
switch (content.charAt(idx)) {
|
||||
case "\n":
|
||||
line += 1;
|
||||
column = 1;
|
||||
default:
|
||||
column += 1;
|
||||
}
|
||||
}
|
||||
content = content.substr(count);
|
||||
}
|
||||
/** Every drop call should end up calling dropChars() or the position tracker will be wrong. **/
|
||||
private function dropChars(count:Int) {
|
||||
for (idx in 0...count) {
|
||||
switch (content.charAt(idx)) {
|
||||
case "\n":
|
||||
line += 1;
|
||||
column = 1;
|
||||
default:
|
||||
column += 1;
|
||||
}
|
||||
}
|
||||
content = content.substr(count);
|
||||
}
|
||||
|
||||
public function takeChars(count:Int):Option<String> {
|
||||
if (count > content.length)
|
||||
return None;
|
||||
var toReturn = content.substr(0, count);
|
||||
dropChars(count);
|
||||
return Some(toReturn);
|
||||
}
|
||||
public function takeChars(count:Int):Option<String> {
|
||||
if (count > content.length)
|
||||
return None;
|
||||
var toReturn = content.substr(0, count);
|
||||
dropChars(count);
|
||||
return Some(toReturn);
|
||||
}
|
||||
|
||||
public function dropString(s:String) {
|
||||
var toDrop = content.substr(0, s.length);
|
||||
if (toDrop != s) {
|
||||
throw 'Expected $s at ${position()}';
|
||||
}
|
||||
dropChars(s.length);
|
||||
}
|
||||
public function dropString(s:String) {
|
||||
var toDrop = content.substr(0, s.length);
|
||||
if (toDrop != s) {
|
||||
throw 'Expected $s at ${position()}';
|
||||
}
|
||||
dropChars(s.length);
|
||||
}
|
||||
|
||||
public function dropUntil(s:String) {
|
||||
dropChars(content.indexOf(s));
|
||||
}
|
||||
public function dropUntil(s:String) {
|
||||
dropChars(content.indexOf(s));
|
||||
}
|
||||
|
||||
public function dropWhitespace() {
|
||||
var trimmed = content.ltrim();
|
||||
dropChars(content.length - trimmed.length);
|
||||
}
|
||||
public function dropWhitespace() {
|
||||
var trimmed = content.ltrim();
|
||||
dropChars(content.length - trimmed.length);
|
||||
}
|
||||
|
||||
public function takeUntilOneOf(terminators:Array<String>):Option<String> {
|
||||
var indices = [for (term in terminators) content.indexOf(term)].filter((idx) -> idx >= 0);
|
||||
if (indices.length == 0)
|
||||
return None;
|
||||
var firstIndex = Math.floor(indices.fold(Math.min, indices[0]));
|
||||
return takeChars(firstIndex);
|
||||
}
|
||||
public function takeUntilOneOf(terminators:Array<String>):Option<String> {
|
||||
var indices = [for (term in terminators) content.indexOf(term)].filter((idx) -> idx >= 0);
|
||||
if (indices.length == 0)
|
||||
return None;
|
||||
var firstIndex = Math.floor(indices.fold(Math.min, indices[0]));
|
||||
return takeChars(firstIndex);
|
||||
}
|
||||
|
||||
public function takeUntilAndDrop(s:String):Option<String> {
|
||||
var idx = content.indexOf(s);
|
||||
public function takeUntilAndDrop(s:String):Option<String> {
|
||||
var idx = content.indexOf(s);
|
||||
|
||||
if (idx < 0)
|
||||
return None;
|
||||
if (idx < 0)
|
||||
return None;
|
||||
|
||||
var toReturn = content.substr(0, idx);
|
||||
dropChars(toReturn.length + s.length);
|
||||
return Some(toReturn);
|
||||
}
|
||||
var toReturn = content.substr(0, idx);
|
||||
dropChars(toReturn.length + s.length);
|
||||
return Some(toReturn);
|
||||
}
|
||||
|
||||
public function expect(whatToExpect:String, f:Void->Option<String>):String {
|
||||
var position = position();
|
||||
switch (f()) {
|
||||
case Some(s):
|
||||
return s;
|
||||
case None:
|
||||
throw 'Expected $whatToExpect at $position';
|
||||
}
|
||||
}
|
||||
public function expect(whatToExpect:String, f:Void->Option<String>):String {
|
||||
var position = position();
|
||||
switch (f()) {
|
||||
case Some(s):
|
||||
return s;
|
||||
case None:
|
||||
throw 'Expected $whatToExpect at $position';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ import utest.Runner;
|
||||
import utest.ui.Report;
|
||||
|
||||
class TestMain {
|
||||
public static function main() {
|
||||
var runner = new Runner();
|
||||
runner.addCases(test.cases);
|
||||
Report.create(runner);
|
||||
runner.run();
|
||||
}
|
||||
public static function main() {
|
||||
var runner = new Runner();
|
||||
runner.addCases(test.cases);
|
||||
Report.create(runner);
|
||||
runner.run();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,72 +6,72 @@ import kiss.Prelude;
|
||||
|
||||
@:build(kiss.Kiss.build("src/test/cases/BasicTestCase.kiss"))
|
||||
class BasicTestCase extends Test {
|
||||
function testStaticVar() {
|
||||
Assert.equals("Howdy", BasicTestCase.message);
|
||||
}
|
||||
function testStaticVar() {
|
||||
Assert.equals("Howdy", BasicTestCase.message);
|
||||
}
|
||||
|
||||
function testHaxeInsertion() {
|
||||
Assert.equals(23, BasicTestCase.mathResult);
|
||||
}
|
||||
function testHaxeInsertion() {
|
||||
Assert.equals(23, BasicTestCase.mathResult);
|
||||
}
|
||||
|
||||
function testStaticFunction() {
|
||||
Assert.equals(6, BasicTestCase.myFloor(6.5));
|
||||
}
|
||||
function testStaticFunction() {
|
||||
Assert.equals(6, BasicTestCase.myFloor(6.5));
|
||||
}
|
||||
|
||||
function testFuncall() {
|
||||
Assert.equals(7, BasicTestCase.funResult);
|
||||
}
|
||||
function testFuncall() {
|
||||
Assert.equals(7, BasicTestCase.funResult);
|
||||
}
|
||||
|
||||
function testField() {
|
||||
Assert.equals(5, new BasicTestCase().myField);
|
||||
}
|
||||
function testField() {
|
||||
Assert.equals(5, new BasicTestCase().myField);
|
||||
}
|
||||
|
||||
function testMethod() {
|
||||
Assert.equals(5, new BasicTestCase().myMethod());
|
||||
}
|
||||
function testMethod() {
|
||||
Assert.equals(5, new BasicTestCase().myMethod());
|
||||
}
|
||||
|
||||
function testArray() {
|
||||
var arr = BasicTestCase.myArray;
|
||||
Assert.equals(3, arr.length);
|
||||
Assert.equals(1, arr[0]);
|
||||
Assert.equals(2, arr[1]);
|
||||
Assert.equals(3, arr[2]);
|
||||
function testArray() {
|
||||
var arr = BasicTestCase.myArray;
|
||||
Assert.equals(3, arr.length);
|
||||
Assert.equals(1, arr[0]);
|
||||
Assert.equals(2, arr[1]);
|
||||
Assert.equals(3, arr[2]);
|
||||
|
||||
// Kiss arrays can be negatively indexed like Python
|
||||
Assert.equals(3, arr[-1]);
|
||||
Assert.equals(2, arr[-2]);
|
||||
Assert.equals(1, arr[-3]);
|
||||
}
|
||||
// Kiss arrays can be negatively indexed like Python
|
||||
Assert.equals(3, arr[-1]);
|
||||
Assert.equals(2, arr[-2]);
|
||||
Assert.equals(1, arr[-3]);
|
||||
}
|
||||
|
||||
function testArrayAccess() {
|
||||
Assert.equals(3, BasicTestCase.myArrayLast);
|
||||
}
|
||||
function testArrayAccess() {
|
||||
Assert.equals(3, BasicTestCase.myArrayLast);
|
||||
}
|
||||
|
||||
function testVariadicAdd() {
|
||||
Assert.equals(6, BasicTestCase.mySum);
|
||||
}
|
||||
function testVariadicAdd() {
|
||||
Assert.equals(6, BasicTestCase.mySum);
|
||||
}
|
||||
|
||||
function testVariadicSubtract() {
|
||||
Assert.equals(-2, BasicTestCase.myDifference);
|
||||
}
|
||||
function testVariadicSubtract() {
|
||||
Assert.equals(-2, BasicTestCase.myDifference);
|
||||
}
|
||||
|
||||
function testVariadicMultiply() {
|
||||
Assert.equals(60, BasicTestCase.myProduct);
|
||||
}
|
||||
function testVariadicMultiply() {
|
||||
Assert.equals(60, BasicTestCase.myProduct);
|
||||
}
|
||||
|
||||
function testVariadicDivide() {
|
||||
Assert.equals(0.5, BasicTestCase.myQuotient);
|
||||
}
|
||||
function testVariadicDivide() {
|
||||
Assert.equals(0.5, BasicTestCase.myQuotient);
|
||||
}
|
||||
|
||||
function testMod() {
|
||||
Assert.equals(4, BasicTestCase.myRemainder);
|
||||
}
|
||||
function testMod() {
|
||||
Assert.equals(4, BasicTestCase.myRemainder);
|
||||
}
|
||||
|
||||
function testPow() {
|
||||
Assert.equals(256, BasicTestCase.myPower);
|
||||
}
|
||||
function testPow() {
|
||||
Assert.equals(256, BasicTestCase.myPower);
|
||||
}
|
||||
|
||||
function testUnop() {
|
||||
Assert.equals(7, BasicTestCase.myInc);
|
||||
}
|
||||
function testUnop() {
|
||||
Assert.equals(7, BasicTestCase.myInc);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user