mirror of https://github.com/adamdruppe/arsd.git
script macro
This commit is contained in:
parent
9c04174573
commit
bb4619715d
29
jsvar.d
29
jsvar.d
|
@ -688,6 +688,11 @@ struct var {
|
|||
if(this.payloadType() == Type.Function) {
|
||||
assert(this._payload._function !is null);
|
||||
return this._payload._function(_this, args);
|
||||
} else if(this.payloadType() == Type.Object) {
|
||||
assert(this._payload._object !is null);
|
||||
var* operator = this._payload._object._peekMember("opCall", true);
|
||||
if(operator !is null && operator._type == Type.Function)
|
||||
return operator.apply(_this, args);
|
||||
}
|
||||
|
||||
version(jsvar_throw)
|
||||
|
@ -1075,13 +1080,19 @@ struct var {
|
|||
return v;
|
||||
}
|
||||
|
||||
@property PrototypeObject prototypeObject() {
|
||||
var v = prototype();
|
||||
if(v._type == Type.Object)
|
||||
return v._payload._object;
|
||||
return null;
|
||||
}
|
||||
|
||||
// what I call prototype is more like what Mozilla calls __proto__, but tbh I think this is better so meh
|
||||
@property ref var prototype() {
|
||||
static var _arrayPrototype;
|
||||
static var _functionPrototype;
|
||||
static var _stringPrototype;
|
||||
|
||||
|
||||
final switch(payloadType()) {
|
||||
case Type.Array:
|
||||
assert(_arrayPrototype._type == Type.Object);
|
||||
|
@ -1100,7 +1111,20 @@ struct var {
|
|||
case Type.String:
|
||||
assert(_stringPrototype._type == Type.Object);
|
||||
if(_stringPrototype._payload._object is null) {
|
||||
_stringPrototype._object = new PrototypeObject();
|
||||
auto p = new PrototypeObject();
|
||||
_stringPrototype._object = p;
|
||||
|
||||
var replaceFunction;
|
||||
replaceFunction._type = Type.Function;
|
||||
replaceFunction._function = (var _this, var[] args) {
|
||||
string s = _this.toString();
|
||||
import std.array : replace;
|
||||
return var(std.array.replace(s,
|
||||
args[0].toString(),
|
||||
args[1].toString()));
|
||||
};
|
||||
|
||||
p._properties["replace"] = replaceFunction;
|
||||
}
|
||||
|
||||
return _stringPrototype;
|
||||
|
@ -1115,6 +1139,7 @@ struct var {
|
|||
// these types don't have prototypes
|
||||
}
|
||||
|
||||
|
||||
var* v = new var(null);
|
||||
return *v;
|
||||
}
|
||||
|
|
92
script.d
92
script.d
|
@ -1,4 +1,6 @@
|
|||
/**
|
||||
FIXME: Also ability to get source code for function something so you can mixin.
|
||||
|
||||
Script features:
|
||||
|
||||
FIXME: add COM support on Windows
|
||||
|
@ -104,6 +106,11 @@
|
|||
args can say var if you want, but don't have to
|
||||
default arguments supported in any position
|
||||
when calling, you can use the default keyword to use the default value in any position
|
||||
* macros:
|
||||
A macro is defined just like a function, except with the
|
||||
macro keyword instead of the function keyword. The difference
|
||||
is a macro must interpret its own arguments - it is passed
|
||||
AST objects instead of values. Still a WIP.
|
||||
|
||||
|
||||
|
||||
|
@ -172,7 +179,7 @@ private enum string[] keywords = [
|
|||
"__FILE__", "__LINE__", // these two are special to the lexer
|
||||
"foreach", "json!q{", "default", "finally",
|
||||
"return", "static", "struct", "import", "module", "assert", "switch",
|
||||
"while", "catch", "throw", "scope", "break", "super", "class", "false", "mixin", "super",
|
||||
"while", "catch", "throw", "scope", "break", "super", "class", "false", "mixin", "super", "macro",
|
||||
"auto", // provided as an alias for var right now, may change later
|
||||
"null", "else", "true", "eval", "goto", "enum", "case", "cast",
|
||||
"var", "for", "try", "new",
|
||||
|
@ -412,6 +419,20 @@ TokenStream!TextStream lexScript(TextStream)(TextStream textStream, string scrip
|
|||
return new TokenStream!TextStream(textStream, scriptFilename);
|
||||
}
|
||||
|
||||
class MacroPrototype : PrototypeObject {
|
||||
var func;
|
||||
|
||||
// macros are basically functions that get special treatment for their arguments
|
||||
// they are passed as AST objects instead of interpreted
|
||||
// calling an AST object will interpret it in the script
|
||||
this(var func) {
|
||||
this.func = func;
|
||||
this._properties["opCall"] = (var _this, var[] args) {
|
||||
return func.apply(_this, args);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
struct InterpretResult {
|
||||
var value;
|
||||
PrototypeObject sc;
|
||||
|
@ -422,6 +443,26 @@ struct InterpretResult {
|
|||
|
||||
class Expression {
|
||||
abstract InterpretResult interpret(PrototypeObject sc);
|
||||
|
||||
// this returns an AST object that can be inspected and possibly altered
|
||||
// by the script. Calling the returned object will interpret the object in
|
||||
// the original scope passed
|
||||
var toScriptExpressionObject(PrototypeObject sc) {
|
||||
var obj = var.emptyObject;
|
||||
|
||||
obj["type"] = typeid(this).name;
|
||||
obj["toString"] = (var _this, var[] args) {
|
||||
return var(this.toString());
|
||||
};
|
||||
obj["opCall"] = (var _this, var[] args) {
|
||||
Expression e = this;
|
||||
// FIXME: if they changed the properties in the
|
||||
// script, we should update them here too.
|
||||
return e.interpret(sc).value;
|
||||
};
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
class MixinExpression : Expression {
|
||||
|
@ -608,7 +649,7 @@ class FunctionLiteralExpression : Expression {
|
|||
}
|
||||
|
||||
override string toString() {
|
||||
string s = "function (";
|
||||
string s = (isMacro ? "macro" : "function") ~ " (";
|
||||
s ~= arguments.toString();
|
||||
|
||||
s ~= ") ";
|
||||
|
@ -631,6 +672,8 @@ class FunctionLiteralExpression : Expression {
|
|||
|
||||
PrototypeObject lexicalScope;
|
||||
|
||||
bool isMacro;
|
||||
|
||||
override InterpretResult interpret(PrototypeObject sc) {
|
||||
assert(DefaultArgumentDummyObject !is null);
|
||||
var v;
|
||||
|
@ -666,6 +709,11 @@ class FunctionLiteralExpression : Expression {
|
|||
assert(0);
|
||||
}
|
||||
};
|
||||
if(isMacro) {
|
||||
var n = var.emptyObject;
|
||||
n._object = new MacroPrototype(v);
|
||||
v = n;
|
||||
}
|
||||
return InterpretResult(v, sc);
|
||||
}
|
||||
}
|
||||
|
@ -1144,6 +1192,22 @@ class ForExpression : Expression {
|
|||
|
||||
return InterpretResult(result, sc, flowControl);
|
||||
}
|
||||
|
||||
override string toString() {
|
||||
string code = "for(";
|
||||
if(initialization !is null)
|
||||
code ~= initialization.toString();
|
||||
code ~= "; ";
|
||||
if(condition !is null)
|
||||
code ~= condition.toString();
|
||||
code ~= "; ";
|
||||
if(advancement !is null)
|
||||
code ~= advancement.toString();
|
||||
code ~= ") ";
|
||||
code ~= loopBody.toString();
|
||||
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
class IfExpression : Expression {
|
||||
|
@ -1169,6 +1233,19 @@ class IfExpression : Expression {
|
|||
}
|
||||
return InterpretResult(result.value, sc, result.flowControl);
|
||||
}
|
||||
|
||||
override string toString() {
|
||||
string code = "if(";
|
||||
code ~= condition.toString();
|
||||
code ~= ") ";
|
||||
if(ifTrue !is null)
|
||||
code ~= ifTrue.toString();
|
||||
else
|
||||
code ~= " { }";
|
||||
if(ifFalse !is null)
|
||||
code ~= " else " ~ ifFalse.toString();
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
// this is kinda like a placement new, and currently isn't exposed inside the language,
|
||||
|
@ -1320,10 +1397,14 @@ class CallExpression : Expression {
|
|||
|
||||
override InterpretResult interpret(PrototypeObject sc) {
|
||||
auto f = func.interpret(sc).value;
|
||||
bool isMacro = (f.payloadType == var.Type.Object && ((cast(MacroPrototype) f._payload._object) !is null));
|
||||
var[] args;
|
||||
foreach(argument; arguments)
|
||||
if(argument !is null) {
|
||||
args ~= argument.interpret(sc).value;
|
||||
if(isMacro) // macro, pass the argument as an expression object
|
||||
args ~= argument.toScriptExpressionObject(sc);
|
||||
else // regular function, interpret the arguments
|
||||
args ~= argument.interpret(sc).value;
|
||||
} else {
|
||||
if(DefaultArgumentDummyObject is null)
|
||||
DefaultArgumentDummyObject = new PrototypeObject();
|
||||
|
@ -1471,6 +1552,7 @@ Expression parsePart(MyTokenStreamHere)(ref MyTokenStreamHere tokens) {
|
|||
obj.elements[key.str] = value;
|
||||
|
||||
goto moreKeys;
|
||||
case "macro":
|
||||
case "function":
|
||||
tokens.requireNextToken(ScriptToken.Type.symbol, "(");
|
||||
|
||||
|
@ -1481,6 +1563,7 @@ Expression parsePart(MyTokenStreamHere)(ref MyTokenStreamHere tokens) {
|
|||
tokens.requireNextToken(ScriptToken.Type.symbol, ")");
|
||||
|
||||
exp.functionBody = parseExpression(tokens);
|
||||
exp.isMacro = token.str == "macro";
|
||||
|
||||
e = exp;
|
||||
break;
|
||||
|
@ -2066,6 +2149,7 @@ Expression parseStatement(MyTokenStreamHere)(ref MyTokenStreamHere tokens, strin
|
|||
goto skip;
|
||||
// literals
|
||||
case "function":
|
||||
case "macro":
|
||||
// function can be a literal, or a declaration.
|
||||
|
||||
tokens.popFront(); // we're peeking ahead
|
||||
|
@ -2091,6 +2175,8 @@ Expression parseStatement(MyTokenStreamHere)(ref MyTokenStreamHere tokens, strin
|
|||
e.identifiers ~= ident.str;
|
||||
e.initializers ~= exp;
|
||||
|
||||
exp.isMacro = token.str == "macro";
|
||||
|
||||
return e;
|
||||
|
||||
} else {
|
||||
|
|
Loading…
Reference in New Issue