From 4f1e820006867bc4406903dd1d46f4818442fd1f Mon Sep 17 00:00:00 2001 From: arookas Date: Sun, 6 Dec 2015 23:15:02 -0500 Subject: [PATCH] Added code so far. --- AssemblyInfo.cs | 16 ++ ast/nodes.cs | 127 +++++++++++ ast/nodes.expressions.cs | 122 +++++++++++ ast/nodes.flow.cs | 224 +++++++++++++++++++ ast/nodes.functions.cs | 95 ++++++++ ast/nodes.literals.cs | 201 +++++++++++++++++ ast/nodes.operators.cs | 453 +++++++++++++++++++++++++++++++++++++++ ast/nodes.statements.cs | 50 +++++ ast/nodes.system.cs | 155 ++++++++++++++ ast/nodes.variables.cs | 96 +++++++++ compiler.cs | 81 +++++++ context.cs | 250 +++++++++++++++++++++ data table.cs | 42 ++++ exceptions.cs | 244 +++++++++++++++++++++ import table.cs | 58 +++++ loop stack.cs | 79 +++++++ main.cs | 70 ++++++ parser.cs | 322 ++++++++++++++++++++++++++++ scope stack.cs | 93 ++++++++ sunscript.grammar | 219 +++++++++++++++++++ symbol table.cs | 212 ++++++++++++++++++ writer.cs | 188 ++++++++++++++++ 22 files changed, 3397 insertions(+) create mode 100644 AssemblyInfo.cs create mode 100644 ast/nodes.cs create mode 100644 ast/nodes.expressions.cs create mode 100644 ast/nodes.flow.cs create mode 100644 ast/nodes.functions.cs create mode 100644 ast/nodes.literals.cs create mode 100644 ast/nodes.operators.cs create mode 100644 ast/nodes.statements.cs create mode 100644 ast/nodes.system.cs create mode 100644 ast/nodes.variables.cs create mode 100644 compiler.cs create mode 100644 context.cs create mode 100644 data table.cs create mode 100644 exceptions.cs create mode 100644 import table.cs create mode 100644 loop stack.cs create mode 100644 main.cs create mode 100644 parser.cs create mode 100644 scope stack.cs create mode 100644 sunscript.grammar create mode 100644 symbol table.cs create mode 100644 writer.cs diff --git a/AssemblyInfo.cs b/AssemblyInfo.cs new file mode 100644 index 0000000..235ad97 --- /dev/null +++ b/AssemblyInfo.cs @@ -0,0 +1,16 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("ssc")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ssc")] +[assembly: AssemblyCopyright("Copyright © 2015 arookas")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("5413c8e1-aefa-43bc-9fe5-c95f221da8c5")] +[assembly: AssemblyVersion("0.1.0.0")] +[assembly: AssemblyFileVersion("0.1.0.0")] diff --git a/ast/nodes.cs b/ast/nodes.cs new file mode 100644 index 0000000..9fcfe93 --- /dev/null +++ b/ast/nodes.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace arookas +{ + public class sunSourceLocation + { + public string File { get; private set; } + public int Line { get; private set; } + public int Column { get; private set; } + + public sunSourceLocation(string file, int line, int column) + { + File = file; + Line = line; + Column = column; + } + + public override string ToString() + { + return String.Format("\"{0}\", ({1},{2})", File, Line, Column); + } + } + + class sunNode : IEnumerable + { + List children; + + public sunNode Parent { get; private set; } + public sunSourceLocation Location { get; private set; } + + public int Count { get { return children.Count; } } + public sunNode this[int index] { get { return index >= 0 && index < Count ? children[index] : null; } } + + public bool IsRoot { get { return Parent == null; } } + public bool IsBranch { get { return Count > 0; } } + public bool IsLeaf { get { return Count < 1; } } + + public sunNode(sunSourceLocation location) + { + children = new List(5); + Location = location; + } + + public void Add(sunNode node) + { + if (node == null) + { + throw new ArgumentNullException("node"); + } + if (node.Parent != null) + { + node.Parent.Remove(node); + } + node.Parent = this; + children.Add(node); + } + public void Remove(sunNode node) + { + if (node == null) + { + throw new ArgumentNullException("node"); + } + if (node.Parent == this) + { + children.Remove(node); + node.Parent = null; + } + } + public void Clear() + { + foreach (var child in this) + { + child.Parent = null; + } + children.Clear(); + } + + public virtual void Compile(sunContext context) + { + // Simply compile all children nodes by default. This is here for the transcient nodes' implementations + // (sunStatement, sunCompoundStatement, etc.) so I only have to type this once. sunExpression is careful + // to override this with the custom shunting-yard algorithm implementation. + foreach (var child in this) + { + child.Compile(context); + } + } + protected bool TryCompile(sunNode node, sunContext context) + { + if (node != null) + { + node.Compile(context); + return true; + } + return false; + } + + public IEnumerator GetEnumerator() { return children.GetEnumerator(); } + IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } + } + + abstract class sunToken : sunNode + { + public TValue Value { get; private set; } + + protected sunToken(sunSourceLocation location, string token) + : base(location) + { + Value = ParseValue(token); + } + + protected abstract TValue ParseValue(string token); + } + + abstract class sunRawToken : sunToken + { + protected sunRawToken(sunSourceLocation location, string token) + : base(location, token) + { + + } + + protected override string ParseValue(string token) { return token; } + } +} diff --git a/ast/nodes.expressions.cs b/ast/nodes.expressions.cs new file mode 100644 index 0000000..088b9ad --- /dev/null +++ b/ast/nodes.expressions.cs @@ -0,0 +1,122 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace arookas +{ + class sunExpression : sunNode + { + public sunExpression(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) + { + Stack operatorStack = new Stack(32); + AnalyzeExpression(context, this, operatorStack); + } + + void AnalyzeExpression(sunContext context, sunExpression expression, Stack operatorStack) + { + // this implementation assumes that the expression production child list alternates between operand and operator + // we can safely assume this as the grammar "operand {binary_operator operand}" enforces it + int stackCount = operatorStack.Count; + foreach (var node in expression) + { + if (node is sunOperand) + { + var operand = node as sunOperand; + + // term + var term = operand.Term; + if (term is sunExpression) + { + AnalyzeExpression(context, term as sunExpression, operatorStack); + } + else + { + term.Compile(context); + } + var unaryOperators = operand.UnaryOperators; + if (unaryOperators != null) + { + unaryOperators.Compile(context); + } + } + else if (node is sunOperator) + { + var operatorNode = node as sunOperator; + while (operatorStack.Count > stackCount && + (operatorNode.IsLeftAssociative && operatorNode.Precedence <= operatorStack.Peek().Precedence) || + (operatorNode.IsRightAssociative && operatorNode.Precedence < operatorStack.Peek().Precedence)) + { + operatorStack.Pop().Compile(context); + } + operatorStack.Push(operatorNode); + } + } + while (operatorStack.Count > stackCount) + { + operatorStack.Pop().Compile(context); + } + } + } + + class sunOperand : sunNode + { + public sunNode UnaryOperators { get { return Count > 1 ? this[0] : null; } } + public sunNode Term { get { return this[Count - 1]; } } + + public sunOperand(sunSourceLocation location) + : base(location) + { + + } + + // operands are compiled in sunExpression.Compile + } + + class sunUnaryOperatorList : sunNode + { + public sunUnaryOperatorList(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) + { + foreach (var child in this.Reverse()) + { + // compile unary operators in reverse order + child.Compile(context); + } + } + } + + class sunTernaryOperator : sunNode + { + public sunExpression Condition { get { return this[0] as sunExpression; } } + public sunExpression TrueBody { get { return this[1] as sunExpression; } } + public sunExpression FalseBody { get { return this[2] as sunExpression; } } + + public sunTernaryOperator(sunSourceLocation node) + : base(node) + { + + } + + public override void Compile(sunContext context) + { + Condition.Compile(context); + var falsePrologue = context.Text.GotoIfZero(); + TrueBody.Compile(context); + var trueEpilogue = context.Text.Goto(); + context.Text.ClosePoint(falsePrologue); + FalseBody.Compile(context); + context.Text.ClosePoint(trueEpilogue); + } + } +} diff --git a/ast/nodes.flow.cs b/ast/nodes.flow.cs new file mode 100644 index 0000000..a7d6df7 --- /dev/null +++ b/ast/nodes.flow.cs @@ -0,0 +1,224 @@ +using PerCederberg.Grammatica.Runtime; +using System.Linq; + +namespace arookas +{ + class sunIf : sunNode + { + public sunExpression Condition { get { return this[0] as sunExpression; } } + public sunNode TrueBody { get { return this[1]; } } + public sunNode FalseBody { get { return this[2]; } } + + public sunIf(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) + { + Condition.Compile(context); + var trueBodyEpilogue = context.Text.GotoIfZero(); + TrueBody.Compile(context); + var falseBody = FalseBody; + if (falseBody != null) + { + var falseBodyEpilogue = context.Text.Goto(); + context.Text.ClosePoint(trueBodyEpilogue); + falseBody.Compile(context); + context.Text.ClosePoint(falseBodyEpilogue); + } + else + { + context.Text.ClosePoint(trueBodyEpilogue); + } + } + } + + abstract class sunLoop : sunNode + { + public bool IsNamed { get { return NameLabel != null; } } + public sunNameLabel NameLabel { get { return this[0] as sunNameLabel; } } + + protected sunLoop(sunSourceLocation location) + : base(location) + { + + } + } + + class sunWhile : sunLoop + { + public sunExpression Condition { get { return this[Count - 2] as sunExpression; } } + public sunNode Body { get { return this[Count - 1]; } } + + public sunWhile(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) + { + context.Loops.Push(IsNamed ? NameLabel.Label.Value : null); + var bodyPrologue = context.Text.OpenPoint(); + var continuePoint = context.Text.OpenPoint(); + Condition.Compile(context); + var bodyEpilogue = context.Text.GotoIfZero(); + Body.Compile(context); + context.Text.Goto(bodyPrologue); + context.Text.ClosePoint(bodyEpilogue); + var breakPoint = context.Text.OpenPoint(); + context.Loops.Pop(context, breakPoint, continuePoint); + } + } + + class sunDo : sunLoop + { + public sunNode Body { get { return this[Count - 2]; } } + public sunExpression Condition { get { return this[Count - 1] as sunExpression; } } + + public sunDo(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) + { + context.Loops.Push(IsNamed ? NameLabel.Label.Value : null); + var bodyPrologue = context.Text.OpenPoint(); + Body.Compile(context); + var continuePoint = context.Text.OpenPoint(); + Condition.Compile(context); + var bodyEpilogue = context.Text.GotoIfZero(); + context.Text.Goto(bodyPrologue); + context.Text.ClosePoint(bodyEpilogue); + var breakPoint = context.Text.OpenPoint(); + context.Loops.Pop(context, breakPoint, continuePoint); + } + } + + class sunFor : sunLoop + { + public sunForDeclaration Declaration { get { return this.FirstOrDefault(i => i is sunForDeclaration) as sunForDeclaration; } } + public sunForCondition Condition { get { return this.FirstOrDefault(i => i is sunForCondition) as sunForCondition; } } + public sunForIteration Iteration { get { return this.FirstOrDefault(i => i is sunForIteration) as sunForIteration; } } + public sunNode Body { get { return this[Count - 1]; } } + + public sunFor(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) + { + context.Scopes.Push(); + context.Loops.Push(IsNamed ? NameLabel.Label.Value : null); + TryCompile(Declaration, context); + var bodyPrologue = context.Text.OpenPoint(); + TryCompile(Condition, context); + var bodyEpilogue = context.Text.GotoIfZero(); + Body.Compile(context); + var continuePoint = context.Text.OpenPoint(); + TryCompile(Iteration, context); + context.Text.Goto(bodyPrologue); + context.Text.ClosePoint(bodyEpilogue); + var breakPoint = context.Text.OpenPoint(); + context.Loops.Pop(context, breakPoint, continuePoint); + context.Scopes.Pop(); + } + } + class sunForDeclaration : sunNode + { + public sunForDeclaration(sunSourceLocation location) + : base(location) + { + + } + } + class sunForCondition : sunNode + { + public sunForCondition(sunSourceLocation location) + : base(location) + { + + } + } + class sunForIteration : sunNode + { + public sunForIteration(sunSourceLocation location) + : base(location) + { + + } + } + + class sunReturn : sunNode + { + public sunExpression Expression { get { return this[0] as sunExpression; } } + + public sunReturn(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) + { + var expression = Expression; + if (expression != null) + { + expression.Compile(context); + context.Text.ReturnValue(); + } + else + { + context.Text.ReturnVoid(); + } + } + } + + class sunBreak : sunNode + { + public bool IsNamed { get { return Count > 0; } } + public sunIdentifier NameLabel { get { return this[0] as sunIdentifier; } } + + public sunBreak(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) + { + var point = context.Text.Goto(); + if (!context.Loops.AddBreak(point, IsNamed ? NameLabel.Value : null)) + { + throw new sunBreakException(this); + } + } + } + + class sunContinue : sunNode + { + public bool IsNamed { get { return Count > 0; } } + public sunIdentifier NameLabel { get { return this[0] as sunIdentifier; } } + + public sunContinue(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) + { + var point = context.Text.Goto(); + if (!context.Loops.AddContinue(point, IsNamed ? NameLabel.Value : null)) + { + throw new sunContinueException(this); + } + } + } +} diff --git a/ast/nodes.functions.cs b/ast/nodes.functions.cs new file mode 100644 index 0000000..18b8bb0 --- /dev/null +++ b/ast/nodes.functions.cs @@ -0,0 +1,95 @@ +using System.Collections.Generic; +using System.Linq; + +namespace arookas +{ + class sunBuiltinDeclaration : sunNode + { + public sunIdentifier Builtin { get { return this[0] as sunIdentifier; } } + public sunParameterList Parameters { get { return this[1] as sunParameterList; } } + + public sunBuiltinDeclaration(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) + { + context.DeclareBuiltin(this); + } + } + + class sunFunctionDefinition : sunNode + { + public sunIdentifier Function { get { return this[0] as sunIdentifier; } } + public sunParameterList Parameters { get { return this[1] as sunParameterList; } } + public sunNode Body { get { return this[2]; } } + + public sunFunctionDefinition(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) + { + context.DefineFunction(this); // possibly counter intuitively, this defines the function in the context; it doesn't compile the definition body + } + } + + class sunFunctionCall : sunNode + { + public sunIdentifier Function { get { return this[0] as sunIdentifier; } } + public sunNode Arguments { get { return this[1] as sunNode; } } + + bool IsStatement { get { return !(Parent is sunOperand); } } + + public sunFunctionCall(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) + { + var callableInfo = context.ResolveCallable(this); + if (!callableInfo.Parameters.ValidateArgumentCount(Arguments.Count)) + { + throw new sunArgumentCountException(this, callableInfo); + } + Arguments.Compile(context); + callableInfo.OpenCallSite(context, Arguments.Count); + if (IsStatement) + { + context.Text.Pop(); + } + } + } + + class sunParameterList : sunNode + { + public IEnumerable Parameters { get { return this.OfType(); } } + public bool IsVariadic { get { return Count > 0 && this[Count - 1] is sunEllipsis; } } + public sunParameterInfo ParameterInfo { get { return new sunParameterInfo(Parameters, IsVariadic); } } + + public sunParameterList(sunSourceLocation location) + : base(location) + { + int count = this.Count(i => i is sunEllipsis); + if (count > 1 || (count > 0 && !(this[Count - 1] is sunEllipsis))) + { + throw new sunVariadicParameterListException(this); + } + } + } + + class sunEllipsis : sunNode + { + public sunEllipsis(sunSourceLocation location) + : base(location) + { + + } + } +} diff --git a/ast/nodes.literals.cs b/ast/nodes.literals.cs new file mode 100644 index 0000000..9406e5d --- /dev/null +++ b/ast/nodes.literals.cs @@ -0,0 +1,201 @@ +using System; +using System.Globalization; +using System.Text; + +namespace arookas +{ + class sunIntLiteral : sunToken // base-10 integer + { + public sunIntLiteral(sunSourceLocation location, string token) + : base(location, token) + { + + } + + protected override int ParseValue(string token) { return Int32.Parse(token); } + + public override void Compile(sunContext context) + { + context.Text.PushInt(Value); + } + } + + class sunHexLiteral : sunIntLiteral // base-16 integer + { + public sunHexLiteral(sunSourceLocation location, string token) + : base(location, token) + { + + } + + protected override int ParseValue(string token) + { + // because .NET's hex parsing is gay and doesn't support + // leading signs, manually detect negative literals + var neg = (token[0] == '-'); + var trim = neg ? 3 : 2; + var digits = token.Substring(trim); // trim the '0x' prefix before parsing + var value = Int32.Parse(token.Substring(2), NumberStyles.AllowHexSpecifier); + if (neg) + { + value = -value; + } + return value; + } + } + + class sunFloatLiteral : sunToken + { + public sunFloatLiteral(sunSourceLocation location, string token) + : base(location, token) + { + + } + + protected override float ParseValue(string image) { return Single.Parse(image); } + + public override void Compile(sunContext context) + { + context.Text.PushFloat(Value); + } + } + + class sunStringLiteral : sunToken + { + public sunStringLiteral(sunSourceLocation location, string token) + : base(location, token) + { + + } + + protected override string ParseValue(string image) { return UnescapeString(image.Substring(1, image.Length - 2)); } // remove enclosing quotes + + public override void Compile(sunContext context) + { + context.Text.PushData(context.DataTable.Add(Value)); + } + + // string unescaping utility + string UnescapeString(string value) + { + // based on Hans Passant's code + StringBuilder sb = new StringBuilder(value.Length); + for (int i = 0; i < value.Length;) + { + int j = value.IndexOf('\\', i); + if (j < 0 || j >= value.Length - 1) + { + j = value.Length; + } + sb.Append(value, i, j - i); + if (j >= value.Length) + { + break; + } + switch (value[j + 1]) + { + case '\'': sb.Append('\''); break; + case '"': sb.Append('"'); break; + case '\\': sb.Append('\\'); break; + case '0': sb.Append('\0'); break; + case 'a': sb.Append('\a'); break; + case 'b': sb.Append('\b'); break; + case 'f': sb.Append('\f'); break; + case 'n': sb.Append('n'); break; + case 't': sb.Append('\t'); break; + case 'v': sb.Append('\v'); break; + case 'x': sb.Append(UnescapeHex(value, j + 2, out i)); continue; + case 'u': sb.Append(UnescapeUnicodeCodeUnit(value, j + 2, out i)); continue; + case 'U': sb.Append(UnescapeUnicodeSurrogatePair(value, j + 2, out i)); continue; + default: throw new sunEscapeSequenceException(this); + } + i = j + 2; + } + return sb.ToString(); + } + char UnescapeHex(string value, int start, out int end) + { + if (start > value.Length) + { + throw new sunEscapeSequenceException(this); // we need at least one digit + } + StringBuilder sb = new StringBuilder(4); + int digits = 0; + while (digits < 4 && start < value.Length && IsHexDigit(value[start])) + { + sb.Append(value[start]); + ++digits; + ++start; + } + end = start; + return (char)Int32.Parse(sb.ToString(), NumberStyles.AllowHexSpecifier); + } + char UnescapeUnicodeCodeUnit(string value, int start, out int end) + { + if (start >= value.Length - 4) + { + throw new sunEscapeSequenceException(this); // we need four digits + } + end = start + 4; + return (char)Int32.Parse(value.Substring(start, 4), NumberStyles.AllowHexSpecifier); + } + string UnescapeUnicodeSurrogatePair(string value, int start, out int end) + { + if (start >= value.Length - 8) + { + throw new sunEscapeSequenceException(this); // we need eight digits + } + char high = (char)Int32.Parse(value.Substring(start, 4), NumberStyles.AllowHexSpecifier); + char low = (char)Int32.Parse(value.Substring(start + 4, 4), NumberStyles.AllowHexSpecifier); + if (!Char.IsHighSurrogate(high) || !Char.IsLowSurrogate(low)) + { + throw new sunEscapeSequenceException(this); // characters are not a surrogate pair + } + end = start + 8; + return String.Concat(high, low); + } + static bool IsHexDigit(char c) + { + return (c >= '0' && c <= '9') || + (c >= 'A' && c <= 'F') || + (c >= 'a' && c <= 'f'); + } + } + + class sunIdentifier : sunRawToken + { + public sunIdentifier(sunSourceLocation location, string token) + : base(location, token) + { + // make sure it is a valid identifier name (i.e. not a keyword) + if (sunParser.IsKeyword(Value)) + { + throw new sunIdentifierException(this); + } + } + + // identifiers are compiled on a per-context basis (i.e. at a higher level) + } + + class sunTrue : sunIntLiteral + { + public sunTrue(sunSourceLocation location, string token) + : base(location, token) + { + + } + + protected override int ParseValue(string token) { return 1; } + } + + class sunFalse : sunIntLiteral + { + public sunFalse(sunSourceLocation location, string token) + : base(location, token) + { + + } + + protected override int ParseValue(string token) { return 0; } + } +} diff --git a/ast/nodes.operators.cs b/ast/nodes.operators.cs new file mode 100644 index 0000000..8065dc3 --- /dev/null +++ b/ast/nodes.operators.cs @@ -0,0 +1,453 @@ +using PerCederberg.Grammatica.Runtime; + +namespace arookas +{ + enum Associativity + { + Left, + Right, + } + + abstract class sunOperator : sunNode + { + public virtual Associativity Associativity { get { return Associativity.Left; } } + public abstract int Precedence { get; } + + public bool IsLeftAssociative { get { return Associativity == Associativity.Left; } } + public bool IsRightAssociative { get { return Associativity == Associativity.Right; } } + + protected sunOperator(sunSourceLocation location) + : base(location) + { + + } + } + + // precedence 0 + class sunLogOR : sunOperator + { + public override int Precedence { get { return 0; } } + + public sunLogOR(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) { context.Text.LogOR(); } + } + + // precedence 1 + class sunLogAND : sunOperator + { + public override int Precedence { get { return 1; } } + + public sunLogAND(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) { context.Text.LogAND(); } + } + + // precedence 2 + class sunBitOR : sunOperator + { + public override int Precedence { get { return 2; } } + + public sunBitOR(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) { context.Text.BitOR(); } + } + + // precedence 3 + class sunBitAND : sunOperator + { + public override int Precedence { get { return 3; } } + + public sunBitAND(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) { context.Text.BitAND(); } + } + + // precedence 4 + class sunEq : sunOperator + { + public override int Precedence { get { return 4; } } + + public sunEq(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) { context.Text.Eq(); } + } + + class sunNtEq : sunOperator + { + public override int Precedence { get { return 4; } } + + public sunNtEq(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) { context.Text.NtEq(); } + } + + // precedence 5 + class sunLt : sunOperator + { + public override int Precedence { get { return 5; } } + + public sunLt(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) { context.Text.Lt(); } + } + + class sunLtEq : sunOperator + { + public override int Precedence { get { return 5; } } + + public sunLtEq(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) { context.Text.LtEq(); } + } + + class sunGt : sunOperator + { + public override int Precedence { get { return 5; } } + + public sunGt(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) { context.Text.Gt(); } + } + + class sunGtEq : sunOperator + { + public override int Precedence { get { return 5; } } + + public sunGtEq(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) { context.Text.GtEq(); } + } + + // precedence 6 + class sunBitLsh : sunOperator + { + public override int Precedence { get { return 6; } } + + public sunBitLsh(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) { context.Text.ShL(); } + } + + class sunBitRsh : sunOperator + { + public override int Precedence { get { return 6; } } + + public sunBitRsh(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) { context.Text.ShR(); } + } + + // precedence 7 + class sunAdd : sunOperator + { + public override int Precedence { get { return 7; } } + + public sunAdd(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) { context.Text.Add(); } + } + + class sunSub : sunOperator + { + public override int Precedence { get { return 7; } } + + public sunSub(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) { context.Text.Sub(); } + } + + // precedence 8 + class sunMul : sunOperator + { + public override int Precedence { get { return 8; } } + + public sunMul(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) { context.Text.Mul(); } + } + + class sunDiv : sunOperator + { + public override int Precedence { get { return 8; } } + + public sunDiv(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) { context.Text.Div(); } + } + + class sunMod : sunOperator + { + public override int Precedence { get { return 8; } } + + public sunMod(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) { context.Text.Mod(); } + } + + // precedence 9 + class sunLogNOT : sunOperator + { + public override int Precedence { get { return 9; } } + + public sunLogNOT(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) { context.Text.LogNOT(); } + } + class sunNeg : sunOperator + { + public override int Precedence { get { return 9; } } + + public sunNeg(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) { context.Text.Neg(); } + } + + // assignment operators + class sunAssign : sunOperator + { + public override Associativity Associativity { get { return Associativity.Right; } } + public override int Precedence { get { return -1; } } + + public sunAssign(sunSourceLocation location) + : base(location) + { + + } + + public virtual void Compile(sunContext context, sunVariableInfo variableInfo, sunExpression expression) + { + expression.Compile(context); + context.Text.StoreVariable(variableInfo); + } + } + + class sunAssignAdd : sunAssign + { + public sunAssignAdd(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context, sunVariableInfo variableInfo, sunExpression expression) + { + context.Text.PushVariable(variableInfo); + expression.Compile(context); + context.Text.Add(); + context.Text.StoreVariable(variableInfo); + } + } + + class sunAssignSub : sunAssign + { + public sunAssignSub(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context, sunVariableInfo variableInfo, sunExpression expression) + { + context.Text.PushVariable(variableInfo); + expression.Compile(context); + context.Text.Sub(); + context.Text.StoreVariable(variableInfo); + } + } + + class sunAssignMul : sunAssign + { + public sunAssignMul(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context, sunVariableInfo variableInfo, sunExpression expression) + { + context.Text.PushVariable(variableInfo); + expression.Compile(context); + context.Text.Mul(); + context.Text.StoreVariable(variableInfo); + } + } + + class sunAssignDiv : sunAssign + { + public sunAssignDiv(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context, sunVariableInfo variableInfo, sunExpression expression) + { + context.Text.PushVariable(variableInfo); + expression.Compile(context); + context.Text.Div(); + context.Text.StoreVariable(variableInfo); + } + } + + class sunAssignMod : sunAssign + { + public sunAssignMod(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context, sunVariableInfo variableInfo, sunExpression expression) + { + context.Text.PushVariable(variableInfo); + expression.Compile(context); + context.Text.Mod(); + context.Text.StoreVariable(variableInfo); + } + } + + class sunAssignBitAND : sunAssign + { + public sunAssignBitAND(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context, sunVariableInfo variableInfo, sunExpression expression) + { + context.Text.PushVariable(variableInfo); + expression.Compile(context); + context.Text.BitAND(); + context.Text.StoreVariable(variableInfo); + } + } + + class sunAssignBitOR : sunAssign + { + public sunAssignBitOR(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context, sunVariableInfo variableInfo, sunExpression expression) + { + context.Text.PushVariable(variableInfo); + expression.Compile(context); + context.Text.BitOR(); + context.Text.StoreVariable(variableInfo); + } + } + + class sunAssignBitLsh : sunAssign + { + public sunAssignBitLsh(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context, sunVariableInfo variableInfo, sunExpression expression) + { + context.Text.PushVariable(variableInfo); + expression.Compile(context); + context.Text.ShL(); + context.Text.StoreVariable(variableInfo); + } + } + + class sunAssignBitRsh : sunAssign + { + public sunAssignBitRsh(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context, sunVariableInfo variableInfo, sunExpression expression) + { + context.Text.PushVariable(variableInfo); + expression.Compile(context); + context.Text.ShR(); + context.Text.StoreVariable(variableInfo); + } + } +} diff --git a/ast/nodes.statements.cs b/ast/nodes.statements.cs new file mode 100644 index 0000000..e3bd7de --- /dev/null +++ b/ast/nodes.statements.cs @@ -0,0 +1,50 @@ +namespace arookas +{ + class sunStatementBlock : sunNode + { + public sunStatementBlock(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) + { + context.Scopes.Push(); + base.Compile(context); + context.Scopes.Pop(); + } + } + + class sunImport : sunNode + { + public sunStringLiteral ImportFile { get { return this[0] as sunStringLiteral; } } + + public sunImport(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) + { + var file = context.Imports.ResolveImport(this); + if (file == null) + { + return; // the file has already been imported + } + context.Compile(file); + } + } + + class sunNameLabel : sunNode + { + public sunIdentifier Label { get { return this[0] as sunIdentifier; } } + + public sunNameLabel(sunSourceLocation location) + : base(location) + { + + } + } +} diff --git a/ast/nodes.system.cs b/ast/nodes.system.cs new file mode 100644 index 0000000..0cc14c7 --- /dev/null +++ b/ast/nodes.system.cs @@ -0,0 +1,155 @@ +namespace arookas +{ + class sunYield : sunNode + { + public sunYield(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) + { + var builtinInfo = context.ResolveSystemBuiltin("yield"); + context.Text.CallBuiltin(builtinInfo.Index, 0); + context.Text.Pop(); + } + } + + class sunExit : sunNode + { + public sunExit(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) + { + var builtinInfo = context.ResolveSystemBuiltin("exit"); + context.Text.CallBuiltin(builtinInfo.Index, 0); + context.Text.Pop(); + } + } + + class sunDump : sunNode + { + public sunDump(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) + { + var builtinInfo = context.ResolveSystemBuiltin("dump"); + context.Text.CallBuiltin(builtinInfo.Index, 0); + context.Text.Pop(); + } + } + + class sunLock : sunNode + { + public sunLock(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) + { + var builtinInfo = context.ResolveSystemBuiltin("lock"); + context.Text.CallBuiltin(builtinInfo.Index, 0); + context.Text.Pop(); + } + } + + class sunUnlock : sunNode + { + public sunUnlock(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) + { + var builtinInfo = context.ResolveSystemBuiltin("unlock"); + context.Text.CallBuiltin(builtinInfo.Index, 0); + context.Text.Pop(); + } + } + + class sunIntCast : sunNode + { + public sunExpression Argument { get { return this[0] as sunExpression; } } + + public sunIntCast(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) + { + var builtinInfo = context.DeclareSystemBuiltin("int", false, "x"); + Argument.Compile(context); + context.Text.CallBuiltin(builtinInfo.Index, 1); + } + } + + class sunFloatCast : sunNode + { + public sunExpression Argument { get { return this[0] as sunExpression; } } + + public sunFloatCast(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) + { + var builtinInfo = context.ResolveSystemBuiltin("float"); + Argument.Compile(context); + context.Text.CallBuiltin(builtinInfo.Index, 1); + } + } + + class sunTypeofCast : sunNode + { + public sunExpression Argument { get { return this[0] as sunExpression; } } + + public sunTypeofCast(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) + { + var builtinInfo = context.ResolveSystemBuiltin("typeof"); + Argument.Compile(context); + context.Text.CallBuiltin(builtinInfo.Index, 1); + } + } + + class sunPrint : sunNode + { + public sunNode ArgumentList { get { return this[0]; } } + + public sunPrint(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) + { + var builtinInfo = context.ResolveSystemBuiltin("print"); + ArgumentList.Compile(context); + context.Text.CallBuiltin(builtinInfo.Index, ArgumentList.Count); + context.Text.Pop(); + } + } +} diff --git a/ast/nodes.variables.cs b/ast/nodes.variables.cs new file mode 100644 index 0000000..d464db4 --- /dev/null +++ b/ast/nodes.variables.cs @@ -0,0 +1,96 @@ +namespace arookas +{ + class sunVariableReference : sunNode + { + public sunIdentifier Variable { get { return this[0] as sunIdentifier; } } + + public sunVariableReference(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) + { + sunVariableInfo variableInfo; + sunConstInfo constInfo; + context.ResolveVariableOrConstant(Variable, out variableInfo, out constInfo); + if (variableInfo != null) + { + context.Text.PushVariable(variableInfo); + } + if (constInfo != null) + { + constInfo.Expression.Compile(context); + } + } + } + + class sunVariableDeclaration : sunNode + { + public sunIdentifier Variable { get { return this[0] as sunIdentifier; } } + + public sunVariableDeclaration(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) + { + var variableInfo = context.DeclareVariable(Variable); + context.Text.DeclareLocal(1); + } + } + + class sunVariableDefinition : sunVariableAssignment + { + public sunVariableDefinition(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) + { + var variableInfo = context.DeclareVariable(Variable); + context.Text.DeclareLocal(1); + base.Compile(context); + } + } + + class sunVariableAssignment : sunVariableDeclaration + { + public sunAssign Operator { get { return this[1] as sunAssign; } } + public sunExpression Expression { get { return this[2] as sunExpression; } } + + public sunVariableAssignment(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) + { + var variableInfo = context.ResolveVariable(Variable); + Operator.Compile(context, variableInfo, Expression); + } + } + + class sunConstDefinition : sunNode + { + public sunIdentifier Constant { get { return this[0] as sunIdentifier; } } + public sunExpression Expression { get { return this[2] as sunExpression; } } + + public sunConstDefinition(sunSourceLocation location) + : base(location) + { + + } + + public override void Compile(sunContext context) + { + var constInfo = context.DeclareConstant(Constant, Expression); + } + } +} diff --git a/compiler.cs b/compiler.cs new file mode 100644 index 0000000..0e3ada6 --- /dev/null +++ b/compiler.cs @@ -0,0 +1,81 @@ +using PerCederberg.Grammatica.Runtime; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; + +namespace arookas +{ + public class sunCompiler + { + Stack files; + string defaultRootDir, rootDir; + public string RootDir + { + get { return rootDir ?? defaultRootDir; } + set { rootDir = value; } + } + + public sunCompiler() + { + defaultRootDir = AppDomain.CurrentDomain.BaseDirectory; + } + + public sunCompilerResults Compile(string file, Stream output) + { + var results = new sunCompilerResults(); + var timer = Stopwatch.StartNew(); + try + { + files = new Stack(5); + sunContext context = new sunContext(output, RootDir); + context.EnterFile += EnterFile; + context.ExitFile += ExitFile; + context.Compile(file); + context.Text.Terminate(); // NOTETOSELF: don't do this in sunScript because imported files will add this as well + foreach (var function in context.SymbolTable.Functions) + { + function.Compile(context); + } + foreach (var function in context.SymbolTable.Functions) + { + function.CloseCallSites(context); + } + results.SymbolCount = context.SymbolTable.Count; + results.BuiltinCount = context.SymbolTable.BuiltinCount; + results.FunctionCount = context.SymbolTable.FunctionCount; + results.VariableCount = context.SymbolTable.VariableCount; + context.Dispose(); + } + catch (sunCompilerException ex) + { + results.Error = ex; + } + catch (ParserLogException ex) + { + results.Error = new sunParserException(files.Peek(), ex[0]); + } + timer.Stop(); + results.CompileTime = timer.Elapsed; + return results; + } + + void EnterFile(object sender, sunFileArgs e) { files.Push(e.File); } + void ExitFile(object sender, sunFileArgs e) { files.Pop(); } + } + + public class sunCompilerResults + { + // success + public bool Success { get { return Error == null; } } + public sunCompilerException Error { get; internal set; } + + // statistics + public int SymbolCount { get; internal set; } + public int BuiltinCount { get; internal set; } + public int FunctionCount { get; internal set; } + public int VariableCount { get; internal set; } + + public TimeSpan CompileTime { get; internal set; } + } +} diff --git a/context.cs b/context.cs new file mode 100644 index 0000000..6ea4d24 --- /dev/null +++ b/context.cs @@ -0,0 +1,250 @@ +using arookas.IO.Binary; +using System; +using System.IO; +using System.Linq; +using System.Text; + +namespace arookas +{ + class sunContext : aDisposable + { + aBinaryWriter writer; + uint textOffset, dataOffset, symbolOffset; + + public sunWriter Text { get; private set; } + public sunDataTable DataTable { get; private set; } + public sunSymbolTable SymbolTable { get; private set; } + public sunScopeStack Scopes { get; private set; } + public sunLoopStack Loops { get; private set; } + public sunImportTable Imports { get; private set; } + + public event EventHandler EnterFile; + public event EventHandler ExitFile; + + // open/close + public sunContext(Stream stream, string rootDir) + { + DataTable = new sunDataTable(); + SymbolTable = new sunSymbolTable(); + Scopes = new sunScopeStack(); + Loops = new sunLoopStack(); + Imports = new sunImportTable(rootDir); + + writer = new aBinaryWriter(stream, Endianness.Big, Encoding.GetEncoding(932)); + Text = new sunWriter(writer); + writer.PushAnchor(); + + WriteHeader(); // dummy header + + // begin text block + textOffset = (uint)writer.Position; + writer.PushAnchor(); // match code offsets and writer offsets + + // add system builtins + DeclareSystemBuiltin("yield", false); + DeclareSystemBuiltin("exit", false); + DeclareSystemBuiltin("dump", false); + DeclareSystemBuiltin("lock", false); + DeclareSystemBuiltin("unlock", false); + DeclareSystemBuiltin("int", false, "x"); + DeclareSystemBuiltin("float", false, "x"); + DeclareSystemBuiltin("typeof", false, "x"); + DeclareSystemBuiltin("print", true); + } + protected override bool Dispose(bool destructor) + { + if (!destructor) + { + writer.PopAnchor(); + dataOffset = (uint)writer.Position; + DataTable.Write(writer); + symbolOffset = (uint)writer.Position; + SymbolTable.Write(writer); + writer.Goto(0); + WriteHeader(); + return true; // don't dispose the writer so the stream doesn't get disposed + } + return false; + } + + // imports + public void Compile(string file) + { + Imports.PushDir(file); + OnEnterFile(new sunFileArgs(file)); + var parser = new sunParser(); + var tree = parser.Parse(file); + tree.Compile(this); + OnExitFile(new sunFileArgs(file)); + Imports.PopDir(); + } + + // builtins + public sunBuiltinInfo DeclareBuiltin(sunBuiltinDeclaration node) + { + var symbolInfo = SymbolTable.Callables.FirstOrDefault(f => f.Name == node.Builtin.Value); + if (symbolInfo != null) + { + throw new sunRedeclaredBuiltinException(node); + } + var builtinInfo = new sunBuiltinInfo(node.Builtin.Value, node.Parameters.ParameterInfo, SymbolTable.Count); + SymbolTable.Add(builtinInfo); + return builtinInfo; + } + public sunBuiltinInfo DeclareSystemBuiltin(string name, bool variadic, params string[] parameters) + { + var builtinInfo = SymbolTable.Builtins.FirstOrDefault(f => f.Name == name); + if (builtinInfo == null) + { + builtinInfo = new sunBuiltinInfo(name, new sunParameterInfo(parameters, variadic), SymbolTable.Count); + SymbolTable.Add(builtinInfo); + } + return builtinInfo; + } + public sunBuiltinInfo ResolveSystemBuiltin(string name) + { + return SymbolTable.Builtins.FirstOrDefault(f => f.Name == name); + } + + // functions + public sunFunctionInfo DefineFunction(sunFunctionDefinition node) + { + if (node.Parameters.IsVariadic) + { + throw new sunVariadicFunctionException(node); + } + var symbolInfo = SymbolTable.Callables.FirstOrDefault(f => f.Name == node.Function.Value); + if (symbolInfo != null) + { + throw new sunRedefinedFunctionException(node); + } + var functionInfo = new sunFunctionInfo(node.Function.Value, node.Parameters.ParameterInfo, node.Body); + SymbolTable.Add(functionInfo); + return functionInfo; + } + public sunCallableSymbolInfo ResolveCallable(sunFunctionCall node) + { + var symbolInfo = SymbolTable.Callables.FirstOrDefault(f => f.Name == node.Function.Value); + if (symbolInfo == null) + { + throw new sunUndefinedFunctionException(node); + } + return symbolInfo; + } + + // variables + public sunVariableInfo DeclareVariable(sunIdentifier node) + { + // assert variable is not already declared in current scope + if (Scopes.Top.GetIsVariableDeclared(node.Value)) + { + throw new sunRedeclaredVariableException(node); + } + var variableInfo = Scopes.Top.DeclareVariable(node.Value, Scopes.Count > 1 ? 1 : 0); + if (Scopes.IsRoot) + { + // global-scope variables are added to the symbol table + SymbolTable.Add(variableInfo); + } + return variableInfo; + } + public sunVariableInfo ResolveVariable(sunIdentifier node) + { + // walk the stack backwards to resolve to the variable's latest declaration + for (int i = Scopes.Count - 1; i >= 0; --i) + { + var variableInfo = Scopes[i].ResolveVariable(node.Value); + if (variableInfo != null) + { + return variableInfo; + } + } + throw new sunUndeclaredVariableException(node); + } + + public sunVariableInfo DeclareParameter(string name) { return Scopes.Top.DeclareVariable(name, 1); } + + // constants + public sunConstInfo DeclareConstant(sunIdentifier node, sunExpression expression) + { + if (Scopes.Top.GetIsConstantDeclared(node.Value)) + { + throw new sunRedeclaredVariableException(node); + } + var constInfo = Scopes.Top.DeclareConstant(node.Value, expression); + return constInfo; + } + public sunConstInfo ResolveConstant(sunIdentifier node) + { + // walk the stack backwards to resolve to the variable's latest declaration + for (int i = Scopes.Count - 1; i >= 0; --i) + { + var constInfo = Scopes[i].ResolveConstant(node.Value); + if (constInfo != null) + { + return constInfo; + } + } + throw new sunUndeclaredVariableException(node); + } + + public void ResolveVariableOrConstant(sunIdentifier node, out sunVariableInfo variableInfo, out sunConstInfo constInfo) + { + try + { + variableInfo = ResolveVariable(node); + } + catch + { + variableInfo = null; + } + try + { + constInfo = ResolveConstant(node); + } + catch + { + constInfo = null; + } + } + + void WriteHeader() + { + writer.WriteString("SPCB"); + writer.Write32(textOffset); + writer.Write32(dataOffset); + writer.WriteS32(DataTable.Count); + writer.Write32(symbolOffset); + writer.WriteS32(SymbolTable.Count); + writer.WriteS32(Scopes.Root.VariableCount); + } + + // events + void OnEnterFile(sunFileArgs e) + { + var func = EnterFile; + if (func != null) + { + func(this, e); + } + } + void OnExitFile(sunFileArgs e) + { + var func = ExitFile; + if (func != null) + { + func(this, e); + } + } + } + + class sunFileArgs : EventArgs + { + public string File { get; private set; } + + public sunFileArgs(string file) + { + File = file; + } + } +} diff --git a/data table.cs b/data table.cs new file mode 100644 index 0000000..3feef96 --- /dev/null +++ b/data table.cs @@ -0,0 +1,42 @@ +using arookas.IO.Binary; +using System.Collections; +using System.Collections.Generic; + +namespace arookas +{ + class sunDataTable : IEnumerable + { + List data = new List(10); + + public int Count { get { return data.Count; } } + + public int Add(string value) + { + int index = data.IndexOf(value); + if (index < 0) + { + index = data.Count; + data.Add(value); + } + return index; + } + public void Clear() { data.Clear(); } + + public void Write(aBinaryWriter writer) + { + int ofs = 0; + foreach (var value in this) + { + writer.WriteS32(ofs); + ofs += value.Length + 1; // include terminator + } + foreach (var value in this) + { + writer.WriteString(value, aBinaryStringFormat.NullTerminated); + } + } + + public IEnumerator GetEnumerator() { return data.GetEnumerator(); } + IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } + } +} diff --git a/exceptions.cs b/exceptions.cs new file mode 100644 index 0000000..6579e4f --- /dev/null +++ b/exceptions.cs @@ -0,0 +1,244 @@ +using PerCederberg.Grammatica.Runtime; +using System; + +namespace arookas +{ + // base exception type + public class sunCompilerException : Exception + { + public sunCompilerException() + { + + } + public sunCompilerException(string format, params object[] args) + : base(String.Format(format, args)) + { + + } + } + + // exceptions that have a location in the source + public abstract class sunScriptException : sunCompilerException + { + public abstract sunSourceLocation Location { get; } + + public sunScriptException() + { + + } + public sunScriptException(string format, params object[] args) + : base(format, args) + { + + } + } + + // wrapper around Grammatica exceptions + class sunParserException : sunScriptException + { + string file; + + public ParseException Info { get; private set; } + public override string Message { get { return Info.ErrorMessage; } } + public override sunSourceLocation Location { get { return new sunSourceLocation(file, Info.Line, Info.Column); } } + + public sunParserException(string file, ParseException info) + { + if (file == null) + { + throw new ArgumentNullException("file"); + } + if (info == null) + { + throw new ArgumentNullException("info"); + } + this.file = file; + Info = info; + } + } + + // node exceptions + abstract class sunNodeException : sunScriptException where TNode : sunNode + { + public TNode Node { get; private set; } + public override sunSourceLocation Location { get { return Node.Location; } } + + protected sunNodeException(TNode node) + { + if (node == null) + { + throw new ArgumentNullException("node"); + } + Node = node; + } + } + + class sunRedeclaredBuiltinException : sunNodeException + { + public override string Message { get { return String.Format("Redeclared builtin '{0}'.", Node.Builtin.Value); } } + + public sunRedeclaredBuiltinException(sunBuiltinDeclaration node) + : base(node) + { + + } + } + class sunUndefinedFunctionException : sunNodeException + { + public override string Message { get { return String.Format("Undefined function or builtin '{0}'.", Node.Function.Value); } } + + public sunUndefinedFunctionException(sunFunctionCall node) + : base(node) + { + + } + } + class sunRedefinedFunctionException : sunNodeException + { + public override string Message { get { return String.Format("Redefined function '{0}'.", Node.Function.Value); } } + + public sunRedefinedFunctionException(sunFunctionDefinition node) + : base(node) + { + + } + } + class sunUndeclaredVariableException : sunNodeException + { + public override string Message { get { return String.Format("Undeclared variable '{0}'.", Node.Value); } } + + public sunUndeclaredVariableException(sunIdentifier node) + : base(node) + { + + } + } + class sunRedeclaredVariableException : sunNodeException + { + public override string Message { get { return String.Format("Redeclared variable '{0}'.", Node.Value); } } + + public sunRedeclaredVariableException(sunIdentifier node) + : base(node) + { + + } + } + class sunRedeclaredParameterException : sunNodeException + { + public override string Message { get { return String.Format("Redeclared parameter '{0}'.", Node.Value); } } + + public sunRedeclaredParameterException(sunIdentifier node) + : base(node) + { + + } + } + class sunVariadicFunctionException : sunNodeException + { + public override string Message { get { return String.Format("Function '{0}' is defined as a variadic function (only builtins may be variadic).", Node.Function.Value); } } + + public sunVariadicFunctionException(sunFunctionDefinition node) + : base(node) + { + + } + } + class sunEscapeSequenceException : sunNodeException + { + public override string Message { get { return String.Format("Bad escape sequence in string."); } } + + public sunEscapeSequenceException(sunStringLiteral node) + : base(node) + { + + } + } + class sunVariadicParameterListException : sunNodeException + { + public override string Message { get { return String.Format("Bad variadic parameter list."); } } + + public sunVariadicParameterListException(sunParameterList node) + : base(node) + { + + } + } + class sunArgumentCountException : sunNodeException + { + public sunCallableSymbolInfo CalledSymbol { get; private set; } + public int ArgumentMinimum { get { return CalledSymbol.Parameters.Minimum; } } + public int ArgumentCount { get { return Node.Arguments.Count; } } + + public override string Message + { + get + { + string format; + if (CalledSymbol.Parameters.IsVariadic) + { + // assuming to be missing because there's only a minimum + format = "Missing {0} argument(s) (expected at least {1}; got {2})."; + } + else if (Node.Arguments.Count < CalledSymbol.Parameters.Minimum) + { + format = "Missing {0} argument(s) (expected {1}; got {2})."; // missing arguments + } + else + { + format = "Too many arguments (expected {1}; got {2})."; // extra arguments + } + return String.Format(format, ArgumentMinimum - ArgumentCount, ArgumentMinimum, ArgumentCount); + } + } + + public sunArgumentCountException(sunFunctionCall node, sunCallableSymbolInfo calledSymbol) + : base(node) + { + if (calledSymbol == null) + { + throw new ArgumentNullException("calledSymbol"); + } + CalledSymbol = calledSymbol; + } + } + class sunIdentifierException : sunNodeException + { + public override string Message { get { return String.Format("Invalid identifier '{0}'.", Node.Value); } } + + public sunIdentifierException(sunIdentifier node) + : base(node) + { + + } + } + class sunMissingImportException : sunNodeException + { + public override string Message { get { return String.Format("Could not find import file '{0}'.", Node.ImportFile.Value); } } + + public sunMissingImportException(sunImport node) + : base(node) + { + + } + } + class sunBreakException : sunNodeException + { + public override string Message { get { return "Break statements must be placed within a loop statement."; } } + + public sunBreakException(sunBreak node) + : base(node) + { + + } + } + class sunContinueException : sunNodeException + { + public override string Message { get { return "Continue statements must be placed within a loop statement."; } } + + public sunContinueException(sunContinue node) + : base(node) + { + + } + } +} diff --git a/import table.cs b/import table.cs new file mode 100644 index 0000000..db48348 --- /dev/null +++ b/import table.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using System.IO; + +namespace arookas +{ + class sunImportTable + { + List imports = new List(10); + Stack curDir = new Stack(5); + string RootDir { get; set; } + + public sunImportTable(string rootDir) + { + RootDir = rootDir; + } + + public void PushDir(string dir) { curDir.Push(Path.GetDirectoryName(dir)); } + public void PopDir() { curDir.Pop(); } + + public string ResolveImport(sunImport import) + { + string fullPath; + string file = import.ImportFile.Value; + if (Path.IsPathRooted(file)) + { + // if the path is absolute, just use it directly + fullPath = file; + if (!File.Exists(fullPath)) + { + // could not find file + throw new sunMissingImportException(import); + } + } + else + { + // check if the file exists relative to the current one; + // if it's not there, check the root directory + fullPath = Path.Combine(curDir.Peek(), file); + if (!File.Exists(fullPath)) + { + fullPath = Path.Combine(RootDir, file); + if (!File.Exists(fullPath)) + { + // could not find file + throw new sunMissingImportException(import); + } + } + } + // make sure the file has not been imported yet + if (imports.Contains(fullPath)) + { + return null; + } + imports.Add(fullPath); + return fullPath; + } + } +} diff --git a/loop stack.cs b/loop stack.cs new file mode 100644 index 0000000..48c5833 --- /dev/null +++ b/loop stack.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; +using System.Linq; + +namespace arookas +{ + class sunLoopStack + { + Stack loops = new Stack(5); + sunLoop Top { get { return loops.Peek(); } } + + sunLoop this[string name] { get { return loops.FirstOrDefault(i => i.Name == name); } } + public int Count { get { return loops.Count; } } + + public void Push() { Push(null); } + public void Push(string name) { loops.Push(new sunLoop(name)); } + public void Pop(sunContext context, sunPoint breakPoint, sunPoint continuePoint) + { + foreach (var _break in Top.Breaks) + { + context.Text.ClosePoint(_break, breakPoint.Offset); + } + foreach (var _continue in Top.Continues) + { + context.Text.ClosePoint(_continue, continuePoint.Offset); + } + loops.Pop(); + } + + public bool AddBreak(sunPoint point) { return AddBreak(point, null); } + public bool AddContinue(sunPoint point) { return AddContinue(point, null); } + public bool AddBreak(sunPoint point, string name) + { + if (Count < 1) + { + return false; + } + var loop = name == null ? Top : this[name]; + if (loop == null) + { + return false; + } + loop.Breaks.Add(point); + return true; + } + public bool AddContinue(sunPoint point, string name) + { + if (Count < 1) + { + return false; + } + var loop = name == null ? Top : this[name]; + if (loop == null) + { + return false; + } + loop.Continues.Add(point); + return true; + } + + class sunLoop + { + public string Name { get; private set; } + public List Breaks { get; private set; } + public List Continues { get; private set; } + + public sunLoop() + : this(null) + { + + } + public sunLoop(string name) + { + Name = name; + Breaks = new List(5); + Continues = new List(5); + } + } + } +} diff --git a/main.cs b/main.cs new file mode 100644 index 0000000..a05cd68 --- /dev/null +++ b/main.cs @@ -0,0 +1,70 @@ +using System; +using System.IO; + +namespace arookas +{ + static class SSC + { + public static void Main(string[] args) + { + string inFile = args[0]; + var compiler = new sunCompiler(); + using (var output = OpenWrite(Path.ChangeExtension(inFile, ".sb"))) + { + var results = compiler.Compile(inFile, output); + if (!results.Success) + { + if (results.Error is sunScriptException) + { + var error = results.Error as sunScriptException; + Console.WriteLine("ERROR:\n \"{0}\"\n pos ({1}, {2})\n{3}", error.Location.File, error.Location.Line, error.Location.Column, error.Message); + Console.ReadKey(); + return; + } + else + { + var error = results.Error; + Console.WriteLine("ERROR:\n", error.Message); + Console.ReadKey(); + return; + } + } + Console.WriteLine("Finished compiling in {0:F2}ms.", results.CompileTime.TotalMilliseconds); + Console.WriteLine("Symbol count: {0}", results.SymbolCount); + Console.WriteLine(" - builtins: {0}", results.BuiltinCount); + Console.WriteLine(" - functions: {0}", results.FunctionCount); + Console.WriteLine(" - variables: {0}", results.VariableCount); + Console.ReadKey(); + } + } + + static FileStream OpenRead(string path) + { + try + { + return File.OpenRead(path); + } + catch + { + Console.WriteLine("Failed to open the file '{0}'.\nPlease make sure the file exists and is not currently in use.", Path.GetFileName(path)); + Console.ReadKey(); + Environment.Exit(1); + return null; + } + } + static FileStream OpenWrite(string path) + { + try + { + return File.Create(path); + } + catch + { + Console.WriteLine("Failed to create the file '{0}'.", Path.GetFileName(path)); + Console.ReadKey(); + Environment.Exit(1); + return null; + } + } + } +} diff --git a/parser.cs b/parser.cs new file mode 100644 index 0000000..24a88b2 --- /dev/null +++ b/parser.cs @@ -0,0 +1,322 @@ +using PerCederberg.Grammatica.Runtime; +using System; +using System.IO; +using System.Linq; + +namespace arookas +{ + class sunParser + { + static string[] keywords = + { + "import", + "builtin", "function", "var", "const", + "if", "while", "do", "for", + "return", "break", "continue", + "yield", "exit", "dump", "lock", "unlock", "int", "float", "typeof", "print", + "true", "false", + }; + + public sunNode Parse(string file) + { + using (var input = new StreamReader(file)) + { + var parser = new __sunParser(input); + var node = parser.Parse(); + return CreateAst(file, node); + } + } + + static sunNode CreateAst(string file, Node node) + { + var ast = ConvertNode(file, node); + if (ast == null) + { + return null; + } + // children + if (node is Production) + { + var production = node as Production; + for (int i = 0; i < production.Count; ++i) + { + var child = CreateAst(file, production[i]); + if (child != null) + { + ast.Add(child); + } + } + } + // transcience + switch ((__sunConstants)node.Id) + { + case __sunConstants.STATEMENT: + case __sunConstants.COMPOUND_STATEMENT: + case __sunConstants.COMPOUND_STATEMENT_ITEM: + case __sunConstants.ASSIGNMENT_OPERATOR: + case __sunConstants.BINARY_OPERATOR: + case __sunConstants.UNARY_OPERATOR: + case __sunConstants.TERM: + case __sunConstants.PARAMETER: + case __sunConstants.ARGUMENT_LIST: + case __sunConstants.ARGUMENT: + { + return Transcient(ast); + } + } + return ast; + } + static sunNode ConvertNode(string file, Node node) + { + var location = new sunSourceLocation(file, node.StartLine, node.StartColumn); + string token = null; + if (node is Token) + { + token = (node as Token).Image; + } + + // statements + switch (GetId(node)) + { + case __sunConstants.SCRIPT: return new sunNode(location); + case __sunConstants.STATEMENT: return new sunNode(location); + case __sunConstants.STATEMENT_BLOCK: return new sunStatementBlock(location); + case __sunConstants.COMPOUND_STATEMENT: return new sunNode(location); + case __sunConstants.COMPOUND_STATEMENT_ITEM: return new sunNode(location); + + case __sunConstants.IMPORT_STATEMENT: return new sunImport(location); + case __sunConstants.NAME_LABEL: return new sunNameLabel(location); + + case __sunConstants.YIELD_STATEMENT: return new sunYield(location); + case __sunConstants.EXIT_STATEMENT: return new sunExit(location); + case __sunConstants.DUMP_STATEMENT: return new sunDump(location); + case __sunConstants.LOCK_STATEMENT: return new sunLock(location); + case __sunConstants.UNLOCK_STATEMENT: return new sunUnlock(location); + case __sunConstants.PRINT_STATEMENT: return new sunPrint(location); + } + + // literals + switch (GetId(node)) + { + case __sunConstants.INT_NUMBER: return new sunIntLiteral(location, token); + case __sunConstants.HEX_NUMBER: return new sunHexLiteral(location, token); + case __sunConstants.DEC_NUMBER: return new sunFloatLiteral(location, token); + case __sunConstants.STRING: return new sunStringLiteral(location, token); + case __sunConstants.IDENTIFIER: return new sunIdentifier(location, token); + case __sunConstants.ELLIPSIS: return new sunEllipsis(location); + case __sunConstants.TRUE: return new sunTrue(location, token); + case __sunConstants.FALSE: return new sunFalse(location, token); + } + + // operators + switch (GetId(node)) + { + case __sunConstants.ADD: return new sunAdd(location); + case __sunConstants.SUB: + { + if (GetId(node.Parent) == __sunConstants.UNARY_OPERATOR) + { + return new sunNeg(location); + } + return new sunSub(location); + } + case __sunConstants.MUL: return new sunMul(location); + case __sunConstants.DIV: return new sunDiv(location); + case __sunConstants.MOD: return new sunMod(location); + + case __sunConstants.BIT_AND: return new sunBitAND(location); + case __sunConstants.BIT_OR: return new sunBitOR(location); + case __sunConstants.BIT_LSH: return new sunBitLsh(location); + case __sunConstants.BIT_RSH: return new sunBitRsh(location); + + case __sunConstants.LOG_AND: return new sunLogAND(location); + case __sunConstants.LOG_OR: return new sunLogOR(location); + case __sunConstants.LOG_NOT: return new sunLogNOT(location); + + case __sunConstants.EQ: return new sunEq(location); + case __sunConstants.NEQ: return new sunNtEq(location); + case __sunConstants.LT: return new sunLt(location); + case __sunConstants.GT: return new sunGt(location); + case __sunConstants.LTEQ: return new sunLtEq(location); + case __sunConstants.GTEQ: return new sunGtEq(location); + + case __sunConstants.ASSIGN: return new sunAssign(location); + case __sunConstants.ASSIGN_ADD: return new sunAssignAdd(location); + case __sunConstants.ASSIGN_SUB: return new sunAssignSub(location); + case __sunConstants.ASSIGN_MUL: return new sunAssignMul(location); + case __sunConstants.ASSIGN_DIV: return new sunAssignDiv(location); + case __sunConstants.ASSIGN_MOD: return new sunAssignMod(location); + + case __sunConstants.ASSIGN_BIT_AND: return new sunAssignBitAND(location); + case __sunConstants.ASSIGN_BIT_OR: return new sunAssignBitOR(location); + case __sunConstants.ASSIGN_BIT_LSH: return new sunAssignBitLsh(location); + case __sunConstants.ASSIGN_BIT_RSH: return new sunAssignBitRsh(location); + + case __sunConstants.ASSIGNMENT_OPERATOR: return new sunNode(location); + case __sunConstants.TERNARY_OPERATOR: return new sunTernaryOperator(location); + case __sunConstants.BINARY_OPERATOR: return new sunNode(location); + case __sunConstants.UNARY_OPERATOR: return new sunNode(location); + } + + // expressions + switch (GetId(node)) + { + case __sunConstants.EXPRESSION: return new sunExpression(location); + case __sunConstants.OPERAND: return new sunOperand(location); + case __sunConstants.TERM: return new sunNode(location); + + case __sunConstants.UNARY_OPERATOR_LIST: return new sunUnaryOperatorList(location); + + case __sunConstants.INT_CAST: return new sunIntCast(location); + case __sunConstants.FLOAT_CAST: return new sunFloatCast(location); + case __sunConstants.TYPEOF_CAST: return new sunTypeofCast(location); + } + + // builtins + switch (GetId(node)) + { + case __sunConstants.BUILTIN_DECLARATION: return new sunBuiltinDeclaration(location); + } + + // functions + switch (GetId(node)) + { + case __sunConstants.FUNCTION_DEFINITION: return new sunFunctionDefinition(location); + case __sunConstants.FUNCTION_CALL: return new sunFunctionCall(location); + + case __sunConstants.PARAMETER_LIST: return new sunParameterList(location); + case __sunConstants.PARAMETER: return new sunNode(location); + case __sunConstants.ARGUMENT_LIST: return new sunNode(location); + case __sunConstants.ARGUMENT: return new sunNode(location); + } + + // variables + switch (GetId(node)) + { + case __sunConstants.VARIABLE_REFERENCE: return new sunVariableReference(location); + case __sunConstants.VARIABLE_DECLARATION: return new sunVariableDeclaration(location); + case __sunConstants.VARIABLE_DEFINITION: return new sunVariableDefinition(location); + case __sunConstants.VARIABLE_ASSIGNMENT: return new sunVariableAssignment(location); + } + + // constants + switch (GetId(node)) + { + case __sunConstants.CONST_DEFINITION: return new sunConstDefinition(location); + } + + // flow control + switch (GetId(node)) + { + case __sunConstants.IF_STATEMENT: return new sunIf(location); + case __sunConstants.WHILE_STATEMENT: return new sunWhile(location); + case __sunConstants.DO_STATEMENT: return new sunDo(location); + case __sunConstants.FOR_STATEMENT: return new sunFor(location); + case __sunConstants.FOR_DECLARATION: return new sunForDeclaration(location); + case __sunConstants.FOR_CONDITION: return new sunForCondition(location); + case __sunConstants.FOR_ITERATION: return new sunForIteration(location); + + case __sunConstants.RETURN_STATEMENT: return new sunReturn(location); + case __sunConstants.BREAK_STATEMENT: return new sunBreak(location); + case __sunConstants.CONTINUE_STATEMENT: return new sunContinue(location); + } + + // cleanup keywords punctuation + switch (GetId(node)) + { + // keywords + case __sunConstants.IMPORT: + case __sunConstants.BUILTIN: + case __sunConstants.FUNCTION: + case __sunConstants.VAR: + case __sunConstants.CONST: + + case __sunConstants.IF: + case __sunConstants.ELSE: + case __sunConstants.DO: + case __sunConstants.WHILE: + case __sunConstants.FOR: + + case __sunConstants.RETURN: + case __sunConstants.BREAK: + case __sunConstants.CONTINUE: + + case __sunConstants.YIELD: + case __sunConstants.EXIT: + case __sunConstants.DUMP: + case __sunConstants.LOCK: + case __sunConstants.UNLOCK: + case __sunConstants.INT: + case __sunConstants.FLOAT: + case __sunConstants.TYPEOF: + case __sunConstants.PRINT: + + case __sunConstants.TRUE: + case __sunConstants.FALSE: + + // punctuation + case __sunConstants.L_BRACE: + case __sunConstants.R_BRACE: + case __sunConstants.L_PAREN: + case __sunConstants.R_PAREN: + case __sunConstants.L_BRACKET: + case __sunConstants.R_BRACKET: + case __sunConstants.COLON: + case __sunConstants.SEMICOLON: + case __sunConstants.COMMA: + case __sunConstants.DOT: + // case __sunConstants.ELLIPSIS: // do not exclude ellipsis for variadic parameters + case __sunConstants.QMARK: + { + return null; + } + } + + // emergency fallback + return null; + } + static sunSourceLocation GetSourceLocation(string file, Node node) + { + if (file == null) + { + throw new ArgumentNullException("file"); + } + if (node == null) + { + throw new ArgumentNullException("node"); + } + if (node is Production) + { + var production = node as Production; + if (production.Count > 0) + { + return GetSourceLocation(file, production[0]); + } + throw new ArgumentException("node is a child-less production.", "node"); + } + else if (node is Token) + { + var token = node as Token; + return new sunSourceLocation(file, token.StartLine, token.StartColumn); + } + throw new ArgumentException("node is an unsupported type.", "node"); + } + static sunNode Transcient(sunNode node) + { + if (node == null) + { + throw new ArgumentNullException("node"); + } + return node.Count == 1 ? node[0] : node; + } + static __sunConstants GetId(Node node) + { + return (__sunConstants)node.Id; + } + + public static bool IsKeyword(string name) + { + return keywords.Contains(name); + } + } +} diff --git a/scope stack.cs b/scope stack.cs new file mode 100644 index 0000000..3011aae --- /dev/null +++ b/scope stack.cs @@ -0,0 +1,93 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace arookas +{ + class sunScopeStack : IEnumerable + { + List stack = new List(8); + + public int Count { get { return stack.Count; } } + public bool IsRoot { get { return Count == 1; } } + + public sunScope Root { get { return this[0]; } } + public sunScope Top { get { return this[Count - 1]; } } + + public sunScope this[int index] { get { return stack[index]; } } + + public sunScopeStack() + { + Push(); // push global scope + } + + public void Push() + { + stack.Add(new sunScope()); + } + public void Pop() + { + if (!IsRoot) + { + stack.RemoveAt(Count - 1); + } + } + public void Clear() + { + stack.Clear(); + Push(); // add global scope + } + + public bool GetIsVariableDeclared(string name) { return stack.Any(i => i.GetIsVariableDeclared(name)); } + + public IEnumerator GetEnumerator() { return stack.GetEnumerator(); } + IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } + } + + class sunScope + { + List variables = new List(10); + List constants = new List(10); + + public int VariableCount { get { return variables.Count; } } + public int ConstantCount { get { return constants.Count; } } + + public bool GetIsVariableDeclared(string name) { return variables.Any(v => v.Name == name); } + public sunVariableInfo DeclareVariable(string name, int display) + { + if (GetIsVariableDeclared(name) || GetIsConstantDeclared(name)) + { + return null; + } + var variableInfo = new sunVariableInfo(name, display, variables.Count); + variables.Add(variableInfo); + return variableInfo; + } + public sunVariableInfo ResolveVariable(string name) { return variables.FirstOrDefault(v => v.Name == name); } + + public bool GetIsConstantDeclared(string name) { return constants.Any(c => c.Name == name); } + public sunConstInfo DeclareConstant(string name, sunExpression expression) + { + if (GetIsVariableDeclared(name) || GetIsConstantDeclared(name)) + { + return null; + } + var constInfo = new sunConstInfo(name, expression); + constants.Add(constInfo); + return constInfo; + } + public sunConstInfo ResolveConstant(string name) { return constants.FirstOrDefault(c => c.Name == name); } + } + + class sunConstInfo + { + public string Name { get; private set; } + public sunExpression Expression { get; private set; } + + public sunConstInfo(string name, sunExpression expression) + { + Name = name; + Expression = expression; + } + } +} diff --git a/sunscript.grammar b/sunscript.grammar new file mode 100644 index 0000000..f4e00fa --- /dev/null +++ b/sunscript.grammar @@ -0,0 +1,219 @@ +%header% + +GRAMMARTYPE = "LL" + +DESCRIPTION = "A grammar for SunScript." + +AUTHOR = "arookas" +VERSION = "1.1" +DATE = "2015/11/29" + +LICENSE = "No license." + +COPYRIGHT = "Copyright (c) 2015 arookas" + +%tokens% + +// whitespace +WHITESPACE = <<[ \t\r\n]+>> %ignore% + +// comments +SINGLE_LINE_COMMENT = <> %ignore% +MULTI_LINE_COMMENT = <> %ignore% + +// keywords +IMPORT = "import" + +BUILTIN = "builtin" +FUNCTION = "function" +VAR = "var" +CONST = "const" + +IF = "if" +ELSE = "else" +DO = "do" +WHILE = "while" +FOR = "for" + +RETURN = "return" +BREAK = "break" +CONTINUE = "continue" + +YIELD = "yield" +EXIT = "exit" +DUMP = "dump" +LOCK = "lock" +UNLOCK = "unlock" +INT = "int" +FLOAT = "float" +TYPEOF = "typeof" +PRINT = "print" + +TRUE = "true" +FALSE = "false" + +// punctuation +L_BRACE = "{" +R_BRACE = "}" +L_PAREN = "(" +R_PAREN = ")" +L_BRACKET = "[" +R_BRACKET = "]" +COLON = ":" +SEMICOLON = ";" +COMMA = "," +DOT = "." +ELLIPSIS = "..." +QMARK = "?" + +// operators +ADD = "+" +SUB = "-" // doubles as the negation operator +MUL = "*" +DIV = "/" +MOD = "%" + +BIT_AND = "&" +BIT_OR = "|" +BIT_LSH = "<<" +BIT_RSH = ">>" + +LOG_NOT = "!" +LOG_AND = "&&" +LOG_OR = "||" + +EQ = "==" +NEQ = "!=" +LT = "<" +LTEQ = "<=" +GT = ">" +GTEQ = ">=" + +ASSIGN = "=" +ASSIGN_ADD = "+=" +ASSIGN_SUB = "-=" +ASSIGN_MUL = "*=" +ASSIGN_DIV = "/=" +ASSIGN_MOD = "%=" +ASSIGN_BIT_AND = "&=" +ASSIGN_BIT_OR = "|=" +ASSIGN_BIT_LSH = "<<=" +ASSIGN_BIT_RSH = ">>=" + +// literals +IDENTIFIER = <<[_A-Za-z][_A-Za-z0-9]*>> +DEC_NUMBER = <<-?[0-9]+\.[0-9]+>> +HEX_NUMBER = <<-?0x[0-9A-Fa-f]+>> +INT_NUMBER = <<-?[0-9]+>> +STRING = <<"(\\.|[^"])*">> + +%productions% + +script = statement+; + +// statements +statement = + import_statement SEMICOLON | + compound_statement SEMICOLON | + function_definition | + builtin_declaration SEMICOLON | + if_statement | + while_statement | + do_statement SEMICOLON | + for_statement | + return_statement SEMICOLON | + break_statement SEMICOLON | + continue_statement SEMICOLON | + yield_statement SEMICOLON | + exit_statement SEMICOLON | + dump_statement SEMICOLON | + lock_statement SEMICOLON | + unlock_statement SEMICOLON | + statement_block; +compound_statement = compound_statement_item {COMMA compound_statement_item}; +compound_statement_item = + const_definition | + variable_definition | + variable_declaration | + variable_assignment | + print_statement | + function_call; +statement_block = L_BRACE {statement} R_BRACE; + +import_statement = IMPORT STRING; +yield_statement = YIELD; +exit_statement = EXIT; +dump_statement = DUMP; +lock_statement = LOCK; +unlock_statement = UNLOCK; +print_statement = PRINT argument_list; + +name_label = IDENTIFIER COLON; + +// operators +assignment_operator = ASSIGN | ASSIGN_ADD | ASSIGN_SUB | ASSIGN_MUL | ASSIGN_DIV | ASSIGN_MOD | ASSIGN_BIT_AND | ASSIGN_BIT_OR | ASSIGN_BIT_LSH | ASSIGN_BIT_RSH; +ternary_operator = expression QMARK expression COLON expression; +binary_operator = + ADD | SUB | MUL | DIV | MOD | // arithmetic + BIT_AND | BIT_OR | BIT_LSH | BIT_RSH | // bitwise + EQ | NEQ | LT | LTEQ | GT | GTEQ | // comparison + LOG_AND | LOG_OR; // logical +unary_operator = LOG_NOT | SUB; + +// expressions +expression = operand {binary_operator operand}; +operand = [unary_operator_list] term; +term = + int_cast | + float_cast | + typeof_cast | + function_call | + variable_reference | + STRING | + DEC_NUMBER | + HEX_NUMBER | + INT_NUMBER | + TRUE | + FALSE | + L_PAREN expression R_PAREN | + L_BRACKET ternary_operator R_BRACKET; // HACK: the brackets remove ambiguity + +unary_operator_list = unary_operator+; + +int_cast = INT L_PAREN expression R_PAREN; +float_cast = FLOAT L_PAREN expression R_PAREN; +typeof_cast = TYPEOF L_PAREN expression R_PAREN; + +// constants +const_definition = CONST IDENTIFIER ASSIGN expression; + +// variables +variable_reference = IDENTIFIER; // used in expressions +variable_declaration = VAR IDENTIFIER; +variable_definition = VAR IDENTIFIER assignment_operator expression; +variable_assignment = IDENTIFIER assignment_operator expression; + +// functions +function_definition = FUNCTION IDENTIFIER parameter_list statement_block; +function_call = IDENTIFIER argument_list; + +parameter_list = L_PAREN [parameter {COMMA parameter}] R_PAREN; // e.g. (a, b, ...) +parameter = IDENTIFIER | ELLIPSIS; +argument_list = L_PAREN [argument {COMMA argument}] R_PAREN; +argument = expression; + +// builtins +builtin_declaration = BUILTIN IDENTIFIER parameter_list; + +// flow control +if_statement = [name_label] IF expression statement [ELSE statement]; +while_statement = [name_label] WHILE expression statement; +do_statement = [name_label] DO statement WHILE expression; +for_statement = [name_label] FOR L_PAREN [for_declaration] SEMICOLON [for_condition] SEMICOLON [for_iteration] R_PAREN statement; +for_declaration = compound_statement; +for_condition = expression; +for_iteration = compound_statement; + +return_statement = RETURN [expression]; +break_statement = BREAK [IDENTIFIER]; +continue_statement = CONTINUE [IDENTIFIER]; diff --git a/symbol table.cs b/symbol table.cs new file mode 100644 index 0000000..99158ac --- /dev/null +++ b/symbol table.cs @@ -0,0 +1,212 @@ +using arookas.Collections; +using arookas.IO.Binary; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace arookas +{ + class sunSymbolTable : IEnumerable + { + List symbols = new List(10); + + public int Count { get { return symbols.Count; } } + public int BuiltinCount { get { return symbols.Count(sym => sym.Type == sunSymbolType.Builtin); } } + public int FunctionCount { get { return symbols.Count(sym => sym.Type == sunSymbolType.Function); } } + public int VariableCount { get { return symbols.Count(sym => sym.Type == sunSymbolType.Variable); } } + + public IEnumerable Callables { get { return symbols.OfType(); } } + public IEnumerable Builtins { get { return symbols.Where(sym => sym.Type == sunSymbolType.Builtin).Cast(); } } + public IEnumerable Functions { get { return symbols.Where(sym => sym.Type == sunSymbolType.Function).Cast(); } } + public IEnumerable Variables { get { return symbols.Where(sym => sym.Type == sunSymbolType.Variable).Cast(); } } + + public void Add(sunSymbolInfo symbol) { symbols.Add(symbol); } + public void Clear() { symbols.Clear(); } + + public void Write(aBinaryWriter writer) + { + int ofs = 0; + foreach (var sym in this) + { + writer.WriteS32((int)sym.Type); + writer.WriteS32(ofs); + writer.Write32(sym.Data); + + // runtime fields + writer.WriteS32(0); + writer.WriteS32(0); + + ofs += sym.Name.Length + 1; // include null terminator + } + foreach (var sym in this) + { + writer.WriteString(sym.Name, aBinaryStringFormat.NullTerminated); + } + } + + public IEnumerator GetEnumerator() { return symbols.GetEnumerator(); } + IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } + } + + abstract class sunSymbolInfo + { + public string Name { get; private set; } + + // symbol table + public abstract sunSymbolType Type { get; } + public abstract uint Data { get; } + + protected sunSymbolInfo(string name) + { + Name = name; + } + } + + abstract class sunCallableSymbolInfo : sunSymbolInfo + { + public sunParameterInfo Parameters { get; private set; } + protected List CallSites { get; private set; } + + public bool HasCallSites { get { return CallSites.Count > 0; } } + + protected sunCallableSymbolInfo(string name, sunParameterInfo parameterInfo) + : base(name) + { + Parameters = parameterInfo; + CallSites = new List(10); + } + + public abstract void OpenCallSite(sunContext context, int argumentCount); + public abstract void CloseCallSites(sunContext context); + + public abstract void Compile(sunContext context); + } + + class sunBuiltinInfo : sunCallableSymbolInfo + { + public int Index { get; private set; } + + // symbol table + public override sunSymbolType Type { get { return sunSymbolType.Builtin; } } + public override uint Data { get { return (uint)Index; } } + + public sunBuiltinInfo(string name, sunParameterInfo parameters, int index) + : base(name, parameters) + { + Index = index; + } + + public override void Compile(sunContext context) + { + throw new InvalidOperationException("Cannot compile builtins."); + } + public override void OpenCallSite(sunContext context, int argumentCount) + { + context.Text.CallBuiltin(Index, argumentCount); + } + public override void CloseCallSites(sunContext context) + { + // do nothing + } + } + + class sunFunctionInfo : sunCallableSymbolInfo + { + sunNode Body { get; set; } + public uint Offset { get; private set; } + + // symbol table + public override sunSymbolType Type { get { return sunSymbolType.Function; } } + public override uint Data { get { return (uint)Offset; } } + + public sunFunctionInfo(string name, sunParameterInfo parameters, sunNode body) + : base(name, parameters) + { + Body = body; + } + + public override void Compile(sunContext context) + { + Offset = context.Text.Offset; + context.Scopes.Push(); + foreach (var parameter in Parameters) + { + context.DeclareParameter(parameter); + } + context.Text.StoreDisplay(1); + Body.Compile(context); + context.Text.ReturnVoid(); + context.Scopes.Pop(); + } + public override void OpenCallSite(sunContext context, int argumentCount) + { + var point = context.Text.CallFunction(argumentCount); + CallSites.Add(point); + } + public override void CloseCallSites(sunContext context) + { + foreach (var callSite in CallSites) + { + context.Text.ClosePoint(callSite, Offset); + } + } + } + + class sunParameterInfo : IEnumerable + { + string[] Parameters { get; set; } + public int Minimum { get { return Parameters.Length; } } + public bool IsVariadic { get; private set; } + + public sunParameterInfo(IEnumerable parameters, bool variadic) + { + // validate parameter names + var duplicate = parameters.FirstOrDefault(a => parameters.Count(b => a.Value == b.Value) > 1); + if (duplicate != null) + { + throw new sunRedeclaredParameterException(duplicate); + } + Parameters = parameters.Select(i => i.Value).ToArray(); + IsVariadic = variadic; + } + public sunParameterInfo(IEnumerable parameters, bool variadic) + { + // validate parameter names + Parameters = parameters.ToArray(); + IsVariadic = variadic; + } + + public bool ValidateArgumentCount(int count) + { + return IsVariadic ? count >= Minimum : count == Minimum; + } + + public IEnumerator GetEnumerator() { return Parameters.GetArrayEnumerator(); } + IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } + } + + class sunVariableInfo : sunSymbolInfo + { + public int Display { get; private set; } + public int Index { get; private set; } + + // symbol table + public override sunSymbolType Type { get { return sunSymbolType.Variable; } } + public override uint Data { get { return (uint)Index; } } + + public sunVariableInfo(string name, int display, int index) + : base(name) + { + Display = display; + Index = index; + } + } + + enum sunSymbolType + { + Builtin, + Function, + Variable, + } +} diff --git a/writer.cs b/writer.cs new file mode 100644 index 0000000..2ccc121 --- /dev/null +++ b/writer.cs @@ -0,0 +1,188 @@ +using arookas.IO.Binary; + +namespace arookas +{ + class sunWriter + { + aBinaryWriter writer; + + public uint Offset { get { return (uint)writer.Position; } } + + public sunWriter(aBinaryWriter writer) + { + this.writer = writer; + } + + public sunPoint OpenPoint() { return new sunPoint(Offset); } + public void ClosePoint(sunPoint point) + { + ClosePoint(point, (uint)writer.Position); + } + public void ClosePoint(sunPoint point, uint offset) + { + writer.Keep(); + writer.Goto(point.Offset); + writer.Write32(offset); + writer.Back(); + } + + public void PushInt(int value) + { + switch (value) // shortcut commands + { + case 0: writer.Write8(0x25); return; + case 1: writer.Write8(0x26); return; + } + writer.Write8(0x00); + writer.WriteS32(value); + } + public void PushFloat(float value) + { + writer.Write8(0x01); + writer.WriteF32(value); + } + public void PushData(int dataIndex) + { + writer.Write8(0x02); + writer.WriteS32(dataIndex); + } + public void PushVariable(sunVariableInfo variableInfo) + { + PushVariable(variableInfo.Display, variableInfo.Index); + } + public void PushVariable(int display, int variableIndex) + { + writer.Write8(0x04); + writer.WriteS32(display); + writer.WriteS32(variableIndex); + } + + public void IncVariable(sunVariableInfo variableInfo) + { + IncVariable(variableInfo.Display, variableInfo.Index); + } + public void DecVariable(sunVariableInfo variableInfo) + { + DecVariable(variableInfo.Display, variableInfo.Index); + } + public void IncVariable(int display, int variableIndex) + { + writer.Write8(0x06); + writer.WriteS32(display); + writer.WriteS32(variableIndex); + } + public void DecVariable(int display, int variableIndex) + { + writer.Write8(0x07); + writer.WriteS32(display); + writer.WriteS32(variableIndex); + } + + public void Add() { writer.Write8(0x08); } + public void Sub() { writer.Write8(0x09); } + public void Mul() { writer.Write8(0x0A); } + public void Div() { writer.Write8(0x0B); } + public void Mod() { writer.Write8(0x0C); } + + public void StoreVariable(sunVariableInfo variableInfo) + { + StoreVariable(variableInfo.Display, variableInfo.Index); + } + public void StoreVariable(int display, int variableIndex) + { + writer.Write8(0x0D); + writer.Write8(0x04); // unused (skipped over by TSpcInterp) + writer.WriteS32(display); + writer.WriteS32(variableIndex); + } + + public void Eq() { writer.Write8(0x0E); } + public void NtEq() { writer.Write8(0x0F); } + public void Gt() { writer.Write8(0x10); } + public void Lt() { writer.Write8(0x11); } + public void GtEq() { writer.Write8(0x12); } + public void LtEq() { writer.Write8(0x13); } + public void Neg() { writer.Write8(0x14); } + public void LogNOT() { writer.Write8(0x15); } + public void LogAND() { writer.Write8(0x16); } + public void LogOR() { writer.Write8(0x17); } + public void BitAND() { writer.Write8(0x18); } + public void BitOR() { writer.Write8(0x19); } + public void ShL() { writer.Write8(0x1A); } + public void ShR() { writer.Write8(0x1B); } + + public sunPoint CallFunction(int argumentCount) + { + writer.Write8(0x1C); + sunPoint point = OpenPoint(); + writer.Write32(0); // dummy + writer.WriteS32(argumentCount); + return point; + } + public void CallFunction(sunPoint point, int argumentCount) + { + writer.Write8(0x1C); + writer.Write32(point.Offset); + writer.WriteS32(argumentCount); + } + public void CallBuiltin(int symbolIndex, int argumentCount) + { + writer.Write8(0x1D); + writer.WriteS32(symbolIndex); + writer.WriteS32(argumentCount); + } + + public void DeclareLocal(int count) + { + writer.Write8(0x1E); + writer.WriteS32(count); + } + public void StoreDisplay(int display) + { + writer.Write8(0x1F); + writer.WriteS32(display); + } + + public void ReturnValue() { writer.Write8(0x20); } + public void ReturnVoid() { writer.Write8(0x21); } + + public sunPoint GotoIfZero() + { + writer.Write8(0x22); + sunPoint point = OpenPoint(); + writer.Write32(0); // dummy + return point; + } + public sunPoint Goto() + { + writer.Write8(0x23); + sunPoint point = OpenPoint(); + writer.Write32(0); // dummy + return point; + } + public void GotoIfZero(sunPoint point) + { + writer.Write8(0x22); + writer.Write32(point.Offset); + } + public void Goto(sunPoint point) + { + writer.Write8(0x23); + writer.Write32(point.Offset); + } + public void Pop() { writer.Write8(0x24); } + + public void Terminate() { writer.Write8(0x27); } + } + + struct sunPoint + { + readonly uint offset; + public uint Offset { get { return offset; } } + + public sunPoint(uint offset) + { + this.offset = offset; + } + } +}