From 5db0b5c10de2fc68b273643ee2e8e3f786254dbc Mon Sep 17 00:00:00 2001 From: Tom Holmes Date: Tue, 28 Nov 2017 20:19:13 -0800 Subject: [PATCH 1/4] add identifier to getmember/getmethod opcodes --- src/kOS.Safe.Test/Execution/SimpleTest.cs | 17 +++++++++++ src/kOS.Safe/Compilation/KS/Compiler.cs | 35 +++++++++++------------ src/kOS.Safe/Compilation/Opcode.cs | 22 ++++++++++++-- 3 files changed, 52 insertions(+), 22 deletions(-) diff --git a/src/kOS.Safe.Test/Execution/SimpleTest.cs b/src/kOS.Safe.Test/Execution/SimpleTest.cs index 5e8a7309a7..5120d658fc 100644 --- a/src/kOS.Safe.Test/Execution/SimpleTest.cs +++ b/src/kOS.Safe.Test/Execution/SimpleTest.cs @@ -116,5 +116,22 @@ public void TestSuffixes() "False" ); } + + [Test] + public void TestSuffixes() + { + // Test that various suffix and index combinations work for getting and setting + RunScript("integration/suffixes.ks"); + RunSingleStep(); + RunSingleStep(); + AssertOutput( + "0", + "1", + "2", + "3", + "0", + "False" + ); + } } } diff --git a/src/kOS.Safe/Compilation/KS/Compiler.cs b/src/kOS.Safe/Compilation/KS/Compiler.cs index ac9b406c86..813340ca19 100644 --- a/src/kOS.Safe/Compilation/KS/Compiler.cs +++ b/src/kOS.Safe/Compilation/KS/Compiler.cs @@ -1747,25 +1747,25 @@ private void VisitSuffix(ParseNode node) // it as a variable, else parse it as a raw identifier: bool rememberIsV = identifierIsVariable; identifierIsVariable = (!startsWithFunc) && nodeIndex == 0; - // Push this term on the stack unless it's the name of the user function or built-in function: + + // when we are setting a member value we need to leave + // the last object and the last suffix in the stack + bool usingSetMember = (suffixTerm.Nodes.Count > 0) && (compilingSetDestination && nodeIndex == (node.Nodes.Count - 1)); + + // Push this term on the stack unless it's the name of the user function or built-in function or a suffix: bool isDirect = true; - if ( (!isUserFunc) && (nodeIndex > 0 || !startsWithFunc) ) + if (nodeIndex != 0 && !usingSetMember) { - VisitNode(suffixTerm.Nodes[0]); + string suffixName = GetIdentifierText(suffixTerm.Nodes[0]); + AddOpcode(startsWithFunc ? new OpcodeGetMethod(suffixName) : new OpcodeGetMember(suffixName)); isDirect = false; } - identifierIsVariable = rememberIsV; - if (nodeIndex != 0) + else if (!isUserFunc && (nodeIndex > 0 || !startsWithFunc)) { - // when we are setting a member value we need to leave - // the last object and the last suffix in the stack - bool usingSetMember = (suffixTerm.Nodes.Count > 0) && (compilingSetDestination && nodeIndex == (node.Nodes.Count - 1)); - - if (!usingSetMember) - { - AddOpcode(startsWithFunc ? new OpcodeGetMethod() : new OpcodeGetMember()); - } + VisitNode(suffixTerm.Nodes[0]); + isDirect = false; } + identifierIsVariable = rememberIsV; // The remaining terms are a chain of function_trailers "(...)" and array_trailers "[...]" or "#.." in any arbitrary order: for (int trailerIndex = 1; trailerIndex < suffixTerm.Nodes.Count; ++trailerIndex) @@ -3214,22 +3214,19 @@ private void VisitForStatement(ParseNode node) PushBreakList(braceNestLevel); VisitNode(node.Nodes[3]); - AddOpcode(new OpcodePush("iterator")); - AddOpcode(new OpcodeGetMember()); + AddOpcode(new OpcodeGetMember("iterator")); AddOpcode(new OpcodeStoreLocal(iteratorIdentifier)); // loop condition Opcode condition = AddOpcode(new OpcodePush(iteratorIdentifier)); string conditionLabel = condition.Label; - AddOpcode(new OpcodePush("next")); - AddOpcode(new OpcodeGetMember()); + AddOpcode(new OpcodeGetMember("next")); // branch Opcode branch = AddOpcode(new OpcodeBranchIfFalse()); AddToBreakList(branch); // assign value to iteration variable string varName = "$" + GetIdentifierText(node.Nodes[1]); AddOpcode(new OpcodePush(iteratorIdentifier)); - AddOpcode(new OpcodePush("value")); - AddOpcode(new OpcodeGetMember()); + AddOpcode(new OpcodeGetMember("value")); AddOpcode(new OpcodeStoreLocal(varName)); // instructions in FOR body VisitNode(node.Nodes[4]); diff --git a/src/kOS.Safe/Compilation/Opcode.cs b/src/kOS.Safe/Compilation/Opcode.cs index 3461001012..5c91dba008 100644 --- a/src/kOS.Safe/Compilation/Opcode.cs +++ b/src/kOS.Safe/Compilation/Opcode.cs @@ -713,15 +713,22 @@ public override void Execute(ICpu cpu) } } - public class OpcodeGetMember : Opcode + public class OpcodeGetMember : OpcodeIdentifierBase { protected override string Name { get { return "getmember"; } } public override ByteCode Code { get { return ByteCode.GETMEMBER; } } protected bool IsMethodCallAttempt = false; + public OpcodeGetMember(string identifier) : base(identifier) + { + } + + protected OpcodeGetMember() : base("") + { + } + public override void Execute(ICpu cpu) { - string suffixName = cpu.PopArgumentStack().ToString(); object popValue = cpu.PopValueEncapsulatedArgument(); var specialValue = popValue as ISuffixed; @@ -731,7 +738,7 @@ public override void Execute(ICpu cpu) throw new Exception(string.Format("Values of type {0} cannot have suffixes", popValue.GetType())); } - ISuffixResult result = specialValue.GetSuffix(suffixName); + ISuffixResult result = specialValue.GetSuffix(Identifier); // If the result is a suffix that is still in need of being invoked and hasn't resolved to a value yet: if (result != null && !IsMethodCallAttempt && !result.HasValue) @@ -778,6 +785,15 @@ public class OpcodeGetMethod : OpcodeGetMember { protected override string Name { get { return "getmethod"; } } public override ByteCode Code { get { return ByteCode.GETMETHOD; } } + + public OpcodeGetMethod(string identifier) : base(identifier) + { + } + + protected OpcodeGetMethod() : base("") + { + } + public override void Execute(ICpu cpu) { IsMethodCallAttempt = true; From 5b68b98a9d2090c076089a15880596ede28cbb70 Mon Sep 17 00:00:00 2001 From: Tom Holmes Date: Tue, 28 Nov 2017 20:54:18 -0800 Subject: [PATCH 2/4] Add suffix name to SetMember opcode --- src/kOS.Safe/Compilation/KS/Compiler.cs | 61 ++++++++++++++++++++++++- src/kOS.Safe/Compilation/Opcode.cs | 15 ++++-- 2 files changed, 70 insertions(+), 6 deletions(-) diff --git a/src/kOS.Safe/Compilation/KS/Compiler.cs b/src/kOS.Safe/Compilation/KS/Compiler.cs index 813340ca19..ba8686d4ff 100644 --- a/src/kOS.Safe/Compilation/KS/Compiler.cs +++ b/src/kOS.Safe/Compilation/KS/Compiler.cs @@ -1754,7 +1754,13 @@ private void VisitSuffix(ParseNode node) // Push this term on the stack unless it's the name of the user function or built-in function or a suffix: bool isDirect = true; - if (nodeIndex != 0 && !usingSetMember) + + if (usingSetMember && suffixTerm.Nodes.Count == 1) + { + // If this is the name of a suffix that we are setting, don't do anything with it. + // ProcessSetOperation will handle putting the suffix name into the opcode. + } + else if (nodeIndex != 0 && !usingSetMember) { string suffixName = GetIdentifierText(suffixTerm.Nodes[0]); AddOpcode(startsWithFunc ? new OpcodeGetMethod(suffixName) : new OpcodeGetMember(suffixName)); @@ -2024,6 +2030,49 @@ private bool VarIdentifierEndsWithSuffix(ParseNode node) prevChild.Token.Type == TokenType.suffixterm); } + /// + /// Get the rightmost suffix in a var_identifier. + /// i.e. return "BBB" if the var_identifier is:
+ /// AAA:BBB, or
+ /// AAA[0]:BBB,
+ ///
+ private string GetVarIdentifierEndSuffix(ParseNode node) + { + // If it's a var_identifier being worked on, drop down one level first + // to get into the actual meat of the syntax tree it represents: + if (node.Token.Type == TokenType.varidentifier) + return GetVarIdentifierEndSuffix(node.Nodes.First()); + + // Descend the rightmost children until encountering the deepest node that is + // still a suffix_trailer, array_trailer, or function_trailer. If that node + // was a suffix_trailer, return true, else it's false. + ParseNode prevChild = node; + ParseNode thisChild = node.Nodes.Last(); + + bool descendedThroughAColon = false; // eeeewwww, sounds disgusting. + + while (thisChild.Token.Type == TokenType.suffix_trailer || + thisChild.Token.Type == TokenType.suffix || + thisChild.Token.Type == TokenType.suffixterm || + thisChild.Token.Type == TokenType.suffixterm_trailer || + thisChild.Token.Type == TokenType.array_trailer || + thisChild.Token.Type == TokenType.function_trailer) + { + if (thisChild.Token.Type == TokenType.suffix_trailer) + descendedThroughAColon = true; + prevChild = thisChild; + thisChild = thisChild.Nodes.Last(); + } + if (descendedThroughAColon && + (prevChild.Token.Type == TokenType.suffix_trailer || + prevChild.Token.Type == TokenType.suffixterm)) + { + return GetIdentifierText(prevChild); + } + + throw new KOSYouShouldNeverSeeThisException("VarIdentifier didn't end in a suffix"); + } + /// /// Check for if the rightmost thing in the var_identifier node /// is an array indexer. i.e. return true if the var_identifier is:
@@ -2118,7 +2167,15 @@ private void ProcessSetOperation(ParseNode setThis, ParseNode toThis) // expression VisitNode(toThis); - AddOpcode(isSuffix ? (Opcode)new OpcodeSetMember() : (Opcode)new OpcodeSetIndex()); + if (isSuffix) + { + string suffixName = GetVarIdentifierEndSuffix(setThis); + AddOpcode(new OpcodeSetMember(suffixName)); + } + else + { + AddOpcode(new OpcodeSetIndex()); + } } else { diff --git a/src/kOS.Safe/Compilation/Opcode.cs b/src/kOS.Safe/Compilation/Opcode.cs index 5c91dba008..c9a7e6e044 100644 --- a/src/kOS.Safe/Compilation/Opcode.cs +++ b/src/kOS.Safe/Compilation/Opcode.cs @@ -802,15 +802,22 @@ public override void Execute(ICpu cpu) } - public class OpcodeSetMember : Opcode + public class OpcodeSetMember : OpcodeIdentifierBase { protected override string Name { get { return "setmember"; } } public override ByteCode Code { get { return ByteCode.SETMEMBER; } } + public OpcodeSetMember(string identifier) : base(identifier) + { + } + + protected OpcodeSetMember() : base("") + { + } + public override void Execute(ICpu cpu) { Structure value = cpu.PopStructureEncapsulatedArgument(); // new value to set it to - string suffixName = cpu.PopArgumentStack().ToString(); // name of suffix being set Structure popValue = cpu.PopStructureEncapsulatedArgument(); // object to which the suffix is attached. // We aren't converting the popValue to a Scalar, Boolean, or String structure here because @@ -827,9 +834,9 @@ public override void Execute(ICpu cpu) // TODO: When we refactor to make every structure use the new suffix style, this conversion // to primative can be removed. Right now there are too many structures that override the // SetSuffix method while relying on unboxing the object rahter than using Convert - if (!specialValue.SetSuffix(suffixName, Structure.ToPrimitive(value))) + if (!specialValue.SetSuffix(Identifier, Structure.ToPrimitive(value))) { - throw new Exception(string.Format("Suffix {0} not found on object", suffixName)); + throw new Exception(string.Format("Suffix {0} not found on object", Identifier)); } } } From b330967cef6290409f0376c3375cc104400610eb Mon Sep 17 00:00:00 2001 From: Tom Holmes Date: Fri, 1 Dec 2017 00:18:26 -0800 Subject: [PATCH 3/4] Build and compile expressions as semantic tree nodes --- kerboscript_tests/integration/func.ks | 8 +- .../integration/short_circuit.ks | 15 + src/kOS.Safe.Test/Execution/SimpleTest.cs | 28 +- src/kOS.Safe/Compilation/KS/Compiler.cs | 1500 +++++------------ .../Compilation/KS/ExpressionBuilder.cs | 370 ++++ src/kOS.Safe/Compilation/KS/ExpressionNode.cs | 234 +++ .../Compilation/KS/IExpressionVisitor.cs | 29 + src/kOS.Safe/kOS.Safe.csproj | 3 + 8 files changed, 1110 insertions(+), 1077 deletions(-) create mode 100644 kerboscript_tests/integration/short_circuit.ks create mode 100644 src/kOS.Safe/Compilation/KS/ExpressionBuilder.cs create mode 100644 src/kOS.Safe/Compilation/KS/ExpressionNode.cs create mode 100644 src/kOS.Safe/Compilation/KS/IExpressionVisitor.cs diff --git a/kerboscript_tests/integration/func.ks b/kerboscript_tests/integration/func.ks index 6c41b774e7..69b68066dc 100644 --- a/kerboscript_tests/integration/func.ks +++ b/kerboscript_tests/integration/func.ks @@ -4,4 +4,10 @@ function a print("a"). } -a(). \ No newline at end of file +a(). + +set b to { + print("b"). +}. + +b(). \ No newline at end of file diff --git a/kerboscript_tests/integration/short_circuit.ks b/kerboscript_tests/integration/short_circuit.ks new file mode 100644 index 0000000000..885a67e4f6 --- /dev/null +++ b/kerboscript_tests/integration/short_circuit.ks @@ -0,0 +1,15 @@ + +function a { + print("a"). + return false. +} + +function b { + print("b"). + return true. +} + +print(a() and b()). +print(a() or b()). +print(b() and a()). +print(b() or a()). \ No newline at end of file diff --git a/src/kOS.Safe.Test/Execution/SimpleTest.cs b/src/kOS.Safe.Test/Execution/SimpleTest.cs index 5120d658fc..88ff75740f 100644 --- a/src/kOS.Safe.Test/Execution/SimpleTest.cs +++ b/src/kOS.Safe.Test/Execution/SimpleTest.cs @@ -41,7 +41,8 @@ public void TestFunc() RunScript("integration/func.ks"); RunSingleStep(); AssertOutput( - "a" + "a", + "b" ); } @@ -118,19 +119,24 @@ public void TestSuffixes() } [Test] - public void TestSuffixes() + public void TestShortCircuit() { - // Test that various suffix and index combinations work for getting and setting - RunScript("integration/suffixes.ks"); - RunSingleStep(); + // Test that boolean logic short circuits + RunScript("integration/short_circuit.ks"); RunSingleStep(); AssertOutput( - "0", - "1", - "2", - "3", - "0", - "False" + "a", + // short circuit away b + "False", + "a", + "b", + "True", + "b", + "a", + "False", + "b", + // short circuit away a + "True" ); } } diff --git a/src/kOS.Safe/Compilation/KS/Compiler.cs b/src/kOS.Safe/Compilation/KS/Compiler.cs index ba8686d4ff..84ce6b12d9 100644 --- a/src/kOS.Safe/Compilation/KS/Compiler.cs +++ b/src/kOS.Safe/Compilation/KS/Compiler.cs @@ -9,7 +9,7 @@ namespace kOS.Safe.Compilation.KS { - class Compiler + class Compiler : IExpressionVisitor { private CodePart part; private Context context; @@ -23,9 +23,6 @@ class Compiler private readonly List returnList = new List(); private readonly List triggerKeepNames = new List(); private bool nowCompilingTrigger; - private bool compilingSetDestination; - private bool identifierIsVariable; - private bool identifierIsSuffix; private bool nowInALoop; private bool needImplicitReturn; private bool nextBraceIsFunction; @@ -66,8 +63,6 @@ private void InitCompileFlags() returnList.Clear(); triggerKeepNames.Clear(); nowCompilingTrigger = false; - compilingSetDestination = false; - identifierIsSuffix = false; nowInALoop = false; needImplicitReturn = true; braceNestLevel = 0; @@ -466,7 +461,7 @@ private void PreProcessOnStatement(ParseNode node) AddOpcode(new OpcodePush(triggerObject.OldValueIdentifier)); AddOpcode(new OpcodeEval()); // eval the expression for the new value, and leave it on the stack twice. - VisitNode(node.Nodes[1]); + VisitExpression(node.Nodes[1]); AddOpcode(new OpcodeEval()); AddOpcode(new OpcodeDup()); // Put one of those two copies of the new value into the old value identifier for next time: @@ -515,7 +510,7 @@ private void PreProcessWhenStatement(ParseNode node) // - - - - - - - - - - - - currentCodeSection = triggerObject.Code; - VisitNode(node.Nodes[1]); + VisitExpression(node.Nodes[1]); OpcodeBranchIfTrue branchToBody = new OpcodeBranchIfTrue(); branchToBody.Distance = 3; AddOpcode(branchToBody); @@ -770,7 +765,14 @@ private void PreProcessUserFunctionStatement(ParseNode node) if (needImplicitArgBottom) AddOpcode(new OpcodeArgBottom()); - VisitNode(bodyNode); + if (isLock) + { + VisitExpression(bodyNode); + } + else + { + VisitNode(bodyNode); + } Int16 implicitReturnScopeDepth = 0; @@ -1076,9 +1078,6 @@ private void VisitNode(ParseNode node) case TokenType.when_stmt: VisitWhenStatement(node); break; - case TokenType.onoff_trailer: - VisitOnOffTrailer(node); - break; case TokenType.stage_stmt: VisitStageStatement(node); break; @@ -1141,86 +1140,6 @@ private void VisitNode(ParseNode node) case TokenType.unset_stmt: VisitUnsetStatement(node); break; - case TokenType.arglist: - VisitArgList(node); - break; - case TokenType.compare_expr: // for issue #20 - case TokenType.arith_expr: - case TokenType.multdiv_expr: - case TokenType.factor: - VisitExpressionChain(node); - break; - case TokenType.expr: - VisitExpr(node); - break; - case TokenType.or_expr: - case TokenType.and_expr: - VisitShortCircuitBoolean(node); - break; - case TokenType.suffix: - VisitSuffix(node); - break; - case TokenType.unary_expr: - VisitUnaryExpression(node); - break; - case TokenType.atom: - VisitAtom(node); - break; - case TokenType.sci_number: - VisitSciNumber(node); - break; - case TokenType.number: - VisitNumber(node); - break; - case TokenType.INTEGER: - VisitInteger(node); - break; - case TokenType.DOUBLE: - VisitDouble(node); - break; - case TokenType.PLUSMINUS: - VisitPlusMinus(node); - break; - case TokenType.MULT: - VisitMult(node); - break; - case TokenType.DIV: - VisitDiv(node); - break; - case TokenType.POWER: - VisitPower(node); - break; - case TokenType.varidentifier: - VisitVarIdentifier(node); - break; - // This never gets called anymore, but it's left here as - // a comment so future programmers who search for it will - // find this comment and realized that it's not an error - // for it to be missing. It's missing-ness is deliberate: - // case TokenType.suffixterm: - // VisitSuffixTerm(node); - // break; - case TokenType.IDENTIFIER: - VisitIdentifier(node); - break; - case TokenType.FILEIDENT: - VisitFileIdent(node); - break; - case TokenType.STRING: - VisitString(node); - break; - case TokenType.TRUEFALSE: - VisitTrueFalse(node); - break; - case TokenType.COMPARATOR: - VisitComparator(node); - break; - case TokenType.AND: - VisitAnd(node); - break; - case TokenType.OR: - VisitOr(node); - break; case TokenType.identifier_led_stmt: VisitIdentifierLedStatement(node); break; @@ -1230,6 +1149,10 @@ private void VisitNode(ParseNode node) case TokenType.directive: VisitDirective(node); break; + case TokenType.EOF: + break; + default: + throw new KOSYouShouldNeverSeeThisException("Unknown token: " + node.Token.Type); } } @@ -1264,382 +1187,6 @@ private void VisitChildNodes(ParseNode node) } } - private void VisitVariableNode(ParseNode node) - { - NodeStartHousekeeping(node); - identifierIsVariable = true; - VisitNode(node); - identifierIsVariable = false; - } - - /// - /// Performs the work for a number of different expressions that all - /// share the following universal basic properties:
- /// - They contain optional binary operators.
- /// - The terms are all at the same precedence level.
- /// - Because of the tie of precedence level, the terms are to be evaluated left-to-right.
- /// - No special extra work is needed, such that simply doing "push expr1, push expr2, then do operator" is all that's needed.
- ///
- /// Examples:
- /// 5 + 4 - x + 2 // because + and - are in the same parse rule, these all get the same flat precedence.
- /// x * y * z
- /// In cases like that where all the operators "tie", the entire chain of terms lives in the same ParseNode,
- /// and we have to unroll those terms and presume left-to-right precedence. That is what this method does.
- ///
- /// - private void VisitExpressionChain(ParseNode node) - { - NodeStartHousekeeping(node); - if (node.Nodes.Count > 1) - { - // it should always be odd, two arguments and one operator - if ((node.Nodes.Count % 2) != 1) return; - - VisitNode(node.Nodes[0]); // pushes lefthand side on stack. - - int nodeIndex = 2; - while (nodeIndex < node.Nodes.Count) - { - VisitNode(node.Nodes[nodeIndex]); // pushes righthand side on stack. - nodeIndex -= 1; - VisitNode(node.Nodes[nodeIndex]); // operator, i.e '*', '+', '-', '/', etc. - nodeIndex += 3; // Move to the next term over (if there's more than 2 terms in the chain). - - // If there are more terms to process, then the value that the operation leaves behind on the stack - // from operating on these two terms will become the 'lefthand side' for the next iteration of this loop. - } - } - else if (node.Nodes.Count == 1) - { - VisitNode(node.Nodes[0]); // This ParseNode isn't *realy* an expression of binary operators, because - // the regex chain of "zero or more" righthand terms.. had zero such terms. - // So just delve in deeper to compile whatever part of speech it is further down. - } - } - - /// - /// The outermost expression level, which may be a normal expression, - /// or may be an anonymous function, depending of if it's got braces. - /// - /// - private void VisitExpr(ParseNode node) - { - NodeStartHousekeeping(node); - - // If it's an instruction block then it's an anonymous function, so - // compile the function body right here, while branching around it so - // it won't execute just yet, and instead just push a UserDelegate of it - // onto the stack as the value of this expression: - if (node.Nodes[0].Token.Type == TokenType.instruction_block) - { - Opcode skipPastFunctionBody = AddOpcode(new OpcodeBranchJump()); - string functionStartLabel = GetNextLabel(false); - - needImplicitReturn = true; - nextBraceIsFunction = true; - VisitNode(node.Nodes[0]); // the braces of the anonymous function and its contents get compiled in-line here. - nextBraceIsFunction = false; - if (needImplicitReturn) - // needImplicitReturn is unconditionally true here, but it's being used anyway so we'll find this block - // of code later when we search for "all the places using needImplicitReturn" and perform a refactor - // of the logic for adding implicit returns. - { - AddOpcode(new OpcodePush(0)); // Functions must push a dummy return val when making implicit returns. Locks already leave an expr atop the stack. - AddOpcode(new OpcodeReturn(0)); - } - Opcode afterFunctionBody = AddOpcode(new OpcodePushDelegateRelocateLater(null,true), functionStartLabel); - skipPastFunctionBody.DestinationLabel = afterFunctionBody.Label; - } - else // ordinary expression - just descend to the next level of the tree and eval the expression as normal: - { - VisitNode(node.Nodes[0]); - } - } - - /// - /// Handles the short-circuit logic of boolean OR and boolean AND - /// chains. It is like VisitExpressionChain (see elsewhere) but - /// in this case it has the special logic to short circuit and skip - /// executing the righthand expression if it can. (The generic VisitExpressionXhain - /// always evaluates both the left and right sides of the operator first, then - /// does the operation). - /// - /// - private void VisitShortCircuitBoolean(ParseNode node) - { - NodeStartHousekeeping(node); - - if (node.Nodes.Count > 1) - { - // it should always be odd, two arguments and one operator - if ((node.Nodes.Count % 2) != 1) return; - - // Determine if this is a chain of ANDs or a chain or ORs. The parser will - // never mix ANDs and ORs into the same ParseNode level. We are guaranteed - // that all the operators in this chain match the first operator in the chain: - // That guarantee is important. Without it, we can't do short-circuiting like this - // because you can't short-circuit a mix of AND and OR at the same precedence. - TokenType operation = node.Nodes[1].Token.Type; // Guaranteed to be either TokenType.AND or TokenType.OR - - // For remembering the instruction pointers from which short-circuit branch jumps came: - List shortCircuitFromIndeces = new List(); - - int nodeIndex = 0; - while (nodeIndex < node.Nodes.Count) - { - if (nodeIndex > 0) // After each term, insert the branch test (which consumes the expr from the stack regardless of if it branches): - { - shortCircuitFromIndeces.Add(currentCodeSection.Count()); - if (operation == TokenType.AND) - AddOpcode(new OpcodeBranchIfFalse()); - else if (operation == TokenType.OR) - AddOpcode(new OpcodeBranchIfTrue()); - else - throw new KOSException("Assertion check: Broken kerboscript compiler (VisitShortCircuitBoolean). See kOS devs"); - } - - VisitNode(node.Nodes[nodeIndex]); // pushes the next term onto the stack. - nodeIndex += 2; // Skip the operator, moving to the next term over. - } - // If it gets to the end of all that and it still hasn't aborted, then the whole expression's - // Boolean value is just the value of its lastmost term, that's already gotten pushed atop the stack. - // Leave the lastmost term there, and just skip ahead past the short-circuit landing target: - OpcodeBranchJump skipShortCircuitTarget = new OpcodeBranchJump(); - skipShortCircuitTarget.Distance = 2; // Hardcoded +2 jump distance skips the upcoming OpcodePush and just lands on - // whatever comes next after this VisitNode. Avoids using DestinationLabel - // for later relocation because it would be messy to reassign this label later - // in whatever VisitNode happens to come up next, when that could be anything. - AddOpcode(skipShortCircuitTarget); - - // Build the instruction all the short circuit checks will jump to if aborting partway through. - // (AND's abort when they're false. OR's abort when they're true.) - AddOpcode(operation == TokenType.AND ? new OpcodePush(false) : new OpcodePush(true)); - string shortCircuitTargetLabel = currentCodeSection[currentCodeSection.Count()-1].Label; - - // Retroactively re-assign the jump labels of all the short circuit branch operations: - foreach (int index in shortCircuitFromIndeces) - { - currentCodeSection[index].DestinationLabel = shortCircuitTargetLabel; - } - } - else if (node.Nodes.Count == 1) - { - VisitNode(node.Nodes[0]); // This ParseNode isn't *realy* an expression of AND or OR operators, because - // the regex chain of "zero or more" righthand terms.. had zero such terms. - // So just delve in deeper to compile whatever part of speech it is further down. - } - } - - private void VisitUnaryExpression(ParseNode node) - { - NodeStartHousekeeping(node); - if (node.Nodes.Count <= 0) return; - - bool addNegation = false; - bool addNot = false; - bool addDefined = false; - int nodeIndex = 0; - - if (node.Nodes[0].Token.Type == TokenType.PLUSMINUS) - { - nodeIndex++; - if (node.Nodes[0].Token.Text == "-") - { - addNegation = true; - } - } - else if (node.Nodes[0].Token.Type == TokenType.NOT) - { - nodeIndex++; - addNot = true; - } - else if (node.Nodes[0].Token.Type == TokenType.DEFINED) - { - nodeIndex++; - addDefined = true; - } - - VisitNode(node.Nodes[nodeIndex]); - - if (addNegation) - { - AddOpcode(new OpcodeMathNegate()); - } - if (addNot) - { - AddOpcode(new OpcodeLogicNot()); - } - if (addDefined) - { - AddOpcode(new OpcodeExists()); - } - } - - private void VisitAtom(ParseNode node) - { - NodeStartHousekeeping(node); - - if (node.Nodes[0].Token.Type == TokenType.BRACKETOPEN) - { - VisitNode(node.Nodes[1]); - } - else - { - VisitNode(node.Nodes[0]); - } - } - - private void VisitSciNumber(ParseNode node) - { - NodeStartHousekeeping(node); - if (node.Nodes.Count == 1) - { - VisitNumber(node.Nodes[0]); - } - else - { - //number in scientific notation - StringBuilder sb = new StringBuilder(); - sb.Append(node.Nodes[0].Nodes[0].Token.Text); // have to use the sub-node of double or integer - for (int i = 1; i < node.Nodes.Count; ++i) - { - sb.Append(node.Nodes[i].Token.Text); - } - string parseText = sb.ToString(); - ScalarValue val; - if (ScalarValue.TryParse(parseText, out val)) - { - AddOpcode(new OpcodePush(val)); - } - else - throw new KOSCompileException(node.Token, string.Format(KOSNumberParseException.TERSE_MSG_FMT, parseText)); - } - } - - private void VisitNumber(ParseNode node) - { - NodeStartHousekeeping(node); - VisitNode(node.Nodes[0]); - } - - private void VisitInteger(ParseNode node) - { - NodeStartHousekeeping(node); - ScalarValue val; - if (ScalarValue.TryParseInt(node.Token.Text.Replace("_", ""), out val) || - ScalarValue.TryParseDouble(node.Token.Text.Replace("_", ""), out val) // fallback if number is too big for an integer. - ) - { - AddOpcode(new OpcodePush(val)); - } - else - throw new KOSCompileException(node.Token, string.Format(KOSNumberParseException.TERSE_MSG_FMT, node.Token.Text)); - } - - private void VisitDouble(ParseNode node) - { - NodeStartHousekeeping(node); - ScalarValue val; - if (ScalarValue.TryParseDouble(node.Token.Text.Replace("_",""), out val)) - { - AddOpcode(new OpcodePush(val)); - } - else - throw new KOSCompileException(node.Token, string.Format(KOSNumberParseException.TERSE_MSG_FMT, node.Token.Text)); - } - - private void VisitTrueFalse(ParseNode node) - { - NodeStartHousekeeping(node); - bool boolValue; - if (bool.TryParse(node.Token.Text, out boolValue)) - { - AddOpcode(new OpcodePush(new BooleanValue(boolValue))); - } - } - - private void VisitOnOffTrailer(ParseNode node) - { - NodeStartHousekeeping(node); - AddOpcode(new OpcodePush((node.Nodes[0].Token.Type == TokenType.ON))); - } - - /// - /// Do the work for function calls. - /// - /// parse node for the function term of the parse tree. - /// true if it should make an OpcodeCall that is Direct, false if it should make an indirect one. - /// See the documentation for OpcodeCall.Direct for the full explanation of the difference. If isDirect is true, then - /// the name to the left of the parentheses will be the name of the function call or the name of the - /// identifier variable that holds the function's jump address in the case of user functions. But in either case - /// the important thing is that when isDirect is true, that means the OpcodeCall uses the Opcode's argument to - /// decide where to call. On the other hand, if isDirect is false, then it will - /// presume the function name, delegate, or branch index was - /// already placed atop the stack by other parts of this compiler, rather than encoding it into the - /// OpcodeCall's argument itself. - /// In the case where it's a direct function, what's the name of it? In the case - /// where it's not direct, this argument doesn't matter. - private void VisitActualFunction(ParseNode node, bool isDirect, string directName = "") - { - NodeStartHousekeeping(node); - - ParseNode trailerNode = node; // the function_trailer rule is here. - - if (trailerNode.Nodes.Count > 0 && trailerNode.Nodes[0].Token.Type == TokenType.ATSIGN) - { - BuildFunctionDelegate(isDirect, directName); - return; - } - - // Need to tell OpcodeCall where in the stack the bottom of the arg list is. - // Even if there are no arguments, it still has to be TOLD that by showing - // it the marker atop the stack with nothing above it. - AddOpcode(new OpcodePush(new KOSArgMarkerType())); - - if (trailerNode.Nodes[1].Token.Type == TokenType.arglist) - { - // Some of the flags remembering the context of - // what we were inside of in the parse tree aren't - // appropriate to be using while evaluating the function's - // argument terms in the list: - bool rememberIsSuffix = identifierIsSuffix; - identifierIsSuffix = false; - bool rememberCompilingSetDestination = compilingSetDestination; - compilingSetDestination = false; - - // Now compile the arguments in the list: - VisitNode(trailerNode.Nodes[1]); - - // And then return the flags to their original condition: - compilingSetDestination = rememberCompilingSetDestination; - identifierIsSuffix = rememberIsSuffix; - } - - if (isDirect) - { - if (options.FuncManager.Exists(directName)) // if the name is a built-in, then add the "()" after it. - directName += "()"; - AddOpcode(new OpcodeCall(directName)); - } - else - { - var op = new OpcodeCall(string.Empty) { Direct = false }; - AddOpcode(op); - } - - } - - private void VisitArgList(ParseNode node) - { - NodeStartHousekeeping(node); - int nodeIndex = 0; - while (nodeIndex < node.Nodes.Count) - { - VisitNode(node.Nodes[nodeIndex]); - nodeIndex += 2; - } - } - // For the case where you wish to eval the args lastmost-first, such // that they'll push onto the stack like so: // arg1 <-- top @@ -1656,242 +1203,10 @@ private void VisitArgListReversed(ParseNode node) int nodeIndex = node.Nodes.Count - 1; while (nodeIndex >= 0) { - VisitNode(node.Nodes[nodeIndex]); + VisitExpression(node.Nodes[nodeIndex]); nodeIndex -= 2; } } - - /// - /// When a function identifier or suffix ends in '@' where parentheses could have gone, - /// then its not really being called like a function. Instead it's being asked to generate - /// a delegate of itself to be put atop the stack. - /// This builds the code that does that. - /// - /// If true, then the directName is the name of the function being called or the user - /// variable holding the function delegate to be called. If false, then the compiler should have built code - /// that will have left a suffix or function reference atop the stack already. - /// only needed when isDirect is true - private void BuildFunctionDelegate(bool isDirect, string directName = "") - { - if (isDirect) - { - if (options.FuncManager.Exists(directName)) // if the name is a built-in, then make a BuiltInDelegate - { - AddOpcode(new OpcodePush(new KOSArgMarkerType())); - AddOpcode(new OpcodePush(directName)); - AddOpcode(new OpcodeCall("makebuiltindelegate()")); - } - else - { - // It is not a built-in, so instead get its value as a user function pointer variable, despite - // the fact that it's being called AS IF it was direct. - if (!directName.EndsWith("*")) directName = directName + "*"; - if (!directName.StartsWith("$")) directName = "$" + directName; - AddOpcode(new OpcodePush(directName)); - } - } - // Else we shouldn't have to do any work because the thing atop the stack will already - // be a suffix delegate. - } - - private void VisitVarIdentifier(ParseNode node) - { - NodeStartHousekeeping(node); - - // I might be called on a raw IDENTIFIER, in which case I have no - // child nodes to descend into. But if I *do* have a child node - // to descend into, then do so: - VisitNode(node.Nodes.Count == 0 ? node : node.Nodes[0]); - } - - // Parses this rule: - // suffix -> suffixterm (suffix_trailer)*; - // suffix_trailer -> (COLON suffixterm); - private void VisitSuffix(ParseNode node) - { - NodeStartHousekeeping(node); - - // For each suffixterm between colons: - for (int nodeIndex = 0; nodeIndex < node.Nodes.Count; ++nodeIndex) - { - - bool remember = identifierIsSuffix; - identifierIsSuffix = (nodeIndex > 0); - - ParseNode suffixTerm; - if (nodeIndex == 0) - suffixTerm = node.Nodes[nodeIndex]; - else - // nodes after the first are suffix_trailers consisting of (COLON suffixterm). This skips the colon. - suffixTerm = node.Nodes[nodeIndex].Nodes[1]; - - // Is it being portrayed like a function call with parentheses? - bool startsWithFunc = - (suffixTerm.Nodes.Count > 1 && - suffixTerm.Nodes[1].Nodes.Count > 0 && - suffixTerm.Nodes[1].Nodes[0].Token.Type == TokenType.function_trailer); - - string firstIdentifier = ""; - bool isUserFunc = false; - if (nodeIndex == 0) - { - firstIdentifier = GetIdentifierText(suffixTerm); - UserFunction userFuncObject = GetUserFunctionWithScopeWalk(firstIdentifier, node); - if (userFuncObject != null && !compilingSetDestination) - { - firstIdentifier = userFuncObject.ScopelessPointerIdentifier; - isUserFunc = true; - } - } - // The term starts with either an identifier or an expression. If it's the start, then parse - // it as a variable, else parse it as a raw identifier: - bool rememberIsV = identifierIsVariable; - identifierIsVariable = (!startsWithFunc) && nodeIndex == 0; - - // when we are setting a member value we need to leave - // the last object and the last suffix in the stack - bool usingSetMember = (suffixTerm.Nodes.Count > 0) && (compilingSetDestination && nodeIndex == (node.Nodes.Count - 1)); - - // Push this term on the stack unless it's the name of the user function or built-in function or a suffix: - bool isDirect = true; - - if (usingSetMember && suffixTerm.Nodes.Count == 1) - { - // If this is the name of a suffix that we are setting, don't do anything with it. - // ProcessSetOperation will handle putting the suffix name into the opcode. - } - else if (nodeIndex != 0 && !usingSetMember) - { - string suffixName = GetIdentifierText(suffixTerm.Nodes[0]); - AddOpcode(startsWithFunc ? new OpcodeGetMethod(suffixName) : new OpcodeGetMember(suffixName)); - isDirect = false; - } - else if (!isUserFunc && (nodeIndex > 0 || !startsWithFunc)) - { - VisitNode(suffixTerm.Nodes[0]); - isDirect = false; - } - identifierIsVariable = rememberIsV; - - // The remaining terms are a chain of function_trailers "(...)" and array_trailers "[...]" or "#.." in any arbitrary order: - for (int trailerIndex = 1; trailerIndex < suffixTerm.Nodes.Count; ++trailerIndex) - { - // suffixterm_trailer is always a wrapper around either function_trailer or array_trailer, - // so delve down one level to get which of them it is: - ParseNode trailerTerm = suffixTerm.Nodes[trailerIndex].Nodes[0]; - bool isFunc = (trailerTerm.Token.Type == TokenType.function_trailer); - bool isArray = (trailerTerm.Token.Type == TokenType.array_trailer); - bool thisTermIsDirect = (isDirect && trailerIndex == 1); // only the firstmost term in a chain can be direct. - - if (isFunc || isUserFunc) - { - // direct if it's just one term like foo(aaa) but indirect - // if it's a list of suffixes like foo:bar(aaa): - VisitActualFunction(trailerTerm, thisTermIsDirect, firstIdentifier); - } - if (isArray) - { - VisitActualArray(trailerTerm); - } - } - - // In the case of a lock function without parentheses, it needs this special case: - if (suffixTerm.Nodes.Count <= 1) - { - if (isDirect && isUserFunc) - { - AddOpcode(new OpcodePush(new KOSArgMarkerType())); - AddOpcode(new OpcodeCall(firstIdentifier)); - } - } - - identifierIsSuffix = remember; - - } - } - - /// - /// Do the work for array index references. It assumes the array object has already - /// been pushed on top of the stack so there's no reason to read that from the - /// node's children. It just reads the indexing part. - /// - /// parse node for the array suffix of the parse tree. - private void VisitActualArray(ParseNode node) - { - ParseNode trailerNode = node; // should be type array_trailer. - - int nodeIndex = 1; - while (nodeIndex < trailerNode.Nodes.Count) - { - // Skip two tokens instead of one between dimensions if using the "[]" syntax: - if (trailerNode.Nodes[nodeIndex].Token.Type == TokenType.SQUAREOPEN) - { - ++nodeIndex; - } - - // Temporarily turn off these flags while evaluating the expression inside - // the array index square brackets. These flags apply to this outer containing - // thing, the array access, not to the expression in the index brackets: - bool rememberIdentIsSuffix = identifierIsSuffix; - identifierIsSuffix = false; - bool rememberCompSetDest = compilingSetDestination; - compilingSetDestination = false; - - VisitNode(trailerNode.Nodes[nodeIndex]); // pushes the result of expression inside square brackets. - - compilingSetDestination = rememberCompSetDest; - identifierIsSuffix = rememberIdentIsSuffix; - - // Two ways to check if this is the last index (i.e. the 'k' in arr[i][j][k]'), - // depending on whether using the "#" syntax or the "[..]" syntax: - bool isLastIndex = false; - var previousNodeType = trailerNode.Nodes[nodeIndex - 1].Token.Type; - switch (previousNodeType) - { - case TokenType.ARRAYINDEX: - isLastIndex = (nodeIndex == trailerNode.Nodes.Count - 1); - break; - case TokenType.SQUAREOPEN: - isLastIndex = (nodeIndex == trailerNode.Nodes.Count - 2); - break; - } - - // when we are setting a member value we need to leave - // the last object and the last index in the stack - // the only exception is when we are setting a suffix of the indexed value - bool atEnd = IsLastmostTrailerInTerm(node); - if (!(compilingSetDestination && isLastIndex) || (!atEnd)) - { - AddOpcode(new OpcodeGetIndex()); - } - - nodeIndex += 2; - } - - } - - /// - /// Returns true if this is the last most trailer term (array_trailer, suffix_trailer, or function_trailer) - /// in a term inside a suffix rule of the parser. Does this by a tree walk to look for siblings to - /// the right of me. - /// - /// Node to check - /// true if I am the rightmost thing in the parse tree all the way up to the suffix term above me. - private bool IsLastmostTrailerInTerm(ParseNode node) - { - ParseNode current = node; - ParseNode parent = node.Parent; - - while (parent != null && current.Token.Type != TokenType.suffix && current.Token.Type != TokenType.varidentifier) - { - if (parent.Nodes.LastIndexOf(current) < parent.Nodes.Count - 1) - return false; // there is a child to the right of me. I am not lastmost. - - current = parent; - parent = current.Parent; - } - return true; - } private string GetIdentifierText(ParseNode node) { @@ -1912,240 +1227,44 @@ private string GetIdentifierText(ParseNode node) return string.Empty; } - - // The fact that there is no VisitSuffixTerm method is not an omission or mistake. - // All the logic of this node of the parse tree is now handled by the parent nodes - // that come above this one instead. I'm leaving this comment here so that future programmers - // searching this code don't attempt to fix this "mistake" by adding this method back in: - // private void VisitSuffixTerm(ParseNode node) - // { - // // nothing here anymore. - // } - - private void VisitIdentifier(ParseNode node) - { - NodeStartHousekeeping(node); - bool isVariable = (identifierIsVariable && !identifierIsSuffix); - string prefix = isVariable ? "$" : String.Empty; - string identifier = GetIdentifierText(node); - - // Special case when the identifier is known to be a lock. - // Note that this only works when the lock is defined in the SAME - // file. When one script calls another one, the compiler won't know - // that the identifier is a lock, and you'll have to use empty parens - // to make it a real function call like var(): - UserFunction userFuncObject = GetUserFunctionWithScopeWalk(identifier, node); - if (isVariable && userFuncObject != null) - { - AddOpcode(new OpcodeCall(userFuncObject.ScopelessPointerIdentifier)); - } - else - { - AddOpcode(new OpcodePush(prefix + identifier)); - } - } - - /// - /// Get the User function with the given the identifier, performing a - /// scope walk from here up to the root of the parse tree until a hit - /// is seen. If none are seen, then return null. This can only "see" - /// the functions that are defined in this same compile, not ones from - /// other scripts this script ran. - /// - /// - /// - /// - private UserFunction GetUserFunctionWithScopeWalk(string identifier, ParseNode node) - { - ParseNode current = node; - while (current != null) - { - Scope nodeScope; - if (scopeMap.TryGetValue(current, out nodeScope)) - if (context.UserFunctions.Contains(identifier, nodeScope.ScopeId)) - return context.UserFunctions.GetUserFunction(identifier, nodeScope.ScopeId, current); - current = current.Parent; - } - - // One more try at global scope: - if (context.UserFunctions.Contains(identifier, 0)) - return context.UserFunctions.GetUserFunction(identifier, 0, node); - - // Okay give up then: - return null; - } - - private void VisitFileIdent(ParseNode node) - { - NodeStartHousekeeping(node); - string identifier = GetIdentifierText(node); - AddOpcode(new OpcodePush(identifier)); - } - - private void VisitString(ParseNode node) - { - NodeStartHousekeeping(node); - AddOpcode(new OpcodePush(new StringValue(node.Token.Text.Trim('"')))); - } - - /// - /// Check for if the rightmost thing in the var_identifier node - /// is a suffix term. i.e. return true if the var_identifier is:
- /// AAA:BBB, or
- /// AAA[0]:BBB,
- /// but NOT if it's this:
- /// AAA:BBB[0].
- /// (Which does *contain* a suffix, but not as the rightmost part of it. The rightmost - /// part of it is the array indexer "[0]".) - ///
- private bool VarIdentifierEndsWithSuffix(ParseNode node) - { - // If it's a var_identifier being worked on, drop down one level first - // to get into the actual meat of the syntax tree it represents: - if (node.Token.Type == TokenType.varidentifier) - return VarIdentifierEndsWithSuffix(node.Nodes.First()); - - // Descend the rightmost children until encountering the deepest node that is - // still a suffix_trailer, array_trailer, or function_trailer. If that node - // was a suffix_trailer, return true, else it's false. - ParseNode prevChild = node; - ParseNode thisChild = node.Nodes.Last(); - - bool descendedThroughAColon = false; // eeeewwww, sounds disgusting. - - while (thisChild.Token.Type == TokenType.suffix_trailer || - thisChild.Token.Type == TokenType.suffix || - thisChild.Token.Type == TokenType.suffixterm || - thisChild.Token.Type == TokenType.suffixterm_trailer || - thisChild.Token.Type == TokenType.array_trailer || - thisChild.Token.Type == TokenType.function_trailer) - { - if (thisChild.Token.Type == TokenType.suffix_trailer) - descendedThroughAColon = true; - prevChild = thisChild; - thisChild = thisChild.Nodes.Last(); - } - return descendedThroughAColon && - (prevChild.Token.Type == TokenType.suffix_trailer || - prevChild.Token.Type == TokenType.suffixterm); - } - - /// - /// Get the rightmost suffix in a var_identifier. - /// i.e. return "BBB" if the var_identifier is:
- /// AAA:BBB, or
- /// AAA[0]:BBB,
- ///
- private string GetVarIdentifierEndSuffix(ParseNode node) - { - // If it's a var_identifier being worked on, drop down one level first - // to get into the actual meat of the syntax tree it represents: - if (node.Token.Type == TokenType.varidentifier) - return GetVarIdentifierEndSuffix(node.Nodes.First()); - - // Descend the rightmost children until encountering the deepest node that is - // still a suffix_trailer, array_trailer, or function_trailer. If that node - // was a suffix_trailer, return true, else it's false. - ParseNode prevChild = node; - ParseNode thisChild = node.Nodes.Last(); - - bool descendedThroughAColon = false; // eeeewwww, sounds disgusting. - - while (thisChild.Token.Type == TokenType.suffix_trailer || - thisChild.Token.Type == TokenType.suffix || - thisChild.Token.Type == TokenType.suffixterm || - thisChild.Token.Type == TokenType.suffixterm_trailer || - thisChild.Token.Type == TokenType.array_trailer || - thisChild.Token.Type == TokenType.function_trailer) - { - if (thisChild.Token.Type == TokenType.suffix_trailer) - descendedThroughAColon = true; - prevChild = thisChild; - thisChild = thisChild.Nodes.Last(); - } - if (descendedThroughAColon && - (prevChild.Token.Type == TokenType.suffix_trailer || - prevChild.Token.Type == TokenType.suffixterm)) - { - return GetIdentifierText(prevChild); - } - - throw new KOSYouShouldNeverSeeThisException("VarIdentifier didn't end in a suffix"); - } - - /// - /// Check for if the rightmost thing in the var_identifier node - /// is an array indexer. i.e. return true if the var_identifier is:
- /// AAA:BBB[0], or
- /// AAA[0],
- /// but NOT if it's this:
- /// AAA[0]:BBB
- /// (Which does *contain* an array indexer, but not as the rightmost part of it. The rightmost - /// part of it is the suffix term ":BBB".) - ///
- private bool VarIdentifierEndsWithIndex(ParseNode node) - { - // If it's a var_identifier being worked on, drop down one level first - // to get into the actual meat of the syntax tree it represents: - if (node.Token.Type == TokenType.varidentifier) - return VarIdentifierEndsWithIndex(node.Nodes.First()); - - // Descend the rightmost children until encountering the deepest node that is - // still a suffix_trailer, array_trailer, or function_trailer. If that node - // was an array_trailer, return true, else it's false. - ParseNode prevChild = node; - ParseNode thisChild = node.Nodes.Last(); - while (thisChild.Token.Type == TokenType.suffix_trailer || - thisChild.Token.Type == TokenType.suffix || - thisChild.Token.Type == TokenType.suffixterm || - thisChild.Token.Type == TokenType.suffixterm_trailer || - thisChild.Token.Type == TokenType.array_trailer || - thisChild.Token.Type == TokenType.function_trailer) - { - prevChild = thisChild; - thisChild = thisChild.Nodes.Last(); - } - return prevChild.Token.Type == TokenType.array_trailer; - } /// - /// Perform a depth-first leftmost search of the parse tree from the starting - /// point given to find the first occurrence of a node of the given token type.
- /// This is intended as a way to make code that might be a bit more robustly able - /// to handle shifts and adjustments to the parse grammar in the TinyPG file.
- /// Instead of assuming "I know that array nodes are always one level underneath - /// function nodes", it instead lets you say "Get the array node version of this - /// node, no matter how many levels down it may be."
- /// This is needed because TinyPG's LL(1) limitations made it so we had to define - /// things like "we're going to call this node a 'function' even though it might - /// not actually be because the "(..)" part of the syntax is optional. In reality - /// it's *potentially* a function, or maybe actually an array, or an identifier.
- /// This method is intended to let you descend however far down is required to get - /// the node in the sort of context you're looking for. + /// Get the User function with the given the identifier, performing a + /// scope walk from here up to the root of the parse tree until a hit + /// is seen. If none are seen, then return null. This can only "see" + /// the functions that are defined in this same compile, not ones from + /// other scripts this script ran. ///
- /// start the search from this point in the parse tree - /// look for this kind of node - /// the found node, or null if no such node found. - private ParseNode DepthFirstLeftSearch(ParseNode node, TokenType tokType) + /// + /// + /// + private UserFunction GetUserFunctionWithScopeWalk(string identifier, ParseNode node) { - if (node.Token.Type == tokType) - { - return node; - } - foreach (ParseNode child in node.Nodes) + ParseNode current = node; + while (current != null) { - ParseNode hit = DepthFirstLeftSearch(child, tokType); - if (hit != null) - return hit; + Scope nodeScope; + if (scopeMap.TryGetValue(current, out nodeScope)) + if (context.UserFunctions.Contains(identifier, nodeScope.ScopeId)) + return context.UserFunctions.GetUserFunction(identifier, nodeScope.ScopeId, current); + current = current.Parent; } + + // One more try at global scope: + if (context.UserFunctions.Contains(identifier, 0)) + return context.UserFunctions.GetUserFunction(identifier, 0, node); - return null; // not found. + // Okay give up then: + return null; } private void VisitSetStatement(ParseNode node) { NodeStartHousekeeping(node); - ProcessSetOperation(node.Nodes[1], node.Nodes[3]); + ProcessSetOperation( + ExpressionBuilder.BuildExpression(node.Nodes[1]), + ExpressionBuilder.BuildExpression(node.Nodes[3]) + ); } /// @@ -2153,49 +1272,54 @@ private void VisitSetStatement(ParseNode node) /// /// The lefthand-side expression to be set /// The righthand-side expression to set it to - private void ProcessSetOperation(ParseNode setThis, ParseNode toThis) + private void ProcessSetOperation(ExpressionNode setThis, ExpressionNode toThis) { - bool isSuffix = VarIdentifierEndsWithSuffix(setThis); - bool isIndex = VarIdentifierEndsWithIndex(setThis); - if (isSuffix || isIndex) + if (setThis is IdentifierAtomNode) { - // destination - compilingSetDestination = true; - VisitNode(setThis); - compilingSetDestination = false; + // set identifier to expr. - // expression - VisitNode(toThis); + string identifier = ((IdentifierAtomNode)setThis).Identifier; - if (isSuffix) + // if this is a locked value, unlock it + UserFunction userFunc = GetUserFunctionWithScopeWalk(identifier, setThis.ParseNode); + if (userFunc != null) { - string suffixName = GetVarIdentifierEndSuffix(setThis); - AddOpcode(new OpcodeSetMember(suffixName)); + UnlockIdentifier(userFunc); } + + toThis.Accept(this); + + if (allowLazyGlobal) + AddOpcode(new OpcodeStore("$" + identifier)); else - { - AddOpcode(new OpcodeSetIndex()); - } + AddOpcode(new OpcodeStoreExist("$" + identifier)); } - else + else if (setThis is GetIndexNode) { - // normal variable set - VisitNode(toThis); + // set base[index] to expr. - string identifier = GetIdentifierText(setThis); + GetIndexNode getIndex = (GetIndexNode)setThis; - UserFunction userFuncObject = GetUserFunctionWithScopeWalk(identifier, setThis); - if (userFuncObject != null) - { - UnlockIdentifier(userFuncObject); - } + getIndex.Base.Accept(this); + getIndex.Index.Accept(this); + toThis.Accept(this); - string varName = "$" + identifier; + AddOpcode(new OpcodeSetIndex()); + } + else if (setThis is GetSuffixNode) + { + // set base:suffix to expr. - if (allowLazyGlobal) - AddOpcode(new OpcodeStore(varName)); - else - AddOpcode(new OpcodeStoreExist(varName)); + GetSuffixNode getSuffix = (GetSuffixNode)setThis; + + getSuffix.Base.Accept(this); + toThis.Accept(this); + + AddOpcode(new OpcodeSetMember(getSuffix.Suffix)); + } + else + { + throw new KOSCompileException(setThis.ParseNode.Token, "Invalid set destination"); } } @@ -2203,7 +1327,7 @@ private void VisitIfStatement(ParseNode node) { NodeStartHousekeeping(node); // The IF check: - VisitNode(node.Nodes[1]); + VisitExpression(node.Nodes[1]); Opcode branchToFalse = AddOpcode(new OpcodeBranchIfFalse()); // The IF BODY: VisitNode(node.Nodes[2]); @@ -2240,7 +1364,7 @@ private void VisitUntilStatement(ParseNode node) string conditionLabel = GetNextLabel(false); PushBreakList(braceNestLevel); - VisitNode(node.Nodes[1]); + VisitExpression(node.Nodes[1]); AddOpcode(new OpcodeLogicNot()); Opcode branch = AddOpcode(new OpcodeBranchIfFalse()); AddToBreakList(branch); @@ -2253,75 +1377,6 @@ private void VisitUntilStatement(ParseNode node) nowInALoop = remember; } - private void VisitPlusMinus(ParseNode node) - { - NodeStartHousekeeping(node); - if (node.Token.Text == "+") - { - AddOpcode(new OpcodeMathAdd()); - } - else if (node.Token.Text == "-") - { - AddOpcode(new OpcodeMathSubtract()); - } - } - - private void VisitMult(ParseNode node) - { - NodeStartHousekeeping(node); - AddOpcode(new OpcodeMathMultiply()); - } - - private void VisitDiv(ParseNode node) - { - NodeStartHousekeeping(node); - AddOpcode(new OpcodeMathDivide()); - } - - private void VisitPower(ParseNode node) - { - NodeStartHousekeeping(node); - AddOpcode(new OpcodeMathPower()); - } - - private void VisitAnd(ParseNode node) - { - NodeStartHousekeeping(node); - AddOpcode(new OpcodeLogicAnd()); - } - - private void VisitOr(ParseNode node) - { - NodeStartHousekeeping(node); - AddOpcode(new OpcodeLogicOr()); - } - - private void VisitComparator(ParseNode node) - { - NodeStartHousekeeping(node); - switch (node.Token.Text) - { - case ">": - AddOpcode(new OpcodeCompareGT()); - break; - case "<": - AddOpcode(new OpcodeCompareLT()); - break; - case ">=": - AddOpcode(new OpcodeCompareGTE()); - break; - case "<=": - AddOpcode(new OpcodeCompareLTE()); - break; - case "<>": - AddOpcode(new OpcodeCompareNE()); - break; - case "=": - AddOpcode(new OpcodeCompareEqual()); - break; - } - } - private void VisitInstructionBlock(ParseNode node) { NodeStartHousekeeping(node); @@ -2342,7 +1397,7 @@ private void VisitInstructionBlock(ParseNode node) // For each child node, but interrupting for the spot // where to insert the argbottom opcode: - for (int i = 0 ; i < node.Nodes.Count ; ++i) + for (int i = 1 ; i < node.Nodes.Count - 1 ; ++i) { if (i == argbottomSpot) AddOpcode(new OpcodeArgBottom()); @@ -2604,7 +1659,7 @@ private void VisitOnStatement(ParseNode node) if (triggerObject.IsInitialized()) { // Store the current value into the old value to prep for the first use of the ON trigger: - VisitNode(node.Nodes[1]); // the expression in the on statement. + VisitExpression(node.Nodes[1]); // the expression in the on statement. AddOpcode(new OpcodeStore(triggerObject.OldValueIdentifier)); AddOpcode(new OpcodePushRelocateLater(null), triggerObject.GetFunctionLabel()); AddOpcode(new OpcodeAddTrigger()); @@ -2632,7 +1687,7 @@ private void VisitWaitStatement(ParseNode node) if (node.Nodes.Count == 3) { // For commands of the form: WAIT N. where N is a number: - VisitNode(node.Nodes[1]); + VisitExpression(node.Nodes[1]); AddOpcode(new OpcodeWait()); } else @@ -2640,7 +1695,7 @@ private void VisitWaitStatement(ParseNode node) // For commands of the form: WAIT UNTIL expr. where expr is any boolean expression: Opcode waitLoopStart = AddOpcode(new OpcodePush(0)); // Loop start: Gives OpcodeWait an argument of zero. AddOpcode(new OpcodeWait()); // Avoid busy polling. Even a WAIT 0 still forces 1 fixedupdate 'tick'. - VisitNode(node.Nodes[2]); // Inserts instructions here to evaluate the expression + VisitExpression(node.Nodes[2]); // Inserts instructions here to evaluate the expression AddOpcode(new OpcodeBranchIfFalse(), waitLoopStart.Label); // Repeat the loop as long as expression is false. // Falls through to whatever comes next when expression is true. } @@ -2657,7 +1712,7 @@ private void VisitDeclareStatement(ParseNode node) // DECLARE [GLOBAL|LOCAL] identifier TO expr. if (lastSubNode.Token.Type == TokenType.declare_identifier_clause) { - VisitNode(lastSubNode.Nodes[2]); + VisitExpression(lastSubNode.Nodes[2]); AddOpcode(CreateAppropriateStoreCode(whereToStore, true, "$" + GetIdentifierText(lastSubNode.Nodes[0]))); } @@ -2723,7 +1778,7 @@ private void VisitDeclareOneParameter(StorageModifier whereToStore, ParseNode id OpcodeBranchIfFalse branchSkippingInit = new OpcodeBranchIfFalse(); AddOpcode(branchSkippingInit); - VisitNode(expressionNode); // evals init expression on the top of the stack where the arg would have been + VisitExpression(expressionNode); // evals init expression on the top of the stack where the arg would have been branchSkippingInit.DestinationLabel = GetNextLabel(false); } @@ -2837,14 +1892,13 @@ private StorageModifier GetStorageModifierForDeclare(ParseNode node) private void VisitToggleStatement(ParseNode node) { NodeStartHousekeeping(node); - string varName = "$" + GetIdentifierText(node.Nodes[1]); - VisitVarIdentifier(node.Nodes[1]); - AddOpcode(new OpcodeLogicToBool()); - AddOpcode(new OpcodeLogicNot()); - if (allowLazyGlobal) - AddOpcode(new OpcodeStore(varName)); - else - AddOpcode(new OpcodeStoreExist(varName)); + ExpressionNode identifier = ExpressionBuilder.BuildExpression(node.Nodes[1]); + ExpressionNode target = new NegateExpressionNode() { + ParseNode = node, + Target = identifier + }; + + ProcessSetOperation(identifier, target); } private void VisitPrintStatement(ParseNode node) @@ -2853,16 +1907,16 @@ private void VisitPrintStatement(ParseNode node) if (node.Nodes.Count == 3) { AddOpcode(new OpcodePush(new KOSArgMarkerType())); - VisitNode(node.Nodes[1]); + VisitExpression(node.Nodes[1]); AddOpcode(new OpcodeCall("print()")); AddOpcode(new OpcodePop()); // all functions now return a value even if it's a dummy we ignore. } else { AddOpcode(new OpcodePush(new KOSArgMarkerType())); - VisitNode(node.Nodes[1]); - VisitNode(node.Nodes[4]); - VisitNode(node.Nodes[6]); + VisitExpression(node.Nodes[1]); + VisitExpression(node.Nodes[4]); + VisitExpression(node.Nodes[6]); AddOpcode(new OpcodeCall("printat()")); AddOpcode(new OpcodePop()); // all functions now return a value even if it's a dummy we ignore. } @@ -2880,7 +1934,7 @@ private void VisitAddStatement(ParseNode node) { NodeStartHousekeeping(node); AddOpcode(new OpcodePush(new KOSArgMarkerType())); - VisitNode(node.Nodes[1]); + VisitExpression(node.Nodes[1]); AddOpcode(new OpcodeCall("add()")); AddOpcode(new OpcodePop()); // all functions now return a value even if it's a dummy we ignore. } @@ -2889,7 +1943,7 @@ private void VisitRemoveStatement(ParseNode node) { NodeStartHousekeeping(node); AddOpcode(new OpcodePush(new KOSArgMarkerType())); - VisitNode(node.Nodes[1]); + VisitExpression(node.Nodes[1]); AddOpcode(new OpcodeCall("remove()")); AddOpcode(new OpcodePop()); // all functions now return a value even if it's a dummy we ignore. } @@ -2906,7 +1960,7 @@ private void VisitEditStatement(ParseNode node) { NodeStartHousekeeping(node); AddOpcode(new OpcodePush(new KOSArgMarkerType())); - VisitNode(node.Nodes[1]); + VisitExpression(node.Nodes[1]); AddOpcode(new OpcodeCall("edit()")); AddOpcode(new OpcodePop()); // all functions now return a value even if it's a dummy we ignore. } @@ -2992,7 +2046,7 @@ private void VisitRunStatement(ParseNode node) if (!hasOn && options.LoadProgramsInSameAddressSpace) { AddOpcode(new OpcodePush(hasOnce)); - VisitNode(node.Nodes[progNameIndex]); // put program name on stack. + VisitExpression(node.Nodes[progNameIndex]); // put program name on stack. AddOpcode(new OpcodeEval(true)); } @@ -3018,7 +2072,7 @@ private void VisitRunStatement(ParseNode node) AddOpcode(new OpcodePush(new KOSArgMarkerType())); // program name - VisitNode(node.Nodes[progNameIndex]); + VisitExpression(node.Nodes[progNameIndex]); // volume where program should be executed (null means local) if (volumeIndex >= 0 && volumeIndex < node.Nodes.Count) @@ -3039,12 +2093,12 @@ private void VisitCompileStatement(ParseNode node) { NodeStartHousekeeping(node); AddOpcode(new OpcodePush(new KOSArgMarkerType())); // for the load() function. - VisitNode(node.Nodes[1]); + VisitExpression(node.Nodes[1]); AddOpcode(new OpcodePush(false)); if (node.Nodes.Count > 3) { // It has a "TO outputfile" clause: - VisitNode(node.Nodes[3]); + VisitExpression(node.Nodes[3]); } else { @@ -3061,7 +2115,7 @@ private void VisitSwitchStatement(ParseNode node) { NodeStartHousekeeping(node); AddOpcode(new OpcodePush(new KOSArgMarkerType())); - VisitNode(node.Nodes[2]); + VisitExpression(node.Nodes[2]); AddOpcode(new OpcodeCall("switch()")); AddOpcode(new OpcodePop()); // all functions now return a value even if it's a dummy we ignore. } @@ -3070,11 +2124,11 @@ private void VisitCopyStatement(ParseNode node) { NodeStartHousekeeping(node); AddOpcode(new OpcodePush(new KOSArgMarkerType())); - VisitNode(node.Nodes[1]); + VisitExpression(node.Nodes[1]); AddOpcode(new OpcodePush(node.Nodes[2].Token.Type == TokenType.FROM ? "from" : "to")); - VisitNode(node.Nodes[3]); + VisitExpression(node.Nodes[3]); AddOpcode(new OpcodeCall("copy_deprecated()")); AddOpcode(new OpcodePop()); // all functions now return a value even if it's a dummy we ignore. } @@ -3101,8 +2155,8 @@ private void VisitRenameStatement(ParseNode node) AddOpcode(new OpcodePush(renameFile ? "file" : "volume")); } - VisitNode(node.Nodes[oldNameIndex]); - VisitNode(node.Nodes[newNameIndex]); + VisitExpression(node.Nodes[oldNameIndex]); + VisitExpression(node.Nodes[newNameIndex]); if (renameFile) { @@ -3119,10 +2173,10 @@ private void VisitDeleteStatement(ParseNode node) { NodeStartHousekeeping(node); AddOpcode(new OpcodePush(new KOSArgMarkerType())); - VisitNode(node.Nodes[1]); + VisitExpression(node.Nodes[1]); if (node.Nodes.Count == 5) - VisitNode(node.Nodes[3]); + VisitExpression(node.Nodes[3]); else AddOpcode(new OpcodePush(null)); @@ -3142,7 +2196,7 @@ private void VisitListStatement(ParseNode node) string varName = "$" + GetIdentifierText(node.Nodes[3]); // list type AddOpcode(new OpcodePush(new KOSArgMarkerType())); - VisitNode(node.Nodes[1]); + VisitExpression(node.Nodes[1]); // build list AddOpcode(new OpcodeCall("buildlist()")); if (allowLazyGlobal) @@ -3155,7 +2209,7 @@ private void VisitListStatement(ParseNode node) AddOpcode(new OpcodePush(new KOSArgMarkerType())); // list type if (hasIdentifier) - VisitNode(node.Nodes[1]); + VisitExpression(node.Nodes[1]); else AddOpcode(new OpcodePush("files")); // print list @@ -3168,8 +2222,8 @@ private void VisitLogStatement(ParseNode node) { NodeStartHousekeeping(node); AddOpcode(new OpcodePush(new KOSArgMarkerType())); - VisitNode(node.Nodes[1]); - VisitNode(node.Nodes[3]); + VisitExpression(node.Nodes[1]); + VisitExpression(node.Nodes[3]); AddOpcode(new OpcodeCall("logfile()")); AddOpcode(new OpcodePop()); // all functions now return a value even if it's a dummy we ignore. } @@ -3212,7 +2266,7 @@ private void VisitReturnStatement(ParseNode node) // keyword with no expression, then push a secret dummy return value of zero: if (node.Nodes.Count > 2) { - VisitNode(node.Nodes[1]); + VisitExpression(node.Nodes[1]); } else { @@ -3270,7 +2324,7 @@ private void VisitForStatement(ParseNode node) PushBreakList(braceNestLevel); - VisitNode(node.Nodes[3]); + VisitExpression(node.Nodes[3]); AddOpcode(new OpcodeGetMember("iterator")); AddOpcode(new OpcodeStoreLocal(iteratorIdentifier)); // loop condition @@ -3312,7 +2366,7 @@ private void VisitUnsetStatement(ParseNode node) } else { - VisitVariableNode(node.Nodes[1]); + VisitExpression(node.Nodes[1]); } AddOpcode(new OpcodeUnset()); @@ -3347,7 +2401,10 @@ private void VisitIdentifierLedExpression(ParseNode node) // Transform the statement into the equivalent more sane statement: // SET BUNCHA_STUFF TO TRUE. // OR FALSE, Depending. // And have the compiler parse it THAT way instead of trying to mess with it here: - ProcessSetOperation(node.Nodes[0], node.Nodes[1]); + ProcessSetOperation( + ExpressionBuilder.BuildExpression(node.Nodes[0]), + ExpressionBuilder.BuildExpression(node.Nodes[1]) + ); } else { @@ -3356,7 +2413,7 @@ private void VisitIdentifierLedExpression(ParseNode node) // the result away off the top of the stack. // To keep this code simple, we just have the rule that there will unconditionally always // be something atop the stack that all function calls leave behind, even if it's a dummy.) - VisitNode(node.Nodes[0]); + VisitExpression(node.Nodes[0]); AddOpcode(new OpcodePop()); } } @@ -3449,5 +2506,318 @@ public void VisitLazyGlobalDirective(ParseNode node) allowLazyGlobal = false; // else do nothing, which really should be an impossible case. } + + public void VisitExpression(ParseNode node) + { + NodeStartHousekeeping(node); + + ExpressionNode expr = ExpressionBuilder.BuildExpression(node); + expr.Accept(this); + } + + public void VisitExpression(OrExpressionNode node) + { + NodeStartHousekeeping(node.ParseNode); + + List skipOps = new List(); + + for (int i = 0; i < node.Expressions.Length; i++) + { + node.Expressions[i].Accept(this); + if (i != node.Expressions.Length - 1) + { + // for all but the last expression, we short circuit when true + skipOps.Add(AddOpcode(new OpcodeBranchIfTrue())); + } + } + + // jump over the short circuit value + AddOpcode(new OpcodeBranchJump() { Distance = 2 }); + + // if we short circuited, it was true + string shortCircuitLabel = AddOpcode(new OpcodePush(true)).Label; + + // make all the skips jump to the short circuit value + foreach (var skipOp in skipOps) + { + skipOp.DestinationLabel = shortCircuitLabel; + } + } + + public void VisitExpression(AndExpressionNode node) + { + NodeStartHousekeeping(node.ParseNode); + + List skipOps = new List(); + + for (int i = 0; i < node.Expressions.Length; i++) + { + node.Expressions[i].Accept(this); + if (i != node.Expressions.Length - 1) + { + // for all but the last expression, we short circuit when false + skipOps.Add(AddOpcode(new OpcodeBranchIfFalse())); + } + } + + // jump over the short circuit value + AddOpcode(new OpcodeBranchJump() { Distance = 2 }); + + // if we short circuited, it was false + string shortCircuitLabel = AddOpcode(new OpcodePush(false)).Label; + + // make all the skips jump to the short circuit value + foreach (var skipOp in skipOps) + { + skipOp.DestinationLabel = shortCircuitLabel; + } + } + + public void VisitExpression(CompareExpressionNode node) + { + NodeStartHousekeeping(node.ParseNode); + + node.Left.Accept(this); + node.Right.Accept(this); + + switch (node.Comparator) + { + case "<": + AddOpcode(new OpcodeCompareLT()); + break; + case "<=": + AddOpcode(new OpcodeCompareLTE()); + break; + case ">=": + AddOpcode(new OpcodeCompareGTE()); + break; + case ">": + AddOpcode(new OpcodeCompareGT()); + break; + case "=": + AddOpcode(new OpcodeCompareEqual()); + break; + case "<>": + AddOpcode(new OpcodeCompareNE()); + break; + default: + throw new KOSYouShouldNeverSeeThisException("Unknown comparator: " + node.Comparator); + } + } + + public void VisitExpression(AddExpressionNode node) + { + NodeStartHousekeeping(node.ParseNode); + + node.Left.Accept(this); + node.Right.Accept(this); + AddOpcode(new OpcodeMathAdd()); + } + + public void VisitExpression(SubtractExpressionNode node) + { + NodeStartHousekeeping(node.ParseNode); + + node.Left.Accept(this); + node.Right.Accept(this); + AddOpcode(new OpcodeMathSubtract()); + } + + public void VisitExpression(MultiplyExpressionNode node) + { + NodeStartHousekeeping(node.ParseNode); + + node.Left.Accept(this); + node.Right.Accept(this); + AddOpcode(new OpcodeMathMultiply()); + } + + public void VisitExpression(DivideExpressionNode node) + { + NodeStartHousekeeping(node.ParseNode); + + node.Left.Accept(this); + node.Right.Accept(this); + AddOpcode(new OpcodeMathDivide()); + } + + public void VisitExpression(PowerExpressionNode node) + { + NodeStartHousekeeping(node.ParseNode); + + node.Left.Accept(this); + node.Right.Accept(this); + AddOpcode(new OpcodeMathPower()); + } + + public void VisitExpression(NegateExpressionNode node) + { + NodeStartHousekeeping(node.ParseNode); + + node.Target.Accept(this); + AddOpcode(new OpcodeMathNegate()); + } + + public void VisitExpression(NotExpressionNode node) + { + NodeStartHousekeeping(node.ParseNode); + + node.Target.Accept(this); + AddOpcode(new OpcodeLogicNot()); + } + + public void VisitExpression(DefinedExpressionNode node) + { + NodeStartHousekeeping(node.ParseNode); + + AddOpcode(new OpcodePush("$" + node.Identifier)); + AddOpcode(new OpcodeExists()); + } + + public void VisitExpression(GetSuffixNode node) + { + NodeStartHousekeeping(node.ParseNode); + + node.Base.Accept(this); + AddOpcode(new OpcodeGetMember(node.Suffix)); + } + + public void VisitExpression(CallSuffixNode node) + { + NodeStartHousekeeping(node.ParseNode); + + node.Base.Accept(this); + AddOpcode(new OpcodeGetMethod(node.Suffix)); + + AddOpcode(new OpcodePush(new KOSArgMarkerType())); + foreach (var arg in node.Arguments) + { + arg.Accept(this); + } + + AddOpcode(new OpcodeCall(string.Empty) { Direct = false }); + } + + public void VisitExpression(GetIndexNode node) + { + NodeStartHousekeeping(node.ParseNode); + + node.Base.Accept(this); + node.Index.Accept(this); + AddOpcode(new OpcodeGetIndex()); + } + + public void VisitExpression(DirectCallNode node) + { + NodeStartHousekeeping(node.ParseNode); + + AddOpcode(new OpcodePush(new KOSArgMarkerType())); + foreach (var arg in node.Arguments) + { + arg.Accept(this); + } + + string directName = node.Identifier; + if (options.FuncManager.Exists(directName)) // if the name is a built-in, then add the "()" after it. + directName += "()"; + AddOpcode(new OpcodeCall(directName)); + } + + public void VisitExpression(IndirectCallNode node) + { + NodeStartHousekeeping(node.ParseNode); + + node.Base.Accept(this); + + AddOpcode(new OpcodePush(new KOSArgMarkerType())); + foreach (var arg in node.Arguments) + { + arg.Accept(this); + } + + AddOpcode(new OpcodeCall(string.Empty) { Direct = false }); + } + + public void VisitExpression(FunctionAddressNode node) + { + NodeStartHousekeeping(node.ParseNode); + + + if (options.FuncManager.Exists(node.Identifier)) // if the name is a built-in, then make a BuiltInDelegate + { + AddOpcode(new OpcodePush(new KOSArgMarkerType())); + AddOpcode(new OpcodePush(node.Identifier)); + AddOpcode(new OpcodeCall("makebuiltindelegate()")); + } + else + { + // It is not a built-in, so instead get its value as a user function pointer variable, despite + // the fact that it's being called AS IF it was direct. + AddOpcode(new OpcodePush("$" + node.Identifier + "*")); + } + } + + public void VisitExpression(LambdaNode node) + { + Opcode skipPastFunctionBody = AddOpcode(new OpcodeBranchJump()); + string functionStartLabel = GetNextLabel(false); + + needImplicitReturn = true; + nextBraceIsFunction = true; + VisitNode(node.ParseNode); // the braces of the anonymous function and its contents get compiled in-line here. + nextBraceIsFunction = false; + if (needImplicitReturn) + // needImplicitReturn is unconditionally true here, but it's being used anyway so we'll find this block + // of code later when we search for "all the places using needImplicitReturn" and perform a refactor + // of the logic for adding implicit returns. + { + AddOpcode(new OpcodePush(0)); // Functions must push a dummy return val when making implicit returns. Locks already leave an expr atop the stack. + AddOpcode(new OpcodeReturn(0)); + } + Opcode afterFunctionBody = AddOpcode(new OpcodePushDelegateRelocateLater(null,true), functionStartLabel); + skipPastFunctionBody.DestinationLabel = afterFunctionBody.Label; + } + + public void VisitExpression(ScalarAtomNode node) + { + NodeStartHousekeeping(node.ParseNode); + + AddOpcode(new OpcodePush(node.Value)); + } + + public void VisitExpression(StringAtomNode node) + { + NodeStartHousekeeping(node.ParseNode); + + AddOpcode(new OpcodePush(new StringValue(node.Value))); + } + + public void VisitExpression(BooleanAtomNode node) + { + NodeStartHousekeeping(node.ParseNode); + + AddOpcode(new OpcodePush(new BooleanValue(node.Value))); + } + + public void VisitExpression(IdentifierAtomNode node) + { + NodeStartHousekeeping(node.ParseNode); + + // Special case when the identifier is known to be a lock. + // Note that this only works when the lock is defined in the SAME + // file. When one script calls another one, the compiler won't know + // that the identifier is a lock, and you'll have to use empty parens + // to make it a real function call like var(): + UserFunction userFuncObject = GetUserFunctionWithScopeWalk(node.Identifier, node.ParseNode); + if (userFuncObject != null) + { + AddOpcode(new OpcodePush(new KOSArgMarkerType())); + AddOpcode(new OpcodeCall(userFuncObject.ScopelessPointerIdentifier)); + } + else + { + AddOpcode(new OpcodePush("$" + node.Identifier)); + } + } } } diff --git a/src/kOS.Safe/Compilation/KS/ExpressionBuilder.cs b/src/kOS.Safe/Compilation/KS/ExpressionBuilder.cs new file mode 100644 index 0000000000..c2d77d70ca --- /dev/null +++ b/src/kOS.Safe/Compilation/KS/ExpressionBuilder.cs @@ -0,0 +1,370 @@ +using System; +using kOS.Safe.Encapsulation; +using kOS.Safe.Exceptions; +namespace kOS.Safe.Compilation.KS +{ + public static class ExpressionBuilder + { + public static ExpressionNode BuildExpression(ParseNode node) + { + switch (node.Token.Type) + { + case TokenType.expr: + // just a wrapper around another node + return BuildExpression(node.Nodes[0]); + case TokenType.or_expr: + return BuildLogicExpression(node, new OrExpressionNode() { ParseNode = node }); + case TokenType.and_expr: + return BuildLogicExpression(node, new AndExpressionNode() { ParseNode = node }); + case TokenType.compare_expr: + return BuildBinaryExpression(node, (separator) => new CompareExpressionNode() { + Comparator = separator.Token.Text, + ParseNode = separator + }); + case TokenType.arith_expr: + return BuildBinaryExpression(node, (separator) => ( + separator.Token.Text == "+" + ? (BinaryExpressionNode) new AddExpressionNode() { ParseNode = separator } + : (BinaryExpressionNode) new SubtractExpressionNode() { ParseNode = separator } + )); + case TokenType.multdiv_expr: + return BuildBinaryExpression(node, (separator) => ( + separator.Token.Type == TokenType.MULT + ? (BinaryExpressionNode) new MultiplyExpressionNode() { ParseNode = separator } + : (BinaryExpressionNode) new DivideExpressionNode() { ParseNode = separator } + )); + case TokenType.unary_expr: + return BuildUnaryExpression(node); + case TokenType.factor: + return BuildBinaryExpression(node, (separator) => new PowerExpressionNode() { ParseNode = separator }); + case TokenType.suffix: + return BuildSuffix(node); + case TokenType.atom: + return BuildAtom(node); + case TokenType.onoff_trailer: + return BuildOnOff(node); + case TokenType.varidentifier: + // just a wrapper around a suffix + return BuildSuffix(node.Nodes[0]); + case TokenType.instruction_block: + return BuildLambda(node); + default: + throw new KOSYouShouldNeverSeeThisException("Unknown expression token: " + node.Token.Type); + } + } + + private static ExpressionNode BuildLogicExpression(ParseNode node, LogicExpressionNode logicNode) + { + // these are of the form + // expr -> sub_expr (SEPARATOR sub_expr)* + // these are logic operations, so we group them all up in one node so we can short circuit easily + + if (node.Nodes.Count == 1) + { + // just a passthrough + return BuildExpression(node.Nodes[0]); + } + + int count = (node.Nodes.Count + 1) / 2; + ExpressionNode[] exprs = new ExpressionNode[count]; + + for (int i = 0; i < count; i++) + { + exprs[i] = BuildExpression(node.Nodes[i*2]); + } + + logicNode.Expressions = exprs; + + return logicNode; + } + + private delegate BinaryExpressionNode BinaryExpressioner(ParseNode separator); + + private static ExpressionNode BuildBinaryExpression(ParseNode node, BinaryExpressioner expressioner) + { + // these all take the form + // expr -> sub_expr (SEPARATOR sub_expr)* + + // start with the leftmost subexpression + ExpressionNode expr = BuildExpression(node.Nodes[0]); + + // build up the tree + for (int i = 1; i < node.Nodes.Count; i += 2) + { + // Construct the parent node from the separator + BinaryExpressionNode next = expressioner(node.Nodes[i]); + // Add its left and right children + next.Left = expr; + next.Right = BuildExpression(node.Nodes[i+1]); + + // this is our new root expression + expr = next; + } + + return expr; + } + + private static ExpressionNode BuildUnaryExpression(ParseNode node) + { + // unary_expr -> (PLUSMINUS|NOT|DEFINED)? factor + + if (node.Nodes.Count == 1) + { + // just a passthrough node + return BuildExpression(node.Nodes[0]); + } + + Token op = node.Nodes[0].Token; + ExpressionNode target = BuildExpression(node.Nodes[1]); + + switch (op.Type) + { + case TokenType.PLUSMINUS: + if (op.Text == "+") + { + // +foo is the same as foo + return target; + } + else + { + return new NegateExpressionNode() { + ParseNode = node, + Target = target + }; + } + case TokenType.NOT: + return new NotExpressionNode() { + ParseNode = node, + Target = target + }; + case TokenType.DEFINED: + if (target is IdentifierAtomNode) + { + return new DefinedExpressionNode() { + ParseNode = node, + Identifier = ((IdentifierAtomNode)target).Identifier + }; + } + else + { + throw new KOSCompileException(node.Token, "DEFINED can only operate on an identifier"); + } + default: + throw new KOSYouShouldNeverSeeThisException("Unexpected unary_expr op: " + op.Type); + } + } + + private static ExpressionNode BuildSuffix(ParseNode node) + { + // suffix -> suffixterm (suffix_trailer)* + + ExpressionNode expr = BuildSuffixTerm(node.Nodes[0], null); + for (int i = 1; i < node.Nodes.Count; i++) + { + // suffix_trailer -> COLON suffixterm + ParseNode trailer = node.Nodes[i]; + + expr = BuildSuffixTerm(trailer.Nodes[1], expr); + } + + return expr; + } + + private static ExpressionNode BuildSuffixTerm(ParseNode node, ExpressionNode baseNode) + { + // suffixterm -> atom suffixterm_trailer* + ExpressionNode expr = BuildExpression(node.Nodes[0]); + + // If we have a baseNode, the first expression is a suffix and must be an IdentifierAtomNode + if (baseNode != null) + { + if (expr is IdentifierAtomNode) + { + expr = new GetSuffixNode() { + ParseNode = node, + Base = baseNode, + Suffix = ((IdentifierAtomNode)expr).Identifier + }; + } + else + { + throw new KOSCompileException(node.Token, "Suffix must be an identifier"); + } + } + + for (int i = 1; i < node.Nodes.Count; i++) + { + // suffixterm_trailer -> (function_trailer | array_trailer) + // immediately unwrap it into the trailer + ParseNode trailer = node.Nodes[i].Nodes[0]; + + switch (trailer.Token.Type) + { + case TokenType.function_trailer: + // function_trailer -> (BRACKETOPEN arglist? BRACKETCLOSE) | ATSIGN + + if (trailer.Nodes[0].Token.Type == TokenType.ATSIGN) + { + // this is only valid on identifiers + if (expr is IdentifierAtomNode) + { + expr = new FunctionAddressNode() { + ParseNode = trailer, + Identifier = ((IdentifierAtomNode)expr).Identifier + }; + } + else + { + throw new KOSCompileException(trailer.Token, "function address only valid on identifiers"); + } + } + else + { + ExpressionNode[] args = new ExpressionNode[0]; + if (trailer.Nodes.Count == 3) + { + args = BuildArglist(trailer.Nodes[1]); + } + + if (expr is IdentifierAtomNode) + { + expr = new DirectCallNode() { + ParseNode = trailer, + Identifier = ((IdentifierAtomNode)expr).Identifier, + Arguments = args + }; + } + else if (expr is GetSuffixNode) + { + expr = new CallSuffixNode() { + ParseNode = trailer, + Base = ((GetSuffixNode)expr).Base, + Suffix = ((GetSuffixNode)expr).Suffix, + Arguments = args + }; + } + else + { + expr = new IndirectCallNode() { + ParseNode = trailer, + Base = expr, + Arguments = args + }; + } + } + break; + case TokenType.array_trailer: + // array_trailer -> (ARRAYINDEX (IDENTIFIER | INTEGER)) | (SQUAREOPEN expr SQUARECLOSE) + // either way the array index is node #1 + expr = new GetIndexNode() { + ParseNode = trailer, + Base = expr, + Index = BuildExpression(trailer.Nodes[1]) + }; + break; + default: + throw new KOSYouShouldNeverSeeThisException("unknown suffixterm_trailer: " + trailer.Token.Type); + } + } + + return expr; + } + + private static ExpressionNode[] BuildArglist(ParseNode node) + { + // arglist -> expr (COMMA expr)* + int count = (node.Nodes.Count + 1) / 2; + ExpressionNode[] args = new ExpressionNode[count]; + + for (int i = 0; i < count; i++) + { + // arguments are children 0, 2, 4, 6, etc. + args[i] = BuildExpression(node.Nodes[i*2]); + } + + return args; + } + + private static ExpressionNode BuildAtom(ParseNode node) + { + // atom -> sci_number | TRUEFALSE | IDENTIFIER | FILEIDENT | STRING | (BRACKETOPEN expr BRACKETCLOSE) + + // If its a parenthesized expression, just return the inner expression + if (node.Nodes.Count == 3) + { + return BuildExpression(node.Nodes[1]); + } + + ParseNode atom = node.Nodes[0]; + + switch (atom.Token.Type) + { + case TokenType.sci_number: + return BuildNumber(atom); + case TokenType.TRUEFALSE: + return new BooleanAtomNode() { + ParseNode = atom, + Value = atom.Token.Text.ToLower() == "true" + }; + case TokenType.IDENTIFIER: + case TokenType.FILEIDENT: + return new IdentifierAtomNode() { + ParseNode = atom, + Identifier = atom.Token.Text + }; + case TokenType.STRING: + // strip off the quotes + return new StringAtomNode() { + ParseNode = atom, + Value = atom.Token.Text.Substring(1, atom.Token.Text.Length - 2) + }; + default: + throw new KOSYouShouldNeverSeeThisException("Unknown atom token: " + atom.Token.Type); + } + } + + private static ExpressionNode BuildNumber(ParseNode node) + { + // sci_number -> number (E PLUSMINUS? INTEGER)? + + // We just join all the strings together, then try to parse it as an int, + // falling back to a double if that doesn't work. + + // number is just a wrapper around a numeric token + string numberText = node.Nodes[0].Nodes[0].Token.Text; + + // add all the suffixes + for (int i = 1; i < node.Nodes.Count; i++) + { + numberText += node.Nodes[i].Token.Text; + } + + numberText = numberText.Replace("_", ""); + + ScalarValue value; + + if (ScalarValue.TryParseInt(numberText, out value) || ScalarValue.TryParseDouble(numberText, out value)) { + return new ScalarAtomNode() { + ParseNode = node, + Value = value + }; + } + + throw new KOSCompileException(node.Token, string.Format(KOSNumberParseException.TERSE_MSG_FMT, node.Token.Text)); + } + + private static ExpressionNode BuildOnOff(ParseNode node) + { + // onoff_trailer -> (ON | OFF) + return new BooleanAtomNode() { + ParseNode = node, + Value = node.Nodes[0].Token.Type == TokenType.ON + }; + } + + private static ExpressionNode BuildLambda(ParseNode node) + { + return new LambdaNode() { ParseNode = node }; + } + } +} diff --git a/src/kOS.Safe/Compilation/KS/ExpressionNode.cs b/src/kOS.Safe/Compilation/KS/ExpressionNode.cs new file mode 100644 index 0000000000..2cdd1e3947 --- /dev/null +++ b/src/kOS.Safe/Compilation/KS/ExpressionNode.cs @@ -0,0 +1,234 @@ +using System; +using kOS.Safe.Encapsulation; +namespace kOS.Safe.Compilation.KS +{ + public abstract class ExpressionNode + { + public ParseNode ParseNode { get; set; } + + public abstract void Accept(IExpressionVisitor visitor); + } + + public abstract class LogicExpressionNode : ExpressionNode + { + public ExpressionNode[] Expressions { get; set; } + } + + public class OrExpressionNode : LogicExpressionNode + { + public override void Accept(IExpressionVisitor visitor) + { + visitor.VisitExpression(this); + } + } + + public class AndExpressionNode : LogicExpressionNode + { + public override void Accept(IExpressionVisitor visitor) + { + visitor.VisitExpression(this); + } + } + + public abstract class BinaryExpressionNode : ExpressionNode + { + public ExpressionNode Left { get; set; } + public ExpressionNode Right { get; set; } + } + + public class CompareExpressionNode : BinaryExpressionNode + { + public string Comparator { get; set; } + + public override void Accept(IExpressionVisitor visitor) + { + visitor.VisitExpression(this); + } + } + + public class AddExpressionNode : BinaryExpressionNode + { + public override void Accept(IExpressionVisitor visitor) + { + visitor.VisitExpression(this); + } + } + + public class SubtractExpressionNode : BinaryExpressionNode + { + public override void Accept(IExpressionVisitor visitor) + { + visitor.VisitExpression(this); + } + } + + public class MultiplyExpressionNode : BinaryExpressionNode + { + public override void Accept(IExpressionVisitor visitor) + { + visitor.VisitExpression(this); + } + } + + public class DivideExpressionNode : BinaryExpressionNode + { + public override void Accept(IExpressionVisitor visitor) + { + visitor.VisitExpression(this); + } + } + + public class PowerExpressionNode : BinaryExpressionNode + { + public override void Accept(IExpressionVisitor visitor) + { + visitor.VisitExpression(this); + } + } + + public class NegateExpressionNode : ExpressionNode + { + public ExpressionNode Target { get; set; } + + public override void Accept(IExpressionVisitor visitor) + { + visitor.VisitExpression(this); + } + } + + public class NotExpressionNode : ExpressionNode + { + public ExpressionNode Target { get; set; } + + public override void Accept(IExpressionVisitor visitor) + { + visitor.VisitExpression(this); + } + } + + public class DefinedExpressionNode : ExpressionNode + { + public string Identifier { get; set; } + + public override void Accept(IExpressionVisitor visitor) + { + visitor.VisitExpression(this); + } + } + + public class GetSuffixNode : ExpressionNode + { + public ExpressionNode Base { get; set; } + public string Suffix { get; set; } + + public override void Accept(IExpressionVisitor visitor) + { + visitor.VisitExpression(this); + } + } + + public class CallSuffixNode : ExpressionNode + { + public ExpressionNode Base { get; set; } + public string Suffix { get; set; } + public ExpressionNode[] Arguments { get; set; } + + public override void Accept(IExpressionVisitor visitor) + { + visitor.VisitExpression(this); + } + } + + public class GetIndexNode : ExpressionNode + { + public ExpressionNode Base { get; set; } + public ExpressionNode Index { get; set; } + + public override void Accept(IExpressionVisitor visitor) + { + visitor.VisitExpression(this); + } + } + + public class DirectCallNode : ExpressionNode + { + public string Identifier { get; set; } + public ExpressionNode[] Arguments { get; set; } + + public override void Accept(IExpressionVisitor visitor) + { + visitor.VisitExpression(this); + } + } + + public class IndirectCallNode : ExpressionNode + { + public ExpressionNode Base { get; set; } + public ExpressionNode[] Arguments { get; set; } + + public override void Accept(IExpressionVisitor visitor) + { + visitor.VisitExpression(this); + } + } + + public class FunctionAddressNode : ExpressionNode + { + public string Identifier { get; set; } + + public override void Accept(IExpressionVisitor visitor) + { + visitor.VisitExpression(this); + } + } + + public class LambdaNode : ExpressionNode + { + // TODO: what needs to be here + + public override void Accept(IExpressionVisitor visitor) + { + visitor.VisitExpression(this); + } + } + + public class ScalarAtomNode : ExpressionNode + { + public ScalarValue Value { get; set; } + + public override void Accept(IExpressionVisitor visitor) + { + visitor.VisitExpression(this); + } + } + + public class StringAtomNode : ExpressionNode + { + public string Value { get; set; } + + public override void Accept(IExpressionVisitor visitor) + { + visitor.VisitExpression(this); + } + } + + public class BooleanAtomNode : ExpressionNode + { + public bool Value { get; set; } + + public override void Accept(IExpressionVisitor visitor) + { + visitor.VisitExpression(this); + } + } + + public class IdentifierAtomNode : ExpressionNode + { + public string Identifier { get; set; } + + public override void Accept(IExpressionVisitor visitor) + { + visitor.VisitExpression(this); + } + } +} diff --git a/src/kOS.Safe/Compilation/KS/IExpressionVisitor.cs b/src/kOS.Safe/Compilation/KS/IExpressionVisitor.cs new file mode 100644 index 0000000000..f5559cb820 --- /dev/null +++ b/src/kOS.Safe/Compilation/KS/IExpressionVisitor.cs @@ -0,0 +1,29 @@ +using System; +namespace kOS.Safe.Compilation.KS +{ + public interface IExpressionVisitor + { + void VisitExpression(OrExpressionNode node); + void VisitExpression(AndExpressionNode node); + void VisitExpression(CompareExpressionNode node); + void VisitExpression(AddExpressionNode node); + void VisitExpression(SubtractExpressionNode node); + void VisitExpression(MultiplyExpressionNode node); + void VisitExpression(DivideExpressionNode node); + void VisitExpression(PowerExpressionNode node); + void VisitExpression(NegateExpressionNode node); + void VisitExpression(NotExpressionNode node); + void VisitExpression(DefinedExpressionNode node); + void VisitExpression(GetSuffixNode node); + void VisitExpression(CallSuffixNode node); + void VisitExpression(GetIndexNode node); + void VisitExpression(DirectCallNode node); + void VisitExpression(IndirectCallNode node); + void VisitExpression(FunctionAddressNode node); + void VisitExpression(LambdaNode node); + void VisitExpression(ScalarAtomNode node); + void VisitExpression(StringAtomNode node); + void VisitExpression(BooleanAtomNode node); + void VisitExpression(IdentifierAtomNode node); + } +} diff --git a/src/kOS.Safe/kOS.Safe.csproj b/src/kOS.Safe/kOS.Safe.csproj index 2bab712165..7fc836ebe2 100644 --- a/src/kOS.Safe/kOS.Safe.csproj +++ b/src/kOS.Safe/kOS.Safe.csproj @@ -284,6 +284,9 @@ + + + From 8ac7c3b05c4d3781a9115818694fec1ab8f31f65 Mon Sep 17 00:00:00 2001 From: Tom Holmes Date: Fri, 1 Dec 2017 18:40:18 -0800 Subject: [PATCH 4/4] Add more comments --- src/kOS.Safe/Compilation/KS/Compiler.cs | 2 ++ src/kOS.Safe/Compilation/KS/ExpressionBuilder.cs | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/src/kOS.Safe/Compilation/KS/Compiler.cs b/src/kOS.Safe/Compilation/KS/Compiler.cs index 84ce6b12d9..17765a0bae 100644 --- a/src/kOS.Safe/Compilation/KS/Compiler.cs +++ b/src/kOS.Safe/Compilation/KS/Compiler.cs @@ -1892,6 +1892,8 @@ private StorageModifier GetStorageModifierForDeclare(ParseNode node) private void VisitToggleStatement(ParseNode node) { NodeStartHousekeeping(node); + // process this as + // SET foo TO NOT foo ExpressionNode identifier = ExpressionBuilder.BuildExpression(node.Nodes[1]); ExpressionNode target = new NegateExpressionNode() { ParseNode = node, diff --git a/src/kOS.Safe/Compilation/KS/ExpressionBuilder.cs b/src/kOS.Safe/Compilation/KS/ExpressionBuilder.cs index c2d77d70ca..bd0d6a1a07 100644 --- a/src/kOS.Safe/Compilation/KS/ExpressionBuilder.cs +++ b/src/kOS.Safe/Compilation/KS/ExpressionBuilder.cs @@ -226,8 +226,10 @@ private static ExpressionNode BuildSuffixTerm(ParseNode node, ExpressionNode bas args = BuildArglist(trailer.Nodes[1]); } + // Depending on what we are calling, construct a different semantic node if (expr is IdentifierAtomNode) { + // identifiers are direct calls expr = new DirectCallNode() { ParseNode = trailer, Identifier = ((IdentifierAtomNode)expr).Identifier, @@ -236,6 +238,7 @@ private static ExpressionNode BuildSuffixTerm(ParseNode node, ExpressionNode bas } else if (expr is GetSuffixNode) { + // GetSuffixNodes are replaced with CallSuffixNodes expr = new CallSuffixNode() { ParseNode = trailer, Base = ((GetSuffixNode)expr).Base, @@ -245,6 +248,9 @@ private static ExpressionNode BuildSuffixTerm(ParseNode node, ExpressionNode bas } else { + // Everything else is an indirect call + // These could be things like + // foo[1]() expr = new IndirectCallNode() { ParseNode = trailer, Base = expr,