refactor: FieldForms.hx

This commit is contained in:
2020-11-13 13:55:32 -07:00
parent 8dfeed797a
commit 60a4b10ca7
4 changed files with 126 additions and 52 deletions

102
src/kiss/FieldForms.hx Normal file
View File

@@ -0,0 +1,102 @@
package kiss;
import haxe.macro.Expr;
import haxe.macro.Context;
import kiss.Reader;
using StringTools;
// Fields usually are set to expressions, so a ReaderExp conversion function
// needs to be passed to field forms
typedef ExprConversion = (ReaderExp) -> Expr;
// Field forms convert Kiss reader expressions into Haxe macro class fields
typedef FieldFormFunction = (position:String, args:Array<ReaderExp>, convert:ExprConversion) -> Field;
class FieldForms {
public static function builtins() {
var map:Map<String, FieldFormFunction> = [];
map["defvar"] = varOrProperty.bind("defvar");
map["defprop"] = varOrProperty.bind("defprop");
map["defun"] = funcOrMethod.bind("defun");
map["defmethod"] = funcOrMethod.bind("defmethod");
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 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';
}
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()
};
}
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);
return {
name: name,
access: access,
kind: FFun({
args: switch (args[1]) {
case List(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(Call(Symbol("begin"), args.slice(2))))
}
}),
pos: Context.currentPos()
};
}
}

View File

@@ -4,6 +4,7 @@ import haxe.macro.Context;
import haxe.macro.Expr; import haxe.macro.Expr;
import kiss.Stream; import kiss.Stream;
import kiss.Reader; import kiss.Reader;
import kiss.FieldForms;
class Kiss { class Kiss {
/** /**
@@ -14,6 +15,9 @@ class Kiss {
var stream = new Stream(kissFile); var stream = new Stream(kissFile);
var reader = new Reader(); var reader = new Reader();
var fieldForms = FieldForms.builtins();
while (true) { while (true) {
stream.dropWhitespace(); stream.dropWhitespace();
if (stream.isEmpty()) if (stream.isEmpty())
@@ -26,7 +30,7 @@ class Kiss {
// 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):
classFields.push(readerExpToField(nextExp, position)); classFields.push(readerExpToField(nextExp, position, fieldForms));
case None: case None:
stream.dropWhitespace(); // If there was a comment, drop whitespace that comes after stream.dropWhitespace(); // If there was a comment, drop whitespace that comes after
} }
@@ -35,58 +39,12 @@ class Kiss {
return classFields; return classFields;
} }
static function readerExpToField(exp:ReaderExp, position:String):Field { static function readerExpToField(exp:ReaderExp, position:String, fieldForms:Map<String, FieldFormFunction>):Field {
return switch (exp) { return switch (exp) {
case Call(Symbol("defvar"), args) if (args.length == 2): case Call(Symbol(formName), args) if (fieldForms.exists(formName)):
{ fieldForms[formName](position, args, readerExpToHaxeExpr);
name: switch (args[0]) {
case Symbol(name):
name;
default:
throw 'The first argument to defvar at $position should be a variable name';
},
access: [APublic, AStatic],
kind: FVar(null, // TODO allow type anotations
readerExpToHaxeExpr(args[1])),
pos: Context.currentPos()
};
case Call(Symbol("defun"), args) if (args.length > 2):
{
name: switch (args[0]) {
case Symbol(name):
name;
default:
throw 'The first argument to defun at $position should be a function name';
},
access: [APublic, AStatic],
kind: FFun({
args: switch (args[1]) {
case List(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(readerExpToHaxeExpr(Call(Symbol("begin"), args.slice(2))))
}
}),
pos: Context.currentPos()
};
default: default:
throw '$exp at $position is not a valid defvar or defun expression'; throw '$exp at $position is not a valid field form';
}; };
} }

View File

@@ -20,4 +20,12 @@ class BasicTestCase extends Test {
function testFuncall() { function testFuncall() {
Assert.equals(7, BasicTestCase.funResult); Assert.equals(7, BasicTestCase.funResult);
} }
function testField() {
Assert.equals(5, new BasicTestCase().myField);
}
function testMethod() {
Assert.equals(5, new BasicTestCase().myMethod());
}
} }

View File

@@ -7,4 +7,10 @@
// funcalls can use dot access // funcalls can use dot access
(Math.floor num)) (Math.floor num))
// functions are resolved in the macro context // functions are resolved in the macro context
(defvar funResult (myFloor 7.5)) (defvar funResult (myFloor 7.5))
// (defprop) declares instance variables
(defprop myField 5)
// (defmethod) declares instance methods
(defmethod myMethod [] this.myField)