defvar
This commit is contained in:
4
build.hxml
Normal file
4
build.hxml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
-cp src
|
||||||
|
--macro nullSafety("hiss", Strict)
|
||||||
|
--main hiss.Main
|
||||||
|
--interp
|
||||||
65
src/hiss/Hiss.hx
Normal file
65
src/hiss/Hiss.hx
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package hiss;
|
||||||
|
|
||||||
|
import haxe.macro.Context;
|
||||||
|
import haxe.macro.Expr;
|
||||||
|
import hiss.Stream;
|
||||||
|
import hiss.Reader;
|
||||||
|
|
||||||
|
class Hiss {
|
||||||
|
/**
|
||||||
|
Build a Haxe class from a corresponding .hiss file
|
||||||
|
**/
|
||||||
|
macro static public function build(hissFile:String):Array<Field> {
|
||||||
|
var classFields = Context.getBuildFields();
|
||||||
|
|
||||||
|
var stream = new Stream(hissFile);
|
||||||
|
var reader = new Reader();
|
||||||
|
while (!stream.isEmpty()) {
|
||||||
|
var position = stream.position();
|
||||||
|
var nextExp = reader.read(stream);
|
||||||
|
trace(nextExp);
|
||||||
|
// The last expression might be a comment, in which case None will be returned
|
||||||
|
switch (nextExp) {
|
||||||
|
case Some(nextExp):
|
||||||
|
classFields.push(readerExpToField(nextExp, position));
|
||||||
|
case None:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return classFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function readerExpToField(exp:ReaderExp, position:String):Field {
|
||||||
|
switch (exp) {
|
||||||
|
case Call(Symbol("defvar"), args) if (args.length == 2):
|
||||||
|
return {
|
||||||
|
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()
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
throw '$exp at $position is not a valid defvar or defun expression';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static function readerExpToHaxeExpr(exp:ReaderExp):Expr {
|
||||||
|
return switch (exp) {
|
||||||
|
case Symbol(name):
|
||||||
|
Context.parse(name, Context.currentPos());
|
||||||
|
case Str(s):
|
||||||
|
return {
|
||||||
|
pos: Context.currentPos(),
|
||||||
|
expr: EConst(CString(s))
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
throw 'cannot convert $exp yet';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/hiss/Main.hiss
Normal file
1
src/hiss/Main.hiss
Normal file
@@ -0,0 +1 @@
|
|||||||
|
(defvar message "Hello, world!")
|
||||||
8
src/hiss/Main.hx
Normal file
8
src/hiss/Main.hx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package hiss;
|
||||||
|
|
||||||
|
@:build(hiss.Hiss.build("src/hiss/Main.hiss"))
|
||||||
|
class Main {
|
||||||
|
public static function main() {
|
||||||
|
trace(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
68
src/hiss/Reader.hx
Normal file
68
src/hiss/Reader.hx
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package hiss;
|
||||||
|
|
||||||
|
import haxe.ds.Option;
|
||||||
|
import hiss.Stream;
|
||||||
|
|
||||||
|
enum ReaderExp {
|
||||||
|
Call(func:ReaderExp, args:Array<ReaderExp>); // (f a1 a2...)
|
||||||
|
List(exps:Array<ReaderExp>); // [v1 v2 v3]
|
||||||
|
Str(s:String);
|
||||||
|
Symbol(name:String); // s
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef ReadFunction = (Stream) -> Null<ReaderExp>;
|
||||||
|
|
||||||
|
class Reader {
|
||||||
|
var readTable:Map<String, ReadFunction> = new Map();
|
||||||
|
|
||||||
|
public function new() {
|
||||||
|
readTable["("] = (stream) -> Call(assertRead(stream), readExpArray(stream, ")"));
|
||||||
|
readTable["["] = (stream) -> List(readExpArray(stream, "]"));
|
||||||
|
readTable["\""] = (stream) -> Str(stream.expect("closing \"", () -> stream.takeUntilAndDrop("\"")));
|
||||||
|
readTable["/*"] = (stream) -> {
|
||||||
|
stream.dropUntil("*/");
|
||||||
|
null;
|
||||||
|
};
|
||||||
|
readTable["//"] = (stream) -> {
|
||||||
|
stream.dropUntil("\n");
|
||||||
|
null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function assertRead(stream:Stream):ReaderExp {
|
||||||
|
var position = stream.position();
|
||||||
|
return switch (read(stream)) {
|
||||||
|
case Some(exp):
|
||||||
|
exp;
|
||||||
|
case None:
|
||||||
|
throw "There were no expressions left in the stream at $position";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function read(stream:Stream):Option<ReaderExp> {
|
||||||
|
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 None;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Some(Symbol(stream.expect("a symbol name", () -> stream.takeUntilOneOf([")", "]", "/*", "\n", " "]))));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function readExpArray(stream:Stream, end:String):Array<ReaderExp> {
|
||||||
|
var array = [];
|
||||||
|
while (stream.expect('$end to terminate list', () -> stream.peekChars(end.length)) != end) {
|
||||||
|
stream.dropWhitespace();
|
||||||
|
array.push(assertRead(stream));
|
||||||
|
}
|
||||||
|
stream.dropString(end);
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
}
|
||||||
105
src/hiss/Stream.hx
Normal file
105
src/hiss/Stream.hx
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
package hiss;
|
||||||
|
|
||||||
|
import sys.io.File;
|
||||||
|
import haxe.ds.Option;
|
||||||
|
|
||||||
|
using StringTools;
|
||||||
|
using Lambda;
|
||||||
|
|
||||||
|
class Stream {
|
||||||
|
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', '');
|
||||||
|
|
||||||
|
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 isEmpty() {
|
||||||
|
return content.length == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 dropUntil(s:String) {
|
||||||
|
dropChars(content.indexOf(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
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 takeUntilAndDrop(s:String):Option<String> {
|
||||||
|
var idx = content.indexOf(s);
|
||||||
|
|
||||||
|
if (idx < 0)
|
||||||
|
return None;
|
||||||
|
|
||||||
|
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';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user