From def3b28b2a88d4028fafd72d9be4da8f28b88f9c Mon Sep 17 00:00:00 2001 From: Antonia Heinen Date: Thu, 14 Jan 2021 10:56:25 +0100 Subject: [PATCH] Intermediate commit because need to checkout other branch --- .../Analysis/AbstractSyntaxTree/IBindable.cs | 5 +-- ... => BinaryOperationChainExpressionNode.cs} | 33 ++++++++++++++++--- .../BinaryOperationExpressionNode.cs | 5 +++ .../Nodes/Expressions/ExpressionNode.cs | 10 ++++++ .../AbstractSyntaxTree/Nodes/INode.cs | 17 ++++++++++ .../Analysis/AbstractSyntaxTree/Nodes/Node.cs | 32 +++++++++++++++++- .../AbstractSyntaxTree/Nodes/ParameterNode.cs | 3 ++ .../AbstractSyntaxTree/Nodes/ScriptNode.cs | 17 +++++++--- .../Nodes/StatementCollectionNode.cs | 10 ++++++ .../Analysis/Grammatical/ExpressionParser.cs | 4 +-- Source/Analysis/Lexical/Lexemes/ILexeme.cs | 9 +++++ .../Lexical/Lexemes/IOperatorLexeme.cs | 8 +---- Source/Analysis/Lexical/Lexemes/Lexeme.cs | 2 +- .../Semantical/Binding/TypeAgnosticBinder.cs | 2 +- UnitTests/BindingTests.cs | 28 ++++++++-------- UnitTests/ParserTests.cs | 2 +- UnitTests/ScriptTests.cs | 12 +++---- 17 files changed, 156 insertions(+), 43 deletions(-) rename Source/Analysis/AbstractSyntaxTree/Nodes/Expressions/{BinaryOperationChainNode.cs => BinaryOperationChainExpressionNode.cs} (81%) create mode 100644 Source/Analysis/AbstractSyntaxTree/Nodes/INode.cs create mode 100644 Source/Analysis/Lexical/Lexemes/ILexeme.cs diff --git a/Source/Analysis/AbstractSyntaxTree/IBindable.cs b/Source/Analysis/AbstractSyntaxTree/IBindable.cs index 9a8649c..6a5223e 100644 --- a/Source/Analysis/AbstractSyntaxTree/IBindable.cs +++ b/Source/Analysis/AbstractSyntaxTree/IBindable.cs @@ -1,9 +1,10 @@ -using Krypton.Analysis.AbstractSyntaxTree.Nodes.Identifiers; +using Krypton.Analysis.AbstractSyntaxTree.Nodes; +using Krypton.Analysis.AbstractSyntaxTree.Nodes.Identifiers; using Krypton.Analysis.AbstractSyntaxTree.Nodes.Symbols; namespace Krypton.Analysis.AbstractSyntaxTree { - public interface IBindable + public interface IBindable : INode { IdentifierNode IdentifierNode { get; } diff --git a/Source/Analysis/AbstractSyntaxTree/Nodes/Expressions/BinaryOperationChainNode.cs b/Source/Analysis/AbstractSyntaxTree/Nodes/Expressions/BinaryOperationChainExpressionNode.cs similarity index 81% rename from Source/Analysis/AbstractSyntaxTree/Nodes/Expressions/BinaryOperationChainNode.cs rename to Source/Analysis/AbstractSyntaxTree/Nodes/Expressions/BinaryOperationChainExpressionNode.cs index 9c709b5..6e05767 100644 --- a/Source/Analysis/AbstractSyntaxTree/Nodes/Expressions/BinaryOperationChainNode.cs +++ b/Source/Analysis/AbstractSyntaxTree/Nodes/Expressions/BinaryOperationChainExpressionNode.cs @@ -7,9 +7,22 @@ namespace Krypton.Analysis.AbstractSyntaxTree.Nodes.Expressions { - public sealed class BinaryOperationChainNode : ExpressionNode + /* A BinaryOperationChainExpressionNode is a helper node + * that should never escape grammatical analysis. + * It represents multiple binary operations chained one + * after the other (e.g. 4 + 5 * 6). + * Its Resolve method performs the act of turning this + * into the tree 4 + (5 * 6). + * It saves an ordered list of ExpressionNodes that represents + * the operands (0 op 1 op 2 op 3 ...) and one that represents + * the operators (ex 0 ex 1 ex 2 ex ...). In a valid state + * there is exactly one more operand than operator. + * An operation chain can only resolved if it is in such + * a valid state. + */ + public sealed class BinaryOperationChainExpressionNode : ExpressionNode { - public BinaryOperationChainNode(int lineNumber) : base(lineNumber) { } + public BinaryOperationChainExpressionNode(int lineNumber) : base(lineNumber) { } private readonly List operators = new(); private readonly List operands = new(); @@ -27,9 +40,21 @@ public void AddOperand(ExpressionNode operand) operands.Add(operand); } - public override BinaryOperationChainNode Clone() + public override BinaryOperationChainExpressionNode Clone() { - throw new NotSupportedException(); + Debug.Assert(operands.Count == operators.Count + 1); + + BinaryOperationChainExpressionNode newChain = new(LineNumber); + + for (int i = 0; i < operators.Count; i++) + { + newChain.AddOperand(operands[i]); + newChain.AddOperator(operators[2]); + } + + newChain.AddOperand(operands[^1]); + + return newChain; } public override void PopulateBranches(List list) diff --git a/Source/Analysis/AbstractSyntaxTree/Nodes/Expressions/BinaryOperationExpressionNode.cs b/Source/Analysis/AbstractSyntaxTree/Nodes/Expressions/BinaryOperationExpressionNode.cs index c0cb980..67063fb 100644 --- a/Source/Analysis/AbstractSyntaxTree/Nodes/Expressions/BinaryOperationExpressionNode.cs +++ b/Source/Analysis/AbstractSyntaxTree/Nodes/Expressions/BinaryOperationExpressionNode.cs @@ -2,6 +2,11 @@ namespace Krypton.Analysis.AbstractSyntaxTree.Nodes.Expressions { + /* A BinaryOperationExpressionNode is any expression + * with a concrete operator lexeme and a sub expression + * on the left hand side and one on the right hand + * side. + */ public abstract class BinaryOperationExpressionNode : ExpressionNode { protected BinaryOperationExpressionNode(ExpressionNode left, ExpressionNode right, int lineNumber) : base(lineNumber) diff --git a/Source/Analysis/AbstractSyntaxTree/Nodes/Expressions/ExpressionNode.cs b/Source/Analysis/AbstractSyntaxTree/Nodes/Expressions/ExpressionNode.cs index 4a38a11..25b1659 100644 --- a/Source/Analysis/AbstractSyntaxTree/Nodes/Expressions/ExpressionNode.cs +++ b/Source/Analysis/AbstractSyntaxTree/Nodes/Expressions/ExpressionNode.cs @@ -1,5 +1,15 @@ namespace Krypton.Analysis.AbstractSyntaxTree.Nodes.Expressions { + /* An ExpressionNode represents an expression according + * to the spec, so for example: + * - an argument + * - a variable initializer + * - a return value + * - a sub expression + * Branches: none as defined by this abstract class + * LineNumber: the line number of the first lexeme + * that makes up the expression + */ public abstract class ExpressionNode : Node { protected ExpressionNode(int lineNumber) : base(lineNumber) { } diff --git a/Source/Analysis/AbstractSyntaxTree/Nodes/INode.cs b/Source/Analysis/AbstractSyntaxTree/Nodes/INode.cs new file mode 100644 index 0000000..2420c8b --- /dev/null +++ b/Source/Analysis/AbstractSyntaxTree/Nodes/INode.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace Krypton.Analysis.AbstractSyntaxTree.Nodes +{ + public interface INode + { + int LineNumber { get; } + + Node? Parent { get; } + + Node Clone(); + + List GetBranches(); + + void PopulateBranches(List list); + } +} diff --git a/Source/Analysis/AbstractSyntaxTree/Nodes/Node.cs b/Source/Analysis/AbstractSyntaxTree/Nodes/Node.cs index 7077f66..16d3961 100644 --- a/Source/Analysis/AbstractSyntaxTree/Nodes/Node.cs +++ b/Source/Analysis/AbstractSyntaxTree/Nodes/Node.cs @@ -3,8 +3,38 @@ namespace Krypton.Analysis.AbstractSyntaxTree.Nodes { + /* A node is a part of the abstract syntax tree. + * Each node also saves the exact line in which + * it occurred in the original code. This is to + * make error messages better. Most nodes logically + * consist of multiple lexemes, so the line number + * will be the line number of the first lexemes + * that makes up the node (for example, the line + * number of an IfStatementNode is the line number + * of the "If" keyword). The first line is 1. If + * the line number is 0, then the node is synthesised + * by the compiler or it representes a builtin type, + * function or constant. + * As the node is part of a tree, it is recursive in + * that every node may save references to other nodes. + * These are called branches of the node. + * The parent of a node is the single node that owns + * a reference to this node in the tree. Some nodes + * like BoundIdentifierNode point across the tree + * to another node. These nodes are not considered + * parent of the other node. The textual equivalence + * of the parent is seen close to the textual equivalence + * of the node in the original code. This does not + * apply to identifier that point across the code. + * A node is able to clone itself. This means that + * a new instance of the node type is created. Every + * branch of the node is cloned as well and put in + * place of the old reference as the branch of the + * cloned node. This rule also applies to the cloning + * of the branches. + */ [DebuggerDisplay("{GetType().Name} -- {GetHashCode()}")] - public abstract class Node + public abstract class Node : INode { protected Node(int lineNumber) { diff --git a/Source/Analysis/AbstractSyntaxTree/Nodes/ParameterNode.cs b/Source/Analysis/AbstractSyntaxTree/Nodes/ParameterNode.cs index 1d195df..1c05d33 100644 --- a/Source/Analysis/AbstractSyntaxTree/Nodes/ParameterNode.cs +++ b/Source/Analysis/AbstractSyntaxTree/Nodes/ParameterNode.cs @@ -4,6 +4,9 @@ namespace Krypton.Analysis.AbstractSyntaxTree.Nodes { + /* A ParameterNode represents the declaration of + * a parameter of a function. It is used by + */ public sealed class ParameterNode : Node { public ParameterNode(string identifier, TypeSymbolNode type, int lineNumber) : base(lineNumber) diff --git a/Source/Analysis/AbstractSyntaxTree/Nodes/ScriptNode.cs b/Source/Analysis/AbstractSyntaxTree/Nodes/ScriptNode.cs index 5859697..b401e74 100644 --- a/Source/Analysis/AbstractSyntaxTree/Nodes/ScriptNode.cs +++ b/Source/Analysis/AbstractSyntaxTree/Nodes/ScriptNode.cs @@ -2,24 +2,33 @@ namespace Krypton.Analysis.AbstractSyntaxTree.Nodes { + /* A ScriptNode is the root of the syntax tree. + * It represents the whole script. Therefore, it + * saves the top level statements of the script + * and all declared symbols (none yet, but it will + * save functions etc.) + * Branches: + * - A StatementCollection that represents the + * top level statements + */ public sealed class ScriptNode : Node { public ScriptNode(StatementCollectionNode statements, int lineNumber) : base(lineNumber) { - Statements = statements; + TopLevelStatements = statements; } - public StatementCollectionNode Statements { get; } + public StatementCollectionNode TopLevelStatements { get; } public override ScriptNode Clone() { - return new(Statements.Clone(), LineNumber); + return new(TopLevelStatements.Clone(), LineNumber); } public override void PopulateBranches(List list) { list.Add(this); - Statements.PopulateBranches(list); + TopLevelStatements.PopulateBranches(list); } } } diff --git a/Source/Analysis/AbstractSyntaxTree/Nodes/StatementCollectionNode.cs b/Source/Analysis/AbstractSyntaxTree/Nodes/StatementCollectionNode.cs index 4b02783..79ff935 100644 --- a/Source/Analysis/AbstractSyntaxTree/Nodes/StatementCollectionNode.cs +++ b/Source/Analysis/AbstractSyntaxTree/Nodes/StatementCollectionNode.cs @@ -5,6 +5,16 @@ namespace Krypton.Analysis.AbstractSyntaxTree.Nodes { + /* A StatementCollectionNode is a collection of statements. + * It is used by the ScriptNode to represents top level + * statements, by control statements like Block or While + * to represents its nested statements, etc. + * Branches: + * - An ordered list of StatementNodes. + * LineNumber: + * - The line number of the first statement + * - If there are no statements: 0 + */ public sealed class StatementCollectionNode : Node, IIndexedEnumerable, IEnumerable { public StatementCollectionNode(IEnumerable statements) : base(statements.FirstOrDefault()?.LineNumber ?? 0) diff --git a/Source/Analysis/Grammatical/ExpressionParser.cs b/Source/Analysis/Grammatical/ExpressionParser.cs index 03f0f22..24a6508 100644 --- a/Source/Analysis/Grammatical/ExpressionParser.cs +++ b/Source/Analysis/Grammatical/ExpressionParser.cs @@ -39,7 +39,7 @@ public ExpressionParser(LexemeCollection lexemes) ParseAfterSubExpression(ref root, ref index); - if (root is BinaryOperationChainNode chain) + if (root is BinaryOperationChainExpressionNode chain) { root = chain.Resolve(); } @@ -126,7 +126,7 @@ private void ParseOperationChain(IOperatorLexeme opLexeme, ref ExpressionNode? r return; } - if (root is not BinaryOperationChainNode chain) + if (root is not BinaryOperationChainExpressionNode chain) { chain = new(opLexeme.LineNumber); chain.AddOperand(root); diff --git a/Source/Analysis/Lexical/Lexemes/ILexeme.cs b/Source/Analysis/Lexical/Lexemes/ILexeme.cs new file mode 100644 index 0000000..2689fee --- /dev/null +++ b/Source/Analysis/Lexical/Lexemes/ILexeme.cs @@ -0,0 +1,9 @@ +namespace Krypton.Analysis.Lexical.Lexemes +{ + public interface ILexeme + { + string Content { get; } + + int LineNumber { get; } + } +} diff --git a/Source/Analysis/Lexical/Lexemes/IOperatorLexeme.cs b/Source/Analysis/Lexical/Lexemes/IOperatorLexeme.cs index d49e955..59935c8 100644 --- a/Source/Analysis/Lexical/Lexemes/IOperatorLexeme.cs +++ b/Source/Analysis/Lexical/Lexemes/IOperatorLexeme.cs @@ -5,14 +5,8 @@ namespace Krypton.Analysis.Lexical.Lexemes // This interface is intended only to be implemented by classes that inherited from // Krypton.Analysis.Lexical.Lexemes.Lexeme. It can't be a class, because both // KeywordLexemes and SyntaxCharacterLexemes can be binary operators. - public interface IOperatorLexeme + public interface IOperatorLexeme : ILexeme { - // All classes that inherit from Lexeme already own this member. - string Content { get; } - - // Same here. - int LineNumber { get; } - OperatorPrecedenceGroup PrecedenceGroup { get; } } } diff --git a/Source/Analysis/Lexical/Lexemes/Lexeme.cs b/Source/Analysis/Lexical/Lexemes/Lexeme.cs index cf26cba..b2729af 100644 --- a/Source/Analysis/Lexical/Lexemes/Lexeme.cs +++ b/Source/Analysis/Lexical/Lexemes/Lexeme.cs @@ -3,7 +3,7 @@ namespace Krypton.Analysis.Lexical.Lexemes { [DebuggerDisplay("{DebuggerDisplay()}")] - public abstract class Lexeme + public abstract class Lexeme : ILexeme { protected Lexeme(int lineNumber) { diff --git a/Source/Analysis/Semantical/Binding/TypeAgnosticBinder.cs b/Source/Analysis/Semantical/Binding/TypeAgnosticBinder.cs index f86f635..5e2490e 100644 --- a/Source/Analysis/Semantical/Binding/TypeAgnosticBinder.cs +++ b/Source/Analysis/Semantical/Binding/TypeAgnosticBinder.cs @@ -24,7 +24,7 @@ public bool PerformBinding() // Bind top level statements LocalVariableIdentifierMap localVariableIdentifierMap = new(); - if (!BindLocalVariables(syntaxTree.Root.Statements, localVariableIdentifierMap, globalIdentifierMap)) + if (!BindLocalVariables(syntaxTree.Root.TopLevelStatements, localVariableIdentifierMap, globalIdentifierMap)) { return false; } diff --git a/UnitTests/BindingTests.cs b/UnitTests/BindingTests.cs index 699fead..814a8f4 100644 --- a/UnitTests/BindingTests.cs +++ b/UnitTests/BindingTests.cs @@ -26,11 +26,11 @@ public void SimpleBindingTest() SyntaxTree? tree = Analyser.Analyse(Code); Assert.NotNull(tree); - Assert.IsInstanceOf(tree!.Root.Statements[0]); - Assert.IsInstanceOf(tree.Root.Statements[1]); + Assert.IsInstanceOf(tree!.Root.TopLevelStatements[0]); + Assert.IsInstanceOf(tree.Root.TopLevelStatements[1]); - var decl = (VariableDeclarationStatementNode)tree!.Root.Statements[0]; - var assg = (VariableAssignmentStatementNode)tree!.Root.Statements[1]; + var decl = (VariableDeclarationStatementNode)tree!.Root.TopLevelStatements[0]; + var assg = (VariableAssignmentStatementNode)tree!.Root.TopLevelStatements[1]; Assert.IsInstanceOf(decl.VariableIdentifierNode); Assert.IsInstanceOf(assg.VariableIdentifierNode); @@ -66,8 +66,8 @@ public void MultibleVariablesBindingTest() Assert.NotNull(tree); - var decl1 = tree!.Root.Statements[0] as VariableDeclarationStatementNode; - var decl2 = tree!.Root.Statements[1] as VariableDeclarationStatementNode; + var decl1 = tree!.Root.TopLevelStatements[0] as VariableDeclarationStatementNode; + var decl2 = tree!.Root.TopLevelStatements[1] as VariableDeclarationStatementNode; Assert.NotNull(decl1); Assert.NotNull(decl2); @@ -114,9 +114,9 @@ public void BuiltinFunctionBindingTest() SyntaxTree? tree = Analyser.Analyse(Code); Assert.NotNull(tree); - Assert.IsInstanceOf(tree!.Root.Statements[0]); + Assert.IsInstanceOf(tree!.Root.TopLevelStatements[0]); - var call = (FunctionCallStatementNode)tree.Root.Statements[0]; + var call = (FunctionCallStatementNode)tree.Root.TopLevelStatements[0]; Assert.IsInstanceOf(call.FunctionExpression); @@ -144,11 +144,11 @@ public void TypeBindingTest() SyntaxTree? tree = Analyser.Analyse(Code); Assert.NotNull(tree); - Assert.AreEqual(2, tree!.Root.Statements.Count); - Assert.IsInstanceOf(tree.Root.Statements[0]); - Assert.IsInstanceOf(tree.Root.Statements[1]); + Assert.AreEqual(2, tree!.Root.TopLevelStatements.Count); + Assert.IsInstanceOf(tree.Root.TopLevelStatements[0]); + Assert.IsInstanceOf(tree.Root.TopLevelStatements[1]); - var decl = (VariableDeclarationStatementNode)tree.Root.Statements[0]; + var decl = (VariableDeclarationStatementNode)tree.Root.TopLevelStatements[0]; Assert.NotNull(decl.Type); Assert.IsInstanceOf(decl.Type); @@ -179,7 +179,7 @@ public void MoreTypeBindingTest() SyntaxTree? tree = Analyser.Analyse(Code); Assert.NotNull(tree); - Assert.AreEqual(4, tree!.Root.Statements.Count); + Assert.AreEqual(4, tree!.Root.TopLevelStatements.Count); BuiltinIdentifierMap bim = new(); SymbolNode?[] symbols = @@ -192,7 +192,7 @@ public void MoreTypeBindingTest() int i = 0; - foreach (var statement in tree.Root.Statements) + foreach (var statement in tree.Root.TopLevelStatements) { Assert.IsInstanceOf(statement); diff --git a/UnitTests/ParserTests.cs b/UnitTests/ParserTests.cs index be0bba2..e0af552 100644 --- a/UnitTests/ParserTests.cs +++ b/UnitTests/ParserTests.cs @@ -75,7 +75,7 @@ public void SeveralNestedParensTest() [Test] public void ChainResolutionTest() { - BinaryOperationChainNode chain = new(1); // Arbitrary line number + BinaryOperationChainExpressionNode chain = new(1); // Arbitrary line number chain.AddOperand(new IntegerLiteralExpressionNode(4, 1)); chain.AddOperator(new CharacterOperatorLexeme(CharacterOperator.Plus, 1)); chain.AddOperand(new IntegerLiteralExpressionNode(4, 1)); diff --git a/UnitTests/ScriptTests.cs b/UnitTests/ScriptTests.cs index 85824cd..84b2e26 100644 --- a/UnitTests/ScriptTests.cs +++ b/UnitTests/ScriptTests.cs @@ -34,14 +34,14 @@ While True ... 3 ScriptNode scriptNode = tree.Root; - Assert.AreEqual(4, scriptNode.Statements.Count); + Assert.AreEqual(4, scriptNode.TopLevelStatements.Count); - Assert.IsInstanceOf(scriptNode.Statements[0]); - Assert.IsInstanceOf(scriptNode.Statements[1]); - Assert.IsInstanceOf(scriptNode.Statements[2]); - Assert.IsInstanceOf(scriptNode.Statements[3]); + Assert.IsInstanceOf(scriptNode.TopLevelStatements[0]); + Assert.IsInstanceOf(scriptNode.TopLevelStatements[1]); + Assert.IsInstanceOf(scriptNode.TopLevelStatements[2]); + Assert.IsInstanceOf(scriptNode.TopLevelStatements[3]); - WhileStatementNode @while = (WhileStatementNode)scriptNode.Statements[3]; + WhileStatementNode @while = (WhileStatementNode)scriptNode.TopLevelStatements[3]; Assert.AreEqual(1, @while.Statements.Count); }