Skip to content

Advanced preprocessor features #8

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
322 changes: 230 additions & 92 deletions hscript/Parser.hx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
* DEALINGS IN THE SOFTWARE.
*/
package hscript;

import hscript.thx_semver.Version;
import hscript.Expr;

enum Token {
Expand Down Expand Up @@ -62,6 +64,11 @@ class Parser {
**/
public var preprocessorValues : Map<String,Dynamic> = new Map();

/**
defines preprocessor variables that will be considered false
**/
public var invalidPreprocessValues : Array<Dynamic> = [null, "0", 0, false];

/**
activate JSON compatibility
**/
Expand Down Expand Up @@ -195,6 +202,10 @@ class Parser {
push(tk);
parseFullExpr(a);
}
if(preprocStack.length > 0)
{
error(EInvalidPreprocessor("Unclosed"), tokenMin, tokenMax);
}
return if( a.length == 1 ) a[0] else mk(EBlock(a),0);
}

Expand Down Expand Up @@ -1346,6 +1357,10 @@ class Parser {
push(tk);
decls.push(parseModuleDecl());
}
if(preprocStack.length > 0)
{
error(EInvalidPreprocessor("Unclosed"), tokenMin, tokenMax);
}
return decls;
}

Expand Down Expand Up @@ -1908,93 +1923,216 @@ class Parser {

var preprocStack : Array<{ r : Bool }>;

function parsePreproCond() {
function parsePreproCond():Expr {
var tk = token();
return switch( tk ) {
case TPOpen:
push(TPOpen);
parseExpr();
case TId(id):
while(true) {
var tk = token();
if(tk == TDot) {
id += ".";
switch( tk ) {
case TPOpen:
push(TPOpen);
return parseExpr();
case TId(id):
var tk;
while(true) {
tk = token();
switch(tk) {
case TId(id2):
id += id2;
default: unexpected(tk);
if(tk == TDot) {
id += ".";
tk = token();
switch(tk) {
case TId(id2):
id += id2;
default: unexpected(tk);
}
} else {
push(tk);
break;
}
} else {
push(tk);
break;
}
}
mk(EIdent(id), tokenMin, tokenMax);
case TOp("!"):
mk(EUnop("!", true, parsePreproCond()), tokenMin, tokenMax);
default:
unexpected(tk);
return mk(EIdent(id), tokenMin, tokenMax);
case TConst(c):
return mk(EConst(c), tokenMin, tokenMax);
case TOp("!"):
return mk(EUnop("!", true, parsePreproCond()), tokenMin, tokenMax);
default:
return unexpected(tk);
}
}

function evalPreproCond( e : Expr ) {
switch( expr(e) ) {
case EIdent(id):
return preprocValue(id) != null;
case EField(e2, f):
switch(expr(e2)) {
case EIdent(id):
return preprocValue(id + "." + f) != null;
default:
error(EInvalidPreprocessor("Can't eval " + expr(e).getName() + " with " + expr(e2).getName()), readPos, readPos);
return false;
}
case EUnop("!", _, e):
return !evalPreproCond(e);
case EParent(e):
return evalPreproCond(e);
case EBinop("&&", e1, e2):
return evalPreproCond(e1) && evalPreproCond(e2);
case EBinop("||", e1, e2):
return evalPreproCond(e1) || evalPreproCond(e2);
default:
error(EInvalidPreprocessor("Can't eval " + expr(e).getName()), readPos, readPos);
return false;
function getValFromPreproExpr( e : Expr ) : Dynamic
{
var edef:ExprDef = expr(e);
switch( edef )
{
case EParent(e):
return getValFromPreproExpr(e);
case EIdent(id):
return preprocValue(id);
case EConst(c):
return switch (c) {
case CInt(v): v;
case CFloat(f): f;
case CString(s): s;
}
case ECall(expr(_) => EIdent("version"), expr(_[0]) => EConst(CString(s))):
try
{
(s : Version);
}
catch(_)
{
error(EInvalidPreprocessor('Invalid version string $s. Should follow SemVer.'), readPos, readPos);
}
return s;

default:
error(EInvalidPreprocessor(edef.getName()), readPos, readPos);
return null;
}
}

function evalPreproCond( e : Expr ):Bool {
var edef:ExprDef = expr(e);
switch( edef ) {
case EConst(c):
return !invalidPreprocessValues.contains(switch (c) {
case CInt(v): v;
case CFloat(f): f;
case CString(s): s;
});
case EIdent(id):
return !invalidPreprocessValues.contains(preprocValue(id));
case EField(e1, f1):
switch(expr(e1)) {
case EIdent(id):
return !invalidPreprocessValues.contains(preprocValue('$id.$f1'));
case edef2:
error(EInvalidPreprocessor("Can't eval " + edef.getName() + " with " + edef2.getName()), readPos, readPos);
return false;
}
case EUnop("!", _, e):
return !evalPreproCond(e);
case EParent(e):
return evalPreproCond(e);
case EBinop(op, e1, e2):
switch (op)
{
case "&&":
return evalPreproCond(e1) && evalPreproCond(e2);
case "||":
return evalPreproCond(e1) || evalPreproCond(e2);
case op:
var e1:Dynamic = getValFromPreproExpr(e1);
var e2:Dynamic = getValFromPreproExpr(e2);
try {
var vers1:Version = Std.string(e1);
var vers2:Version = Std.string(e2);
switch (op) {
case "==":
return vers1 == vers2;
case "!=":
return vers1 != vers2;
case ">=":
return vers1 >= vers2;
case ">":
return vers1 > vers2;
case "<=":
return vers1 <= vers2;
case "<":
return vers1 < vers2;
default:
throw null;
}
} catch(e:String) { // doesn't capture SemVer error
} catch(e) {
error(EInvalidPreprocessor('Invalid operator \'$op\' for SemVer'), readPos, readPos);
return false;
}
try {
switch (op) {
case "==":
return e1 == e2;
case "!=":
return e1 != e2;
case ">=":
return e1 >= e2;
case ">":
return e1 > e2;
case "<=":
return e1 <= e2;
case "<":
return e1 < e2;
default:
}
} catch(_) {
error(EInvalidPreprocessor('Invalid operator \'$op\''), readPos, readPos);
return false;
}
error(EInvalidPreprocessor('Uncorrected operator \'$op\''), readPos, readPos);
return false;
}
default:
error(EInvalidPreprocessor("Can't eval " + edef.getName()), readPos, readPos);
return false;
}
}

var _inIfPrepocess:Bool = false;

function preprocess( id : String ) : Token {
switch( id ) {
case "if":
var e = parsePreproCond();
if( evalPreproCond(e) ) {
preprocStack.push({ r : true });
return token();
inline function returnToken() {
return switch (token()) {
case TPrepro(id = "if" | "else" | "elseif" | "end" | "error"):
preprocess(id);
case t: t;
}
preprocStack.push({ r : false });
skipTokens();
return token();
case "else", "elseif" if( preprocStack.length > 0 ):
if( preprocStack[preprocStack.length - 1].r ) {
preprocStack[preprocStack.length - 1].r = false;
skipTokens();
return token();
} else if( id == "else" ) {
preprocStack.pop();
preprocStack.push({ r : true });
return token();
} else {
// elseif
preprocStack.pop();
return preprocess("if");
}
if ( !_inIfPrepocess ) {
switch( id ) {
case "if":
_inIfPrepocess = true;
var result = evalPreproCond(parsePreproCond());
_inIfPrepocess = false;
preprocStack.push({ r : result });
if(!result) {
skipTokens();
}
return returnToken();
case "else", "elseif":
if ( preprocStack.length > 0 ) {
var last = preprocStack[preprocStack.length - 1];
if( last.r ) {
skipTokens();
last.r = false;
return returnToken();
} else if( id == "else" ) {
preprocStack.pop();
preprocStack.push({ r : true });
return returnToken();
} else {
// elseif
preprocStack.pop();
return preprocess("if");
}
}
else
{
return unexpected(TPrepro(id));
}
case "end":
if( preprocStack.length > 0 )
{
preprocStack.pop();
return returnToken();
}
else
{
return unexpected(TPrepro(id));
}
case "error" if ( preprocStack.length == 0 || preprocStack[preprocStack.length - 1].r):
var tokenMin = tokenMin;
error(ECustom(getValFromPreproExpr(parsePreproCond())), tokenMin,tokenMax);
return returnToken();
}
case "end" if( preprocStack.length > 0 ):
preprocStack.pop();
return token();
default:
return TPrepro(id);
}
return TPrepro(id);
}

function skipTokens() {
Expand All @@ -2003,7 +2141,7 @@ class Parser {
var pos = readPos;
while( true ) {
var tk = token();

if( tk == TEof ) {
if (preprocStack.length != 0) {
error(EInvalidPreprocessor("Unclosed"), pos, pos);
Expand Down Expand Up @@ -2073,24 +2211,24 @@ class Parser {

function tokenString( t ) {
return switch( t ) {
case TEof: "<eof>";
case TConst(c): constString(c);
case TId(s): s;
case TOp(s): s;
case TPOpen: "(";
case TPClose: ")";
case TBrOpen: "{";
case TBrClose: "}";
case TDot: ".";
case TQuestionDot: "?.";
case TComma: ",";
case TSemicolon: ";";
case TBkOpen: "[";
case TBkClose: "]";
case TQuestion: "?";
case TDoubleDot: ":";
case TMeta(id): "@" + id;
case TPrepro(id): "#" + id;
case TEof: "<eof>";
case TConst(c): constString(c);
case TId(s): s;
case TOp(s): s;
case TPOpen: "(";
case TPClose: ")";
case TBrOpen: "{";
case TBrClose: "}";
case TDot: ".";
case TQuestionDot: "?.";
case TComma: ",";
case TSemicolon: ";";
case TBkOpen: "[";
case TBkClose: "]";
case TQuestion: "?";
case TDoubleDot: ":";
case TMeta(id): "@" + id;
case TPrepro(id): "#" + id;
}
}

Expand Down
Loading