refactor: FieldForms.hx
This commit is contained in:
102
src/kiss/FieldForms.hx
Normal file
102
src/kiss/FieldForms.hx
Normal 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()
|
||||
};
|
||||
}
|
||||
}
|
@@ -4,6 +4,7 @@ import haxe.macro.Context;
|
||||
import haxe.macro.Expr;
|
||||
import kiss.Stream;
|
||||
import kiss.Reader;
|
||||
import kiss.FieldForms;
|
||||
|
||||
class Kiss {
|
||||
/**
|
||||
@@ -14,6 +15,9 @@ class Kiss {
|
||||
|
||||
var stream = new Stream(kissFile);
|
||||
var reader = new Reader();
|
||||
|
||||
var fieldForms = FieldForms.builtins();
|
||||
|
||||
while (true) {
|
||||
stream.dropWhitespace();
|
||||
if (stream.isEmpty())
|
||||
@@ -26,7 +30,7 @@ class Kiss {
|
||||
// The last expression might be a comment, in which case None will be returned
|
||||
switch (nextExp) {
|
||||
case Some(nextExp):
|
||||
classFields.push(readerExpToField(nextExp, position));
|
||||
classFields.push(readerExpToField(nextExp, position, fieldForms));
|
||||
case None:
|
||||
stream.dropWhitespace(); // If there was a comment, drop whitespace that comes after
|
||||
}
|
||||
@@ -35,58 +39,12 @@ class Kiss {
|
||||
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) {
|
||||
case Call(Symbol("defvar"), args) if (args.length == 2):
|
||||
{
|
||||
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()
|
||||
};
|
||||
case Call(Symbol(formName), args) if (fieldForms.exists(formName)):
|
||||
fieldForms[formName](position, args, readerExpToHaxeExpr);
|
||||
default:
|
||||
throw '$exp at $position is not a valid defvar or defun expression';
|
||||
throw '$exp at $position is not a valid field form';
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -20,4 +20,12 @@ class BasicTestCase extends Test {
|
||||
function testFuncall() {
|
||||
Assert.equals(7, BasicTestCase.funResult);
|
||||
}
|
||||
|
||||
function testField() {
|
||||
Assert.equals(5, new BasicTestCase().myField);
|
||||
}
|
||||
|
||||
function testMethod() {
|
||||
Assert.equals(5, new BasicTestCase().myMethod());
|
||||
}
|
||||
}
|
||||
|
@@ -7,4 +7,10 @@
|
||||
// funcalls can use dot access
|
||||
(Math.floor num))
|
||||
// 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)
|
Reference in New Issue
Block a user