break and continue statements
both break and continue work on the innermost loop structure
they work by pushing start/end loop targets to the stack,
if we encounter a break we pop both and use the end statement
if we encounter a continue we peek at the start statement
&& and || short circuiting
EXAMPLE
"if(a && b)" in this case, if a is false, b will not be tested and the if will be exited
"if(a || b)" in this case, if a is true, b will not be tested and the if will be exited
Stack Machine Bytecode of how this works.... (for an && statement)
-----------------------------------------
load_var a ;expression 1 code goes here
if_zero_goto label1
load_var b ;expression 2 code goes here
if_zero_goto label1
;; once we get here, both values are true
load_const 1
goto label2
_label1:
load_const 0
_label2:
;we load constants to the stack for further expression handling
Stack Machine Bytecode of how this works.... (for an || statement)
-----------------------------------------
load_var a ;expression 1 code goes here
if_not_zero_goto label1
load_var b ;expression 2 code goes here
if_not_zero_goto label1
;; once we get here, both values are true
load_const 0
goto label2
_label1:
load_const 1
_label2:
;we load constants to the stack for further expression handling
+= -= /= *=
We just expand these to slightly simpler expressions
+=
lvalue = lvalue + (expression)
-=
lvalue = lvalue - (expression)
/=
lvalue = lvalue / (expression)
*=
lvalue = lvalue * (expression)
c = 2 & 1
c is 0, since the bitwise and of 10b and 01b is 0
c = 2 && 1
c is 1, since neither value equals zero, which is what boolean (logical) and does
The Pixie wrote:I like the sound of this. += etc. would be a great addition to Quest, and I would prefer == for equality as it would be the same as every other langiage I use, and I would feel more confident return (a == b) was returning a boolean, rather than b.
The Pixie wrote:I would want to have some operator for raising to the power; it occasionally useful.
The Pixie wrote:Would a translation program be possible? It would mean maintaining two versions of libraries.
Basic rules for updating (subject to tweaking of course):
change every = to == unless it is by itself on a line and the lvalue is an identifier
change every and outside of an if statement into an '&'
change every or outside of an if statement into an '|'
change every ^ to whatever syntax we choose
change every xor to ^
The Pixie wrote:Would it be possible to update flee to use both the new and old syntax as a first step?
Alex wrote:I would like to keep the syntax as similar as possible, otherwise there will be problems when people upgrade their games from an older version of Quest.
Alex wrote:In any case I would like to keep FLEE for older game versions. We should use a factory to create Expression objects, then games with WorldModelVersion <= v530 (for example) could use FLEE and newer games can use the new expression evaluator. (This would be a change for v5.4 or later as v5.3 is big enough as it is)
Alex wrote:We do some amount of pre-processing of expressions anyway, which enables us to handle things like spaces in object names. This means even if the expression evaluator requires double-equals, we can keep the Quest syntax using single-equals, and simply convert internally when the expression is loaded. I prefer single-equals for Quest as the language is designed to be easy for beginners, and it seems rare that one would want to use assignment in the middle of an expression.
Alex wrote:Does NCalc give us control over object dot notation? That has always been a bit of a pain with FLEE and the current workaround is a bit cumbersome.
Alex wrote:It would be worthwhile doing a performance comparison, as of course the expression evaluator is used very heavily.
Chase wrote:"Alex"
I would like to keep the syntax as similar as possible, otherwise there will be problems when people upgrade their games from an older version of Quest.
I would like a version that has a more advanced syntax, such as a library writer wants to be able to do b = 6 * ( a-= 4) or something. Not sure if a hidden toggle would be viable. Such as a scripting attribute on the asl tag. It would mean it would open the platform to more serious programmers who wouldn't want to be burdened by the restrictions of a simpler syntax.
Alex wrote:We do some amount of pre-processing of expressions anyway, which enables us to handle things like spaces in object names. This means even if the expression evaluator requires double-equals, we can keep the Quest syntax using single-equals, and simply convert internally when the expression is loaded. I prefer single-equals for Quest as the language is designed to be easy for beginners, and it seems rare that one would want to use assignment in the middle of an expression.
It is only rare for beginners. It can save a great deal of effort in some routines. See the first point.
As for spaces in identifiers. NCalc might be able to be modified to allow that natively, since it only handles the expressions. Since spaces are only whitespace, if it sees (identifier) (some amount of spaces) (identifier) it would know it is all one identifier. However identifiers starting or ending with spaces would be right out.
Alex wrote:Does NCalc give us control over object dot notation? That has always been a bit of a pain with FLEE and the current workaround is a bit cumbersome.
NCalc has no idea what dot notation is. Adding it however is not a problem and actually easily done. It could even be done in the parser. Could have it directly ask Quest.. "What is 'player' 'parent' 'name'". or it could just pass the 'player.parent.name' variable request directly to quest.
Alex wrote:It would be worthwhile doing a performance comparison, as of course the expression evaluator is used very heavily.
Well it depends if you currently cache the bytecode results from FLEE, or if FLEE does it automatically. NCalc has a built in cache system for post parsed expressions, but doesn't use IL, but rather tree evaluation. This would only likely effect repeat evaluation, since the slowest part of any expression evaluator is its parsing. Antlr isn't known for it's speed, but it is likely as fast as any other LL parser (Grammatica).
If IL is a required part of it (like for say, the Web Player scripting, which couldn't handle parsing trees very easily), adding it to NCalc is very easy, as it already tree walks.
Alex wrote:
I suppose what I really mean is that the new syntax should add to, rather than change, the existing expression syntax. It will be confusing if people have to change "=" to "==" with a new version for example. I can see the argument for adding an attribute to the asl tag, but I think this will cause confusion - there would have to be two different versions for each code sample that somebody posted to the forum, for example.
I can see that using -= and += in an expression can be useful, and this could be added while retaining full compatibility with existing expressions. So I'd be fine with that. I don't see the point of using single-equals for assignment within an expression though, so I think we can keep using single-equals for equality.
Alex wrote:Sounds good. The only thing to watch out for are operators such as "and" and "or". Quest joins words together as part of a single identifier, except where one of the words is one of these keywords. Not sure how easy it would be for NCalc to take that into account. It also means we have to be careful about adding new operators - although I think something like "pow" is unlikely to cause problems. But we should prefer symbols as far as possible.
Alex wrote:
We compile with FLEE the first time an expression is evaluated, and recompile if any of the types in the expression have changed. This applies to both desktop and WebPlayer as the back-end is exactly the same (web-based games run on the server - only UI stuff happens on the client).
Alex wrote:I guess we will only really know once there is an NCalc version of the code, then some performance profiling can be done.
[a] = 4 => 4
2 * (3 + 5) => 16
2 * (2*(2*(2+1))) => 24
10 % 3 => 1
false or not (false and true) => True
3 > 2 and 1 <= (3-2) => True
3 % 2 != 10 % 3 => 1
3 and 4 => True
[age] => 14
[ no really and what be or age? ] => 24
[age] >= 18 => False
if( [age] >= 18, 'majeur', 'mineur') => mineur
if( [age]+6 >= 18, 'majeur', 'mineur') => majeur
3 % 2 != 10 % 3 => 1
Abs
Acos
Asin
Atan
Ceiling
Cos
Exp
Floor
IEEERemainder
Log
Log10
Pow
Round
Sign
Sin
Sqrt
Tan
Truncate
Max
Min
if
in
3 % 2 => 1
10 % 3 => 1
3 % 2 == 10 % 3 => True
3 % 2 <> 10 % 3 => False
public T Execute(Context c)
{
NCalc.Expression expr = new NCalc.Expression(m_expression);
expr.EvaluateFunction += m_scriptContext.EvaluateFunctionHandler;
expr.EvaluateVariable += m_scriptContext.EvaluateVariableHandler;
object obj = expr.Evaluate();
//this will likely not work, but lets do it anyway
return (T)obj;
}
public object Execute(Context c)
{
NCalc.Expression expr = new NCalc.Expression(m_expression);
expr.EvaluateFunction += m_scriptContext.EvaluateFunctionHandler;
expr.EvaluateVariable += m_scriptContext.EvaluateVariableHandler;
//seems sane enough
return expr.Evaluate();
}
/**
* Connector for NCalc function calls.
*/
private void FunctionHandler(string name, FunctionArgs args)
{
Element proc = m_worldModel.Procedure(name);
Parameters parameters = new Parameters();
int cnt = 0;
// TO DO: Check number of parameters matches
foreach (object val in args.EvaluateParameters())
parameters.Add((string)proc.Fields[FieldDefinitions.ParamNames][cnt++], val);
args.Result = m_worldModel.RunProcedure(name, parameters, true);
}
/**
* Connector for NCalc variable calls.
*/
private void VariableHandler(string name, VariableArgs args)
{
if (args.IsAssignment)
{
//This will probably not work, but not sure how quest handles this yet
m_context.Parameters[name] = args.Result;
}
else
{
args.Result = ResolveVariable(name);
}
}
fragment IdentFragment
: Letter (Letter | Digit)*
;
Ident
: IdentFragment+ ('.' IdentFragment+)*
;