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; + } + } +}