From ad50a190572c0a4f84b2b8e5788f2db9cc168f35 Mon Sep 17 00:00:00 2001 From: Rohan Singh Date: Wed, 23 Jan 2019 22:52:45 -0500 Subject: [PATCH] Add code --- README.md | 6 +- ScriptConverter.sln | 22 + ScriptConverter/App.config | 6 + .../Ast/Declarations/Declaration.cs | 24 + .../Ast/Declarations/FieldDeclaration.cs | 34 + .../Ast/Declarations/IncludeDeclaration.cs | 25 + .../Ast/Declarations/InheritsDeclaration.cs | 25 + .../Ast/Declarations/MethodDeclaration.cs | 46 + ScriptConverter/Ast/Document.cs | 19 + .../Expressions/ArrayInitializerExpression.cs | 32 + .../Expressions/BinaryOperatorExpression.cs | 32 + .../Ast/Expressions/BoolExpression.cs | 25 + .../Ast/Expressions/CallExpression.cs | 36 + .../Ast/Expressions/CastExpression.cs | 29 + ScriptConverter/Ast/Expressions/Expression.cs | 25 + .../Ast/Expressions/FieldExpression.cs | 29 + .../Ast/Expressions/IdentifierExpression.cs | 25 + .../Ast/Expressions/IndexerExpression.cs | 30 + .../Ast/Expressions/InstanceOfExpression.cs | 29 + .../Ast/Expressions/NewExpression.cs | 47 + .../Ast/Expressions/NullExpression.cs | 23 + .../Ast/Expressions/NumberExpression.cs | 25 + .../Expressions/PostfixOperatorExpression.cs | 29 + .../Expressions/PrefixOperatorExpression.cs | 29 + .../Ast/Expressions/StringExpression.cs | 27 + .../Ast/Expressions/TernaryExpression.cs | 31 + ScriptConverter/Ast/IAstVisitor.cs | 48 + ScriptConverter/Ast/ScriptType.cs | 48 + .../Ast/Statements/BlockStatement.cs | 38 + .../Ast/Statements/BreakStatement.cs | 23 + .../Ast/Statements/ContinueStatement.cs | 23 + ScriptConverter/Ast/Statements/DoStatement.cs | 31 + .../Ast/Statements/EmptyStatement.cs | 23 + .../Ast/Statements/ForStatement.cs | 55 + ScriptConverter/Ast/Statements/IfStatement.cs | 53 + .../Ast/Statements/NakedStatement.cs | 27 + .../Ast/Statements/ReturnStatement.cs | 29 + ScriptConverter/Ast/Statements/Statement.cs | 25 + .../Ast/Statements/SwitchStatement.cs | 55 + .../Ast/Statements/ThrowStatement.cs | 28 + .../Ast/Statements/TryStatement.cs | 53 + .../Ast/Statements/VariableStatement.cs | 56 + .../Ast/Statements/WhileStatement.cs | 31 + ScriptConverter/AstPrinter.cs | 1136 +++++++++++++++++ ScriptConverter/CoreAnnotations.cs | 406 ++++++ ScriptConverter/GenericComparer.cs | 20 + ScriptConverter/IndentTextWriter.cs | 63 + ScriptConverter/Parser/CompilerError.cs | 13 + ScriptConverter/Parser/CompilerException.cs | 21 + ScriptConverter/Parser/Lexer.Static.cs | 60 + ScriptConverter/Parser/Lexer.cs | 224 ++++ ScriptConverter/Parser/LexerException.cs | 15 + .../Parselets/Declarations/ConstParselet.cs | 23 + .../Parselets/Declarations/IncludeParselet.cs | 34 + .../Declarations/InheritsParselet.cs | 21 + .../Parselets/Declarations/MethodParselet.cs | 43 + .../Parselets/Declarations/PublicParselet.cs | 23 + .../Expressions/ArrayInitializerParselet.cs | 22 + .../Expressions/BinaryOperatorParselet.cs | 23 + .../Parselets/Expressions/BoolParselet.cs | 19 + .../Parselets/Expressions/CallParselet.cs | 25 + .../Parselets/Expressions/FieldParselet.cs | 15 + .../Parselets/Expressions/GroupParselet.cs | 54 + .../Parselets/Expressions/HashParselet.cs | 86 ++ .../Expressions/IdentifierParselet.cs | 12 + .../Parselets/Expressions/IndexerParselet.cs | 18 + .../Expressions/InstanceOfParselet.cs | 15 + .../Parselets/Expressions/NewParselet.cs | 58 + .../Parselets/Expressions/NullParselet.cs | 12 + .../Parselets/Expressions/NumberParselet.cs | 12 + .../Expressions/PostfixOperatorParselet.cs | 19 + .../Expressions/PrefixOperatorParselet.cs | 20 + .../Parselets/Expressions/StringParselet.cs | 19 + .../Parselets/Expressions/TernaryParselet.cs | 18 + .../Parser/Parselets/IDeclarationParselet.cs | 9 + .../Parser/Parselets/IInfixParselet.cs | 11 + .../Parser/Parselets/IPrefixParselet.cs | 9 + .../Parser/Parselets/IStatementParselet.cs | 9 + .../Parselets/Statements/BlockParselet.cs | 24 + .../Parselets/Statements/BreakParselet.cs | 13 + .../Statements/ConstVariableParselet.cs | 29 + .../Parselets/Statements/ContinueParselet.cs | 13 + .../Parser/Parselets/Statements/DoParselet.cs | 23 + .../Parselets/Statements/EmptyParselet.cs | 13 + .../Parselets/Statements/ForParselet.cs | 49 + .../Parser/Parselets/Statements/IfParselet.cs | 47 + .../Parselets/Statements/ReturnParselet.cs | 18 + .../Parselets/Statements/SwitchParselet.cs | 84 ++ .../Parselets/Statements/ThrowParselet.cs | 15 + .../Parselets/Statements/TryParselet.cs | 34 + .../Parselets/Statements/VariableParselet.cs | 42 + .../Parselets/Statements/WhileParselet.cs | 20 + ScriptConverter/Parser/Parser.cs | 109 ++ ScriptConverter/Parser/PrecedenceValue.cs | 22 + ScriptConverter/Parser/ScriptLexer.Static.cs | 105 ++ ScriptConverter/Parser/ScriptLexer.cs | 254 ++++ ScriptConverter/Parser/ScriptParser.Static.cs | 122 ++ ScriptConverter/Parser/ScriptParser.cs | 237 ++++ ScriptConverter/Parser/SourcePosition.cs | 35 + ScriptConverter/Parser/Token.cs | 172 +++ ScriptConverter/Program.cs | 125 ++ ScriptConverter/Properties/AssemblyInfo.cs | 36 + ScriptConverter/ScriptConverter.csproj | 151 +++ 103 files changed, 5734 insertions(+), 1 deletion(-) create mode 100644 ScriptConverter.sln create mode 100644 ScriptConverter/App.config create mode 100644 ScriptConverter/Ast/Declarations/Declaration.cs create mode 100644 ScriptConverter/Ast/Declarations/FieldDeclaration.cs create mode 100644 ScriptConverter/Ast/Declarations/IncludeDeclaration.cs create mode 100644 ScriptConverter/Ast/Declarations/InheritsDeclaration.cs create mode 100644 ScriptConverter/Ast/Declarations/MethodDeclaration.cs create mode 100644 ScriptConverter/Ast/Document.cs create mode 100644 ScriptConverter/Ast/Expressions/ArrayInitializerExpression.cs create mode 100644 ScriptConverter/Ast/Expressions/BinaryOperatorExpression.cs create mode 100644 ScriptConverter/Ast/Expressions/BoolExpression.cs create mode 100644 ScriptConverter/Ast/Expressions/CallExpression.cs create mode 100644 ScriptConverter/Ast/Expressions/CastExpression.cs create mode 100644 ScriptConverter/Ast/Expressions/Expression.cs create mode 100644 ScriptConverter/Ast/Expressions/FieldExpression.cs create mode 100644 ScriptConverter/Ast/Expressions/IdentifierExpression.cs create mode 100644 ScriptConverter/Ast/Expressions/IndexerExpression.cs create mode 100644 ScriptConverter/Ast/Expressions/InstanceOfExpression.cs create mode 100644 ScriptConverter/Ast/Expressions/NewExpression.cs create mode 100644 ScriptConverter/Ast/Expressions/NullExpression.cs create mode 100644 ScriptConverter/Ast/Expressions/NumberExpression.cs create mode 100644 ScriptConverter/Ast/Expressions/PostfixOperatorExpression.cs create mode 100644 ScriptConverter/Ast/Expressions/PrefixOperatorExpression.cs create mode 100644 ScriptConverter/Ast/Expressions/StringExpression.cs create mode 100644 ScriptConverter/Ast/Expressions/TernaryExpression.cs create mode 100644 ScriptConverter/Ast/IAstVisitor.cs create mode 100644 ScriptConverter/Ast/ScriptType.cs create mode 100644 ScriptConverter/Ast/Statements/BlockStatement.cs create mode 100644 ScriptConverter/Ast/Statements/BreakStatement.cs create mode 100644 ScriptConverter/Ast/Statements/ContinueStatement.cs create mode 100644 ScriptConverter/Ast/Statements/DoStatement.cs create mode 100644 ScriptConverter/Ast/Statements/EmptyStatement.cs create mode 100644 ScriptConverter/Ast/Statements/ForStatement.cs create mode 100644 ScriptConverter/Ast/Statements/IfStatement.cs create mode 100644 ScriptConverter/Ast/Statements/NakedStatement.cs create mode 100644 ScriptConverter/Ast/Statements/ReturnStatement.cs create mode 100644 ScriptConverter/Ast/Statements/Statement.cs create mode 100644 ScriptConverter/Ast/Statements/SwitchStatement.cs create mode 100644 ScriptConverter/Ast/Statements/ThrowStatement.cs create mode 100644 ScriptConverter/Ast/Statements/TryStatement.cs create mode 100644 ScriptConverter/Ast/Statements/VariableStatement.cs create mode 100644 ScriptConverter/Ast/Statements/WhileStatement.cs create mode 100644 ScriptConverter/AstPrinter.cs create mode 100644 ScriptConverter/CoreAnnotations.cs create mode 100644 ScriptConverter/GenericComparer.cs create mode 100644 ScriptConverter/IndentTextWriter.cs create mode 100644 ScriptConverter/Parser/CompilerError.cs create mode 100644 ScriptConverter/Parser/CompilerException.cs create mode 100644 ScriptConverter/Parser/Lexer.Static.cs create mode 100644 ScriptConverter/Parser/Lexer.cs create mode 100644 ScriptConverter/Parser/LexerException.cs create mode 100644 ScriptConverter/Parser/Parselets/Declarations/ConstParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Declarations/IncludeParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Declarations/InheritsParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Declarations/MethodParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Declarations/PublicParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Expressions/ArrayInitializerParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Expressions/BinaryOperatorParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Expressions/BoolParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Expressions/CallParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Expressions/FieldParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Expressions/GroupParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Expressions/HashParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Expressions/IdentifierParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Expressions/IndexerParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Expressions/InstanceOfParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Expressions/NewParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Expressions/NullParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Expressions/NumberParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Expressions/PostfixOperatorParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Expressions/PrefixOperatorParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Expressions/StringParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Expressions/TernaryParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/IDeclarationParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/IInfixParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/IPrefixParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/IStatementParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Statements/BlockParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Statements/BreakParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Statements/ConstVariableParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Statements/ContinueParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Statements/DoParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Statements/EmptyParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Statements/ForParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Statements/IfParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Statements/ReturnParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Statements/SwitchParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Statements/ThrowParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Statements/TryParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Statements/VariableParselet.cs create mode 100644 ScriptConverter/Parser/Parselets/Statements/WhileParselet.cs create mode 100644 ScriptConverter/Parser/Parser.cs create mode 100644 ScriptConverter/Parser/PrecedenceValue.cs create mode 100644 ScriptConverter/Parser/ScriptLexer.Static.cs create mode 100644 ScriptConverter/Parser/ScriptLexer.cs create mode 100644 ScriptConverter/Parser/ScriptParser.Static.cs create mode 100644 ScriptConverter/Parser/ScriptParser.cs create mode 100644 ScriptConverter/Parser/SourcePosition.cs create mode 100644 ScriptConverter/Parser/Token.cs create mode 100644 ScriptConverter/Program.cs create mode 100644 ScriptConverter/Properties/AssemblyInfo.cs create mode 100644 ScriptConverter/ScriptConverter.csproj diff --git a/README.md b/README.md index 5fffedd..1093a3d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,6 @@ # SWG-ScriptConverter -Some tool I made in 2015 to convert the original Star Wars Galaxies server scripts to Java. +In 2015 someone asked me to make a tool to convert the original Star Wars Galaxies server scripts to Java. I believe it resulted in [this project](https://github.com/SWG-Source) as they have the scripts in Java form over there. That seems to be a fork though so it's probably in other places too. + +The source they have on [their repo](https://github.com/SWG-Source/dsrc/tree/master/sku.0/sys.server/compiled/game/script) is missing all the fun comments that were in the original scripts. I didn't bother preserving them in the output from this tool. + +Also I only got this to the point where you could compile the resulting Java source into class files. There is almost certainly a version out there that has additional fixes to this code. diff --git a/ScriptConverter.sln b/ScriptConverter.sln new file mode 100644 index 0000000..17c8f3e --- /dev/null +++ b/ScriptConverter.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScriptConverter", "ScriptConverter\ScriptConverter.csproj", "{3F560E64-ACCE-4520-B3E7-EA6502E20F49}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3F560E64-ACCE-4520-B3E7-EA6502E20F49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3F560E64-ACCE-4520-B3E7-EA6502E20F49}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3F560E64-ACCE-4520-B3E7-EA6502E20F49}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3F560E64-ACCE-4520-B3E7-EA6502E20F49}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/ScriptConverter/App.config b/ScriptConverter/App.config new file mode 100644 index 0000000..8e15646 --- /dev/null +++ b/ScriptConverter/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/ScriptConverter/Ast/Declarations/Declaration.cs b/ScriptConverter/Ast/Declarations/Declaration.cs new file mode 100644 index 0000000..d168297 --- /dev/null +++ b/ScriptConverter/Ast/Declarations/Declaration.cs @@ -0,0 +1,24 @@ +using System; +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Declarations +{ + abstract class Declaration + { + public readonly ScriptToken Start; + public readonly ScriptToken End; + + protected Declaration(ScriptToken start, ScriptToken end = null) + { + if (start == null) + throw new ArgumentNullException("start"); + + Start = start; + End = end ?? start; + } + + public abstract TDecl Accept(IAstVisitor visitor); + + public abstract void SetParent(); + } +} diff --git a/ScriptConverter/Ast/Declarations/FieldDeclaration.cs b/ScriptConverter/Ast/Declarations/FieldDeclaration.cs new file mode 100644 index 0000000..b097355 --- /dev/null +++ b/ScriptConverter/Ast/Declarations/FieldDeclaration.cs @@ -0,0 +1,34 @@ +using ScriptConverter.Ast.Expressions; +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Declarations +{ + class FieldDeclaration : Declaration + { + public ScriptType Type { get; private set; } + public string Name { get; private set; } + public Expression Value { get; private set; } + public bool IsConstant { get; private set; } + public bool IsPublic { get; private set; } + + public FieldDeclaration(ScriptToken start, ScriptToken end, ScriptType type, string name, Expression value, bool isConst, bool isPublic) + : base(start, end) + { + Type = type; + Name = name; + Value = value; + IsConstant = isConst; + IsPublic = isPublic; + } + + public override TDecl Accept(IAstVisitor visitor) + { + return visitor.Visit(this); + } + + public override void SetParent() + { + Value.SetParent(null); + } + } +} diff --git a/ScriptConverter/Ast/Declarations/IncludeDeclaration.cs b/ScriptConverter/Ast/Declarations/IncludeDeclaration.cs new file mode 100644 index 0000000..6b53a30 --- /dev/null +++ b/ScriptConverter/Ast/Declarations/IncludeDeclaration.cs @@ -0,0 +1,25 @@ +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Declarations +{ + class IncludeDeclaration : Declaration + { + public string Name { get; private set; } + + public IncludeDeclaration(ScriptToken start, ScriptToken end, string name) + : base(start, end) + { + Name = name; + } + + public override TDecl Accept(IAstVisitor visitor) + { + return visitor.Visit(this); + } + + public override void SetParent() + { + + } + } +} diff --git a/ScriptConverter/Ast/Declarations/InheritsDeclaration.cs b/ScriptConverter/Ast/Declarations/InheritsDeclaration.cs new file mode 100644 index 0000000..61c87c9 --- /dev/null +++ b/ScriptConverter/Ast/Declarations/InheritsDeclaration.cs @@ -0,0 +1,25 @@ +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Declarations +{ + class InheritsDeclaration : Declaration + { + public string Name { get; private set; } + + public InheritsDeclaration(ScriptToken start, ScriptToken end, string name) + : base(start, end) + { + Name = name; + } + + public override TDecl Accept(IAstVisitor visitor) + { + return visitor.Visit(this); + } + + public override void SetParent() + { + + } + } +} diff --git a/ScriptConverter/Ast/Declarations/MethodDeclaration.cs b/ScriptConverter/Ast/Declarations/MethodDeclaration.cs new file mode 100644 index 0000000..d2246f5 --- /dev/null +++ b/ScriptConverter/Ast/Declarations/MethodDeclaration.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using ScriptConverter.Ast.Statements; +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Declarations +{ + class MethodDeclaration : Declaration + { + public class Parameter + { + public ScriptType Type { get; private set; } + public string Name { get; private set; } + + public Parameter(ScriptType type, string name) + { + Type = type; + Name = name; + } + } + + public ScriptType ReturnType { get; private set; } + public string Name { get; private set; } + public ReadOnlyCollection Parameters { get; private set; } + public BlockStatement Block { get; private set; } + + public MethodDeclaration(ScriptToken start, ScriptToken end, ScriptType returnType, string name, List parameters, BlockStatement block) + : base(start, end) + { + ReturnType = returnType; + Name = name; + Parameters = parameters.AsReadOnly(); + Block = block; + } + + public override TDecl Accept(IAstVisitor visitor) + { + return visitor.Visit(this); + } + + public override void SetParent() + { + Block.SetParent(null); + } + } +} diff --git a/ScriptConverter/Ast/Document.cs b/ScriptConverter/Ast/Document.cs new file mode 100644 index 0000000..12ec918 --- /dev/null +++ b/ScriptConverter/Ast/Document.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using ScriptConverter.Ast.Declarations; + +namespace ScriptConverter.Ast +{ + class Document + { + public ReadOnlyCollection Declarations { get; private set; } + + public Document(IEnumerable declarations) + { + Declarations = declarations + .ToList() + .AsReadOnly(); + } + } +} diff --git a/ScriptConverter/Ast/Expressions/ArrayInitializerExpression.cs b/ScriptConverter/Ast/Expressions/ArrayInitializerExpression.cs new file mode 100644 index 0000000..d994bc4 --- /dev/null +++ b/ScriptConverter/Ast/Expressions/ArrayInitializerExpression.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Expressions +{ + class ArrayInitializerExpression : Expression + { + public ReadOnlyCollection Values { get; private set; } + + public ArrayInitializerExpression(ScriptToken start, ScriptToken end, List values) + : base(start, end) + { + Values = values.AsReadOnly(); + } + + public override TExpr Accept(IAstVisitor visitor) + { + return visitor.Visit(this); + } + + public override void SetParent(Expression parent) + { + Parent = parent; + + foreach (var expression in Values) + { + expression.SetParent(this); + } + } + } +} diff --git a/ScriptConverter/Ast/Expressions/BinaryOperatorExpression.cs b/ScriptConverter/Ast/Expressions/BinaryOperatorExpression.cs new file mode 100644 index 0000000..3eeb23a --- /dev/null +++ b/ScriptConverter/Ast/Expressions/BinaryOperatorExpression.cs @@ -0,0 +1,32 @@ +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Expressions +{ + class BinaryOperatorExpression : Expression + { + public ScriptTokenType Operation { get; private set; } + public Expression Left { get; private set; } + public Expression Right { get; private set; } + + public BinaryOperatorExpression(ScriptTokenType op, Expression left, Expression right) + : base(left.Start, right.End) + { + Operation = op; + Left = left; + Right = right; + } + + public override TExpr Accept(IAstVisitor visitor) + { + return visitor.Visit(this); + } + + public override void SetParent(Expression parent) + { + Parent = parent; + + Left.SetParent(this); + Right.SetParent(this); + } + } +} diff --git a/ScriptConverter/Ast/Expressions/BoolExpression.cs b/ScriptConverter/Ast/Expressions/BoolExpression.cs new file mode 100644 index 0000000..d78373c --- /dev/null +++ b/ScriptConverter/Ast/Expressions/BoolExpression.cs @@ -0,0 +1,25 @@ +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Expressions +{ + class BoolExpression : Expression + { + public bool Value { get; private set; } + + public BoolExpression(ScriptToken token, bool value) + : base(token) + { + Value = value; + } + + public override TExpr Accept(IAstVisitor visitor) + { + return visitor.Visit(this); + } + + public override void SetParent(Expression parent) + { + Parent = parent; + } + } +} diff --git a/ScriptConverter/Ast/Expressions/CallExpression.cs b/ScriptConverter/Ast/Expressions/CallExpression.cs new file mode 100644 index 0000000..d8205ef --- /dev/null +++ b/ScriptConverter/Ast/Expressions/CallExpression.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Expressions +{ + class CallExpression : Expression + { + public Expression Left { get; private set; } + public ReadOnlyCollection Parameters { get; private set; } + + public CallExpression(ScriptToken end, Expression left, List parameters) + : base(left.Start, end) + { + Left = left; + Parameters = parameters.AsReadOnly(); + } + + public override TExpr Accept(IAstVisitor visitor) + { + return visitor.Visit(this); + } + + public override void SetParent(Expression parent) + { + Parent = parent; + + Left.SetParent(this); + + foreach (var expression in Parameters) + { + expression.SetParent(this); + } + } + } +} diff --git a/ScriptConverter/Ast/Expressions/CastExpression.cs b/ScriptConverter/Ast/Expressions/CastExpression.cs new file mode 100644 index 0000000..8cc86fb --- /dev/null +++ b/ScriptConverter/Ast/Expressions/CastExpression.cs @@ -0,0 +1,29 @@ +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Expressions +{ + class CastExpression : Expression + { + public ScriptType Type { get; private set; } + public Expression Value { get; private set; } + + public CastExpression(ScriptToken start, ScriptType type, Expression value) + : base(start, value.End) + { + Type = type; + Value = value; + } + + public override TExpr Accept(IAstVisitor visitor) + { + return visitor.Visit(this); + } + + public override void SetParent(Expression parent) + { + Parent = parent; + + Value.SetParent(this); + } + } +} diff --git a/ScriptConverter/Ast/Expressions/Expression.cs b/ScriptConverter/Ast/Expressions/Expression.cs new file mode 100644 index 0000000..7e3a97e --- /dev/null +++ b/ScriptConverter/Ast/Expressions/Expression.cs @@ -0,0 +1,25 @@ +using System; +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Expressions +{ + abstract class Expression + { + public readonly ScriptToken Start; + public readonly ScriptToken End; + public Expression Parent { get; protected set; } + + protected Expression(ScriptToken start, ScriptToken end = null) + { + if (start == null) + throw new ArgumentNullException("start"); + + Start = start; + End = end ?? start; + } + + public abstract TExpr Accept(IAstVisitor visitor); + + public abstract void SetParent(Expression parent); + } +} diff --git a/ScriptConverter/Ast/Expressions/FieldExpression.cs b/ScriptConverter/Ast/Expressions/FieldExpression.cs new file mode 100644 index 0000000..46b7087 --- /dev/null +++ b/ScriptConverter/Ast/Expressions/FieldExpression.cs @@ -0,0 +1,29 @@ +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Expressions +{ + class FieldExpression : Expression + { + public Expression Left { get; private set; } + public string Name { get; private set; } + + public FieldExpression(Expression left, ScriptToken name) + : base(left.Start, name) + { + Left = left; + Name = name.Contents; + } + + public override TExpr Accept(IAstVisitor visitor) + { + return visitor.Visit(this); + } + + public override void SetParent(Expression parent) + { + Parent = parent; + + Left.SetParent(this); + } + } +} diff --git a/ScriptConverter/Ast/Expressions/IdentifierExpression.cs b/ScriptConverter/Ast/Expressions/IdentifierExpression.cs new file mode 100644 index 0000000..71c1196 --- /dev/null +++ b/ScriptConverter/Ast/Expressions/IdentifierExpression.cs @@ -0,0 +1,25 @@ +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Expressions +{ + class IdentifierExpression : Expression + { + public string Name { get; private set; } + + public IdentifierExpression(ScriptToken token) + : base(token) + { + Name = token.Contents; + } + + public override TExpr Accept(IAstVisitor visitor) + { + return visitor.Visit(this); + } + + public override void SetParent(Expression parent) + { + Parent = parent; + } + } +} diff --git a/ScriptConverter/Ast/Expressions/IndexerExpression.cs b/ScriptConverter/Ast/Expressions/IndexerExpression.cs new file mode 100644 index 0000000..afb1865 --- /dev/null +++ b/ScriptConverter/Ast/Expressions/IndexerExpression.cs @@ -0,0 +1,30 @@ +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Expressions +{ + class IndexerExpression : Expression + { + public Expression Left { get; private set; } + public Expression Index { get; private set; } + + public IndexerExpression(ScriptToken end, Expression left, Expression index) + : base(left.Start, end) + { + Left = left; + Index = index; + } + + public override TExpr Accept(IAstVisitor visitor) + { + return visitor.Visit(this); + } + + public override void SetParent(Expression parent) + { + Parent = parent; + + Left.SetParent(this); + Index.SetParent(this); + } + } +} diff --git a/ScriptConverter/Ast/Expressions/InstanceOfExpression.cs b/ScriptConverter/Ast/Expressions/InstanceOfExpression.cs new file mode 100644 index 0000000..499ae0b --- /dev/null +++ b/ScriptConverter/Ast/Expressions/InstanceOfExpression.cs @@ -0,0 +1,29 @@ +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Expressions +{ + class InstanceOfExpression : Expression + { + public Expression Left { get; private set; } + public ScriptType Type { get; private set; } + + public InstanceOfExpression(ScriptToken end, Expression left, ScriptType type) + : base(left.Start, end) + { + Left = left; + Type = type; + } + + public override TExpr Accept(IAstVisitor visitor) + { + return visitor.Visit(this); + } + + public override void SetParent(Expression parent) + { + Parent = parent; + + Left.SetParent(this); + } + } +} diff --git a/ScriptConverter/Ast/Expressions/NewExpression.cs b/ScriptConverter/Ast/Expressions/NewExpression.cs new file mode 100644 index 0000000..8ed62cd --- /dev/null +++ b/ScriptConverter/Ast/Expressions/NewExpression.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Expressions +{ + class NewExpression : Expression + { + public string Type { get; private set; } + public bool IsArray { get { return ArrayDimensions > 0; } } + public int ArrayDimensions { get; private set; } + public ReadOnlyCollection Parameters { get; private set; } + public ReadOnlyCollection Initializer { get; private set; } + + public NewExpression(ScriptToken start, ScriptToken end, string type, int arrayDimensions, List parameters, List initializer = null) + : base(start, end) + { + Type = type; + ArrayDimensions = arrayDimensions; + Parameters = parameters.AsReadOnly(); + Initializer = initializer != null ? initializer.AsReadOnly() : null; + } + + public override TExpr Accept(IAstVisitor visitor) + { + return visitor.Visit(this); + } + + public override void SetParent(Expression parent) + { + Parent = parent; + + foreach (var expression in Parameters) + { + expression.SetParent(this); + } + + if (Initializer != null) + { + foreach (var expression in Initializer) + { + expression.SetParent(this); + } + } + } + } +} diff --git a/ScriptConverter/Ast/Expressions/NullExpression.cs b/ScriptConverter/Ast/Expressions/NullExpression.cs new file mode 100644 index 0000000..984ef0b --- /dev/null +++ b/ScriptConverter/Ast/Expressions/NullExpression.cs @@ -0,0 +1,23 @@ +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Expressions +{ + class NullExpression : Expression + { + public NullExpression(ScriptToken token) + : base(token) + { + + } + + public override TExpr Accept(IAstVisitor visitor) + { + return visitor.Visit(this); + } + + public override void SetParent(Expression parent) + { + Parent = parent; + } + } +} diff --git a/ScriptConverter/Ast/Expressions/NumberExpression.cs b/ScriptConverter/Ast/Expressions/NumberExpression.cs new file mode 100644 index 0000000..3a4ae22 --- /dev/null +++ b/ScriptConverter/Ast/Expressions/NumberExpression.cs @@ -0,0 +1,25 @@ +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Expressions +{ + class NumberExpression : Expression + { + public string Value { get; private set; } + + public NumberExpression(ScriptToken token) + : base(token) + { + Value = token.Contents; + } + + public override TExpr Accept(IAstVisitor visitor) + { + return visitor.Visit(this); + } + + public override void SetParent(Expression parent) + { + Parent = parent; + } + } +} diff --git a/ScriptConverter/Ast/Expressions/PostfixOperatorExpression.cs b/ScriptConverter/Ast/Expressions/PostfixOperatorExpression.cs new file mode 100644 index 0000000..ea3d9c1 --- /dev/null +++ b/ScriptConverter/Ast/Expressions/PostfixOperatorExpression.cs @@ -0,0 +1,29 @@ +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Expressions +{ + class PostfixOperatorExpression : Expression + { + public ScriptTokenType Operation { get; private set; } + public Expression Left { get; private set; } + + public PostfixOperatorExpression(ScriptToken end, Expression left) + : base(left.Start, end) + { + Operation = end.Type; + Left = left; + } + + public override TExpr Accept(IAstVisitor visitor) + { + return visitor.Visit(this); + } + + public override void SetParent(Expression parent) + { + Parent = parent; + + Left.SetParent(this); + } + } +} diff --git a/ScriptConverter/Ast/Expressions/PrefixOperatorExpression.cs b/ScriptConverter/Ast/Expressions/PrefixOperatorExpression.cs new file mode 100644 index 0000000..cb25108 --- /dev/null +++ b/ScriptConverter/Ast/Expressions/PrefixOperatorExpression.cs @@ -0,0 +1,29 @@ +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Expressions +{ + class PrefixOperatorExpression : Expression + { + public ScriptTokenType Operation { get; private set; } + public Expression Right { get; private set; } + + public PrefixOperatorExpression(ScriptToken start, Expression right) + : base(start, right.End) + { + Operation = start.Type; + Right = right; + } + + public override TExpr Accept(IAstVisitor visitor) + { + return visitor.Visit(this); + } + + public override void SetParent(Expression parent) + { + Parent = parent; + + Right.SetParent(this); + } + } +} diff --git a/ScriptConverter/Ast/Expressions/StringExpression.cs b/ScriptConverter/Ast/Expressions/StringExpression.cs new file mode 100644 index 0000000..346fc5e --- /dev/null +++ b/ScriptConverter/Ast/Expressions/StringExpression.cs @@ -0,0 +1,27 @@ +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Expressions +{ + class StringExpression : Expression + { + public string Value { get; private set; } + public bool IsSingleQuote { get; private set; } + + public StringExpression(ScriptToken token, bool singleQuote) + : base(token) + { + Value = token.Contents; + IsSingleQuote = singleQuote; + } + + public override TExpr Accept(IAstVisitor visitor) + { + return visitor.Visit(this); + } + + public override void SetParent(Expression parent) + { + Parent = parent; + } + } +} diff --git a/ScriptConverter/Ast/Expressions/TernaryExpression.cs b/ScriptConverter/Ast/Expressions/TernaryExpression.cs new file mode 100644 index 0000000..2c83f96 --- /dev/null +++ b/ScriptConverter/Ast/Expressions/TernaryExpression.cs @@ -0,0 +1,31 @@ +namespace ScriptConverter.Ast.Expressions +{ + class TernaryExpression : Expression + { + public Expression Condition { get; private set; } + public Expression TrueExpression { get; private set; } + public Expression FalseExpression { get; private set; } + + public TernaryExpression(Expression condition, Expression trueExpression, Expression falseExpression) + : base(condition.Start, falseExpression.End) + { + Condition = condition; + TrueExpression = trueExpression; + FalseExpression = falseExpression; + } + + public override TExpr Accept(IAstVisitor visitor) + { + return visitor.Visit(this); + } + + public override void SetParent(Expression parent) + { + Parent = parent; + + Condition.SetParent(this); + TrueExpression.SetParent(this); + FalseExpression.SetParent(this); + } + } +} diff --git a/ScriptConverter/Ast/IAstVisitor.cs b/ScriptConverter/Ast/IAstVisitor.cs new file mode 100644 index 0000000..68c2666 --- /dev/null +++ b/ScriptConverter/Ast/IAstVisitor.cs @@ -0,0 +1,48 @@ +using ScriptConverter.Ast.Declarations; +using ScriptConverter.Ast.Expressions; +using ScriptConverter.Ast.Statements; + +namespace ScriptConverter.Ast +{ + interface IAstVisitor + { + TDoc Visit(Document document); + + TDecl Visit(FieldDeclaration declaration); + TDecl Visit(IncludeDeclaration declaration); + TDecl Visit(InheritsDeclaration declaration); + TDecl Visit(MethodDeclaration declaration); + + TStmt Visit(BlockStatement statement); + TStmt Visit(BreakStatement statement); + TStmt Visit(ContinueStatement statement); + TStmt Visit(DoStatement statement); + TStmt Visit(EmptyStatement statement); + TStmt Visit(ForStatement statement); + TStmt Visit(IfStatement statement); + TStmt Visit(NakedStatement statement); + TStmt Visit(ReturnStatement statement); + TStmt Visit(SwitchStatement statement); + TStmt Visit(ThrowStatement statement); + TStmt Visit(TryStatement statement); + TStmt Visit(VariableStatement statement); + TStmt Visit(WhileStatement statement); + + TExpr Visit(ArrayInitializerExpression expression); + TExpr Visit(BinaryOperatorExpression expression); + TExpr Visit(BoolExpression expression); + TExpr Visit(CallExpression expression); + TExpr Visit(CastExpression expression); + TExpr Visit(FieldExpression expression); + TExpr Visit(IdentifierExpression expression); + TExpr Visit(IndexerExpression expression); + TExpr Visit(InstanceOfExpression expression); + TExpr Visit(NewExpression expression); + TExpr Visit(NullExpression expression); + TExpr Visit(NumberExpression expression); + TExpr Visit(PostfixOperatorExpression expression); + TExpr Visit(PrefixOperatorExpression expression); + TExpr Visit(StringExpression expression); + TExpr Visit(TernaryExpression expression); + } +} diff --git a/ScriptConverter/Ast/ScriptType.cs b/ScriptConverter/Ast/ScriptType.cs new file mode 100644 index 0000000..11c7231 --- /dev/null +++ b/ScriptConverter/Ast/ScriptType.cs @@ -0,0 +1,48 @@ +using System.Linq; + +namespace ScriptConverter.Ast +{ + class ScriptType + { + public string Name { get; private set; } + public bool IsArray { get { return ArrayDimensions > 0; } } + public bool IsResizable { get; private set; } + public int ArrayDimensions { get; private set; } + + public ScriptType(string name, int arrayDimensions = 0, bool isResizable = false) + { + if (name == "unknown") + name = "Object"; + + if (name == "string") + name = "String"; + + if (name == "modifyable_int") + name = "modifiable_int"; + + if (name == "modifyable _float") + name = "modifiable_float"; + + if (name == "modifyable_string_id") + name = "modifiable_string_id"; + + if (isResizable) + arrayDimensions = 1; + + Name = name; + IsResizable = isResizable; + ArrayDimensions = arrayDimensions; + } + + public override string ToString() + { + if (IsResizable) + return "Vector"; + + if (!IsArray) + return Name; + + return Name + string.Join("", Enumerable.Repeat("[]", ArrayDimensions)); + } + } +} diff --git a/ScriptConverter/Ast/Statements/BlockStatement.cs b/ScriptConverter/Ast/Statements/BlockStatement.cs new file mode 100644 index 0000000..80d89f2 --- /dev/null +++ b/ScriptConverter/Ast/Statements/BlockStatement.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Statements +{ + class BlockStatement : Statement + { + public ReadOnlyCollection Statements { get; private set; } + public bool IsSwitch { get; private set; } + + public BlockStatement(ScriptToken start, ScriptToken end, List statements, bool isSwitch = false) + : base(start, end) + { + if (statements == null) + throw new ArgumentNullException("statements"); + + Statements = statements.AsReadOnly(); + IsSwitch = isSwitch; + } + + public override TStmt Accept(IAstVisitor visitor) + { + return visitor.Visit(this); + } + + public override void SetParent(Statement parent) + { + Parent = parent; + + foreach (var statement in Statements) + { + statement.SetParent(this); + } + } + } +} diff --git a/ScriptConverter/Ast/Statements/BreakStatement.cs b/ScriptConverter/Ast/Statements/BreakStatement.cs new file mode 100644 index 0000000..20ff15b --- /dev/null +++ b/ScriptConverter/Ast/Statements/BreakStatement.cs @@ -0,0 +1,23 @@ +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Statements +{ + class BreakStatement : Statement + { + public BreakStatement(ScriptToken token) + : base(token) + { + + } + + public override TStmt Accept(IAstVisitor visitor) + { + return visitor.Visit(this); + } + + public override void SetParent(Statement parent) + { + Parent = parent; + } + } +} diff --git a/ScriptConverter/Ast/Statements/ContinueStatement.cs b/ScriptConverter/Ast/Statements/ContinueStatement.cs new file mode 100644 index 0000000..ceb0106 --- /dev/null +++ b/ScriptConverter/Ast/Statements/ContinueStatement.cs @@ -0,0 +1,23 @@ +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Statements +{ + class ContinueStatement : Statement + { + public ContinueStatement(ScriptToken token) + : base(token) + { + + } + + public override TStmt Accept(IAstVisitor visitor) + { + return visitor.Visit(this); + } + + public override void SetParent(Statement parent) + { + Parent = parent; + } + } +} diff --git a/ScriptConverter/Ast/Statements/DoStatement.cs b/ScriptConverter/Ast/Statements/DoStatement.cs new file mode 100644 index 0000000..a93c61f --- /dev/null +++ b/ScriptConverter/Ast/Statements/DoStatement.cs @@ -0,0 +1,31 @@ +using ScriptConverter.Ast.Expressions; +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Statements +{ + class DoStatement : Statement + { + public Expression Condition { get; private set; } + public BlockStatement Block { get; private set; } + + public DoStatement(ScriptToken start, ScriptToken end, Expression condition, BlockStatement block) + : base(start, end) + { + Condition = condition; + Block = block; + } + + public override TStmt Accept(IAstVisitor visitor) + { + return visitor.Visit(this); + } + + public override void SetParent(Statement parent) + { + Parent = parent; + + Condition.SetParent(null); + Block.SetParent(this); + } + } +} diff --git a/ScriptConverter/Ast/Statements/EmptyStatement.cs b/ScriptConverter/Ast/Statements/EmptyStatement.cs new file mode 100644 index 0000000..6022e6c --- /dev/null +++ b/ScriptConverter/Ast/Statements/EmptyStatement.cs @@ -0,0 +1,23 @@ +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Statements +{ + class EmptyStatement : Statement + { + public EmptyStatement(ScriptToken token) + : base(token) + { + + } + + public override TStmt Accept(IAstVisitor visitor) + { + return visitor.Visit(this); + } + + public override void SetParent(Statement parent) + { + Parent = parent; + } + } +} diff --git a/ScriptConverter/Ast/Statements/ForStatement.cs b/ScriptConverter/Ast/Statements/ForStatement.cs new file mode 100644 index 0000000..ea3b1d0 --- /dev/null +++ b/ScriptConverter/Ast/Statements/ForStatement.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using ScriptConverter.Ast.Expressions; +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Statements +{ + class ForStatement : Statement + { + public ReadOnlyCollection Initializers { get; private set; } + public Expression Condition { get; private set; } + public ReadOnlyCollection Increment { get; private set; } + public BlockStatement Block { get; private set; } + + public ForStatement(ScriptToken start, ScriptToken end, List initializers, Expression condition, List increment, BlockStatement block) + : base(start, end) + { + Initializers = initializers != null ? initializers.AsReadOnly() : null; + Condition = condition; + Increment = increment != null ? increment.AsReadOnly() : null; + Block = block; + } + + public override TStmt Accept(IAstVisitor visitor) + { + return visitor.Visit(this); + } + + public override void SetParent(Statement parent) + { + Parent = parent; + + if (Initializers != null) + { + foreach (var statement in Initializers) + { + statement.SetParent(this); + } + } + + if (Condition != null) + Condition.SetParent(null); + + if (Increment != null) + { + foreach (var expression in Increment) + { + expression.SetParent(null); + } + } + + Block.SetParent(this); + } + } +} diff --git a/ScriptConverter/Ast/Statements/IfStatement.cs b/ScriptConverter/Ast/Statements/IfStatement.cs new file mode 100644 index 0000000..572c646 --- /dev/null +++ b/ScriptConverter/Ast/Statements/IfStatement.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using ScriptConverter.Ast.Expressions; +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Statements +{ + class IfStatement : Statement + { + public class Branch + { + public Expression Condition { get; private set; } + public BlockStatement Block { get; private set; } + + public Branch(Expression condition, BlockStatement block) + { + Condition = condition; + Block = block; + } + } + + public ReadOnlyCollection Branches { get; private set; } + public Branch DefaultBranch { get; private set; } + + public IfStatement(ScriptToken start, ScriptToken end, List branches, Branch defaultBranch) + : base(start, end) + { + Branches = branches.AsReadOnly(); + DefaultBranch = defaultBranch; + } + + public override TStmt Accept(IAstVisitor visitor) + { + return visitor.Visit(this); + } + + public override void SetParent(Statement parent) + { + Parent = parent; + + foreach (var branch in Branches) + { + branch.Condition.SetParent(null); + branch.Block.SetParent(this); + } + + if (DefaultBranch != null) + { + DefaultBranch.Block.SetParent(this); + } + } + } +} diff --git a/ScriptConverter/Ast/Statements/NakedStatement.cs b/ScriptConverter/Ast/Statements/NakedStatement.cs new file mode 100644 index 0000000..c451288 --- /dev/null +++ b/ScriptConverter/Ast/Statements/NakedStatement.cs @@ -0,0 +1,27 @@ +using ScriptConverter.Ast.Expressions; + +namespace ScriptConverter.Ast.Statements +{ + class NakedStatement : Statement + { + public Expression Expression { get; private set; } + + public NakedStatement(Expression expression) + : base(expression.Start, expression.End) + { + Expression = expression; + } + + public override TStmt Accept(IAstVisitor visitor) + { + return visitor.Visit(this); + } + + public override void SetParent(Statement parent) + { + Parent = parent; + + Expression.SetParent(null); + } + } +} diff --git a/ScriptConverter/Ast/Statements/ReturnStatement.cs b/ScriptConverter/Ast/Statements/ReturnStatement.cs new file mode 100644 index 0000000..6c80980 --- /dev/null +++ b/ScriptConverter/Ast/Statements/ReturnStatement.cs @@ -0,0 +1,29 @@ +using ScriptConverter.Ast.Expressions; +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Statements +{ + class ReturnStatement : Statement + { + public Expression Value { get; private set; } + + public ReturnStatement(ScriptToken start, ScriptToken end, Expression value) + : base(start, end) + { + Value = value; + } + + public override TStmt Accept(IAstVisitor visitor) + { + return visitor.Visit(this); + } + + public override void SetParent(Statement parent) + { + Parent = parent; + + if (Value != null) + Value.SetParent(null); + } + } +} diff --git a/ScriptConverter/Ast/Statements/Statement.cs b/ScriptConverter/Ast/Statements/Statement.cs new file mode 100644 index 0000000..036af75 --- /dev/null +++ b/ScriptConverter/Ast/Statements/Statement.cs @@ -0,0 +1,25 @@ +using System; +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Statements +{ + abstract class Statement + { + public readonly ScriptToken Start; + public readonly ScriptToken End; + public Statement Parent { get; protected set; } + + protected Statement(ScriptToken start, ScriptToken end = null) + { + if (start == null) + throw new ArgumentNullException("start"); + + Start = start; + End = end ?? start; + } + + public abstract TStmt Accept(IAstVisitor visitor); + + public abstract void SetParent(Statement parent); + } +} diff --git a/ScriptConverter/Ast/Statements/SwitchStatement.cs b/ScriptConverter/Ast/Statements/SwitchStatement.cs new file mode 100644 index 0000000..f14316f --- /dev/null +++ b/ScriptConverter/Ast/Statements/SwitchStatement.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using ScriptConverter.Ast.Expressions; +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Statements +{ + class SwitchStatement : Statement + { + public class Branch + { + public ReadOnlyCollection Conditions { get; private set; } + public BlockStatement Block { get; private set; } + + public Branch(List conditions, BlockStatement block) + { + Conditions = conditions.AsReadOnly(); + Block = block; + } + } + + public Expression Expression { get; private set; } + public ReadOnlyCollection Branches { get; private set; } + + public SwitchStatement(ScriptToken start, ScriptToken end, Expression expression, List branches) + : base(start, end) + { + Expression = expression; + Branches = branches.AsReadOnly(); + } + + public override TStmt Accept(IAstVisitor visitor) + { + return visitor.Visit(this); + } + + public override void SetParent(Statement parent) + { + Parent = parent; + + Expression.SetParent(null); + + foreach (var branch in Branches) + { + foreach (var expression in branch.Conditions) + { + if (expression != null) + expression.SetParent(null); + } + + branch.Block.SetParent(this); + } + } + } +} diff --git a/ScriptConverter/Ast/Statements/ThrowStatement.cs b/ScriptConverter/Ast/Statements/ThrowStatement.cs new file mode 100644 index 0000000..d97149a --- /dev/null +++ b/ScriptConverter/Ast/Statements/ThrowStatement.cs @@ -0,0 +1,28 @@ +using ScriptConverter.Ast.Expressions; +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Statements +{ + class ThrowStatement : Statement + { + public Expression Value { get; private set; } + + public ThrowStatement(ScriptToken start, Expression value) + : base(start, value.End) + { + Value = value; + } + + public override TStmt Accept(IAstVisitor visitor) + { + return visitor.Visit(this); + } + + public override void SetParent(Statement parent) + { + Parent = parent; + + Value.SetParent(null); + } + } +} diff --git a/ScriptConverter/Ast/Statements/TryStatement.cs b/ScriptConverter/Ast/Statements/TryStatement.cs new file mode 100644 index 0000000..e90b1b9 --- /dev/null +++ b/ScriptConverter/Ast/Statements/TryStatement.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Statements +{ + class TryStatement : Statement + { + public class Branch + { + public ScriptType Type { get; private set; } + public string Name { get; private set; } + public BlockStatement Block { get; private set; } + + public Branch(ScriptType type, string name, BlockStatement block) + { + Type = type; + Name = name; + Block = block; + } + } + + public BlockStatement Block { get; private set; } + public ReadOnlyCollection Branches { get; private set; } + + public TryStatement(ScriptToken start, ScriptToken end, BlockStatement block, List branches) : base(start, end) + { + if (branches.Count < 1) + throw new ArgumentException("need at least 1 branch", "branches"); + + Block = block; + Branches = branches.AsReadOnly(); + } + + public override TStmt Accept(IAstVisitor visitor) + { + return visitor.Visit(this); + } + + public override void SetParent(Statement parent) + { + Parent = parent; + + Block.SetParent(this); + + foreach (var branch in Branches) + { + branch.Block.SetParent(this); + } + } + } +} diff --git a/ScriptConverter/Ast/Statements/VariableStatement.cs b/ScriptConverter/Ast/Statements/VariableStatement.cs new file mode 100644 index 0000000..874eaad --- /dev/null +++ b/ScriptConverter/Ast/Statements/VariableStatement.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using ScriptConverter.Ast.Expressions; +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Statements +{ + class VariableStatement : Statement + { + public class Definition + { + public ScriptType Type { get; private set; } + public string Name { get; private set; } + public Expression Value { get; private set; } + + public Definition(ScriptType type, string name, Expression value) + { + Type = type; + Name = name; + Value = value; + } + } + + public ScriptType BaseType { get; private set; } + public bool Final { get; private set; } + public ReadOnlyCollection Definitions { get; private set; } + + public VariableStatement(ScriptToken start, ScriptToken end, ScriptType baseType, bool final, List definitions) + : base(start, end) + { + if (definitions.Count < 1) + throw new ArgumentException("need at least 1 definition", "definitions"); + + BaseType = baseType; + Final = final; + Definitions = definitions.AsReadOnly(); + } + + public override TStmt Accept(IAstVisitor visitor) + { + return visitor.Visit(this); + } + + public override void SetParent(Statement parent) + { + Parent = parent; + + foreach (var definition in Definitions) + { + if (definition.Value != null) + definition.Value.SetParent(null); + } + } + } +} diff --git a/ScriptConverter/Ast/Statements/WhileStatement.cs b/ScriptConverter/Ast/Statements/WhileStatement.cs new file mode 100644 index 0000000..59f8bf4 --- /dev/null +++ b/ScriptConverter/Ast/Statements/WhileStatement.cs @@ -0,0 +1,31 @@ +using ScriptConverter.Ast.Expressions; +using ScriptConverter.Parser; + +namespace ScriptConverter.Ast.Statements +{ + class WhileStatement : Statement + { + public Expression Condition { get; private set; } + public BlockStatement Block { get; private set; } + + public WhileStatement(ScriptToken start, ScriptToken end, Expression condition, BlockStatement block) + : base(start, end) + { + Condition = condition; + Block = block; + } + + public override TStmt Accept(IAstVisitor visitor) + { + return visitor.Visit(this); + } + + public override void SetParent(Statement parent) + { + Parent = parent; + + Condition.SetParent(null); + Block.SetParent(this); + } + } +} diff --git a/ScriptConverter/AstPrinter.cs b/ScriptConverter/AstPrinter.cs new file mode 100644 index 0000000..609874c --- /dev/null +++ b/ScriptConverter/AstPrinter.cs @@ -0,0 +1,1136 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using ScriptConverter.Ast; +using ScriptConverter.Ast.Declarations; +using ScriptConverter.Ast.Expressions; +using ScriptConverter.Ast.Statements; +using ScriptConverter.Parser; + +namespace ScriptConverter +{ + // TODO: make == to .equals() conversion use type info + + class AstPrinter : IAstVisitor + { + private IndentTextWriter _writer; + private readonly string _package; + private readonly string _name; + private readonly bool _isLib; + + public AstPrinter(string package, string name, bool isLib) + : this(Console.Out, package, name, isLib) + { + + } + + public AstPrinter(TextWriter writer, string package, string name, bool isLib) + { + _writer = new IndentTextWriter(writer, " "); + _package = package; + _name = name; + _isLib = isLib; + + _scopeStack = new Stack>(); + } + + public int Visit(Document document) + { + _writer.Write("package "); + _writer.Write(_package); + _writer.WriteLine(";"); + _writer.WriteLine(); + + _writer.WriteLine("import script.*;"); + _writer.WriteLine("import script.base_class.*;"); + _writer.WriteLine("import script.combat_engine.*;"); + _writer.WriteLine("import java.util.Arrays;"); + _writer.WriteLine("import java.util.Hashtable;"); + _writer.WriteLine("import java.util.Vector;"); + _writer.WriteLine("import script.base_script;"); + _writer.WriteLine(); + + foreach (var include in document.Declarations.OfType()) + { + include.Accept(this); + } + + _writer.WriteLine(); + + var inherits = document.Declarations.OfType().SingleOrDefault(); + + _writer.Write("public class "); + _writer.Write(_name); + + if (_name == "base_script") + { + _writer.Write(" extends script.base_class"); + } + else + { + _writer.Write(" extends script."); + _writer.Write(inherits != null ? inherits.Name : "base_script"); + } + + _writer.WriteLine(); + _writer.WriteLine("{"); + _writer.Indent++; + + _writer.Write("public "); + _writer.Write(_name); + _writer.WriteLine("() { }"); + _writer.WriteLine(); + + PushScope(); + + foreach (var constant in document.Declarations.OfType()) + { + constant.SetParent(); + constant.Accept(this); + } + + _writer.WriteLine(); + + foreach (var method in document.Declarations.OfType()) + { + method.SetParent(); + method.Accept(this); + _writer.WriteLine(); + } + + PopScope(); + + _writer.Indent--; + _writer.WriteLine("}"); + + _writer.WriteLine(); + return 0; + } + + #region Declarations + + public int Visit(FieldDeclaration declaration) + { + Declare(declaration.Name, declaration.Type); + + if (declaration.IsPublic) + _writer.Write("public "); + + if (declaration.IsConstant) + _writer.Write("static final "); + + _writer.Write(declaration.Type); + _writer.Write(" "); + _writer.Write(declaration.Name); + _writer.Write(" = "); + declaration.Value.Accept(this); + _writer.WriteLine(";"); + return 0; + } + + public int Visit(IncludeDeclaration declaration) + { + _writer.Write("import "); + + if (!declaration.Name.StartsWith("java.")) + _writer.Write("script."); + + _writer.Write(declaration.Name); + _writer.WriteLine(";"); + return 0; + } + + public int Visit(InheritsDeclaration declaration) + { + throw new NotSupportedException(); + } + + public int Visit(MethodDeclaration declaration) + { + List extraParameters; + var isEventHandler = EventHandlers.TryGetValue(declaration.ReturnType.ToString(), out extraParameters); + + var extras = isEventHandler ? extraParameters : Enumerable.Empty(); + var parameters = extras.Concat(declaration.Parameters).ToList(); + + _methodReturnType = declaration.ReturnType; + _methodParameters = parameters.ToDictionary(p => p.Name, p => p.Type); + + _writer.Write("public "); + + if (_isLib) + _writer.Write("static "); + + _writer.Write(isEventHandler ? "int" : declaration.ReturnType.ToString()); + _writer.Write(" "); + _writer.Write(declaration.Name); + _writer.Write("("); + + var first = true; + foreach (var parameter in parameters) + { + if (!first) + _writer.Write(", "); + + first = false; + + _writer.Write(parameter.Type); + _writer.Write(" "); + _writer.Write(parameter.Name); + } + + _writer.WriteLine(") throws InterruptedException"); + + declaration.Block.Accept(this); + + return 0; + } + + #endregion + + #region Statements + + public int Visit(BlockStatement statement) + { + if (!statement.IsSwitch) + { + _writer.WriteLine("{"); + PushScope(); + } + + _writer.Indent++; + + foreach (var subStatement in statement.Statements) + { + var nakedStmt = subStatement as NakedStatement; + if (nakedStmt != null) + nakedStmt.Expression.SetParent(null); + + subStatement.Accept(this); + } + + _writer.Indent--; + + if (!statement.IsSwitch) + { + PopScope(); + _writer.WriteLine("}"); + } + + return 0; + } + + public int Visit(BreakStatement statement) + { + _writer.WriteLine("break;"); + return 0; + } + + public int Visit(ContinueStatement statement) + { + _writer.WriteLine("continue;"); + return 0; + } + + public int Visit(DoStatement statement) + { + _writer.WriteLine("do"); + statement.Block.Accept(this); + _writer.Write("while ("); + statement.Condition.Accept(this); + _writer.WriteLine(");"); + return 0; + } + + public int Visit(EmptyStatement statement) + { + _writer.WriteLine(";"); + return 0; + } + + public int Visit(ForStatement statement) + { + _writer.Write("for ("); + + if (statement.Initializers != null) + { + var first = true; + foreach (var initializer in statement.Initializers) + { + if (!first) + _writer.Write(", "); + + first = false; + + var initVariable = initializer as VariableStatement; + if (initVariable != null) + { + if (initVariable.Final) + throw new Exception("why"); + + _writer.Write(initVariable.BaseType); + _writer.Write(" "); + + var firstVar = true; + foreach (var definition in initVariable.Definitions) + { + if (!firstVar) + _writer.Write(", "); + + firstVar = false; + + _writer.Write(definition.Name); + + var i = initVariable.BaseType.ArrayDimensions; + while (definition.Type.ArrayDimensions > i++) + { + _writer.Write("[]"); + } + + if (definition.Value != null) + { + _writer.Write(" = "); + definition.Value.Accept(this); + } + } + } + + var initExpr = initializer as NakedStatement; + if (initExpr != null) + { + initExpr.Expression.Accept(this); + } + } + } + + _writer.Write("; "); + + if (statement.Condition != null) + statement.Condition.Accept(this); + + _writer.Write("; "); + + if (statement.Increment != null) + { + var first = true; + foreach (var increment in statement.Increment) + { + if (!first) + _writer.Write(", "); + + first = false; + increment.Accept(this); + } + } + + _writer.WriteLine(")"); + statement.Block.Accept(this); + return 0; + } + + public int Visit(IfStatement statement) + { + for (var i = 0; i < statement.Branches.Count; i++) + { + var branch = statement.Branches[i]; + + _writer.Write(i == 0 ? "if" : "else if"); + + _writer.Write(" ("); + branch.Condition.Accept(this); + _writer.WriteLine(")"); + + branch.Block.Accept(this); + } + + if (statement.DefaultBranch != null) + { + _writer.WriteLine("else "); + statement.DefaultBranch.Block.Accept(this); + } + + return 0; + } + + public int Visit(NakedStatement statement) + { + statement.Expression.Accept(this); + _writer.WriteLine(";"); + return 0; + } + + public int Visit(ReturnStatement statement) + { + if (statement.Value != null) + { + var valueIdentifier = statement.Value as IdentifierExpression; + if (valueIdentifier != null) + { + var type = GetType(valueIdentifier.Name); + if (type != null && type.IsResizable) + { + if (_methodReturnType != null && _methodReturnType.IsArray && !_methodReturnType.IsResizable) + { + var result = ResizableToArray(valueIdentifier.Name, _methodReturnType); + _writer.WriteLine("return {0};", result); + return 0; + } + } + } + + _writer.Write("return "); + statement.Value.Accept(this); + _writer.WriteLine(";"); + return 0; + } + + _writer.WriteLine("return;"); + return 0; + } + + public int Visit(SwitchStatement statement) + { + _writer.Write("switch ("); + statement.Expression.Accept(this); + _writer.WriteLine(")"); + + _writer.WriteLine("{"); + _writer.Indent++; + + for (var i = 0; i < statement.Branches.Count; i++) + { + var branch = statement.Branches[i]; + + foreach (var condition in branch.Conditions) + { + if (condition == null) + { + _writer.WriteLine("default:"); + continue; + } + + _writer.Write("case "); + condition.Accept(this); + _writer.WriteLine(":"); + } + + branch.Block.Accept(this); + + if (i < statement.Branches.Count - 1) + _writer.WriteLine(); + } + + _writer.WriteLine(); + + _writer.Indent--; + _writer.WriteLine("}"); + return 0; + } + + public int Visit(ThrowStatement statement) + { + _writer.Write("throw "); + statement.Value.Accept(this); + _writer.WriteLine(";"); + return 0; + } + + public int Visit(TryStatement statement) + { + _writer.WriteLine("try"); + statement.Block.Accept(this); + + foreach (var branch in statement.Branches) + { + _writer.Write("catch ("); + _writer.Write(branch.Type); + _writer.Write(" "); + _writer.Write(branch.Name); + _writer.WriteLine(")"); + branch.Block.Accept(this); + } + + return 0; + } + + public int Visit(VariableStatement statement) + { + if (statement.Definitions.Count == 1) + { + var def = statement.Definitions[0]; + + var targetType = def.Type; + + var valueIdent = def.Value as IdentifierExpression; + if (valueIdent != null) + { + var valueType = GetType(valueIdent.Name); + if (valueType != null) + { + if (targetType.IsResizable && (valueType.IsArray && !valueType.IsResizable)) + { + Declare(def.Name, def.Type); + // Vector def = arrayIdent; + + var result = ArrayToResizable(valueIdent.Name); + _writer.WriteLine("Vector {0} = {1};", def.Name, result); + + return 0; + } + + if ((targetType.IsArray && !targetType.IsResizable) && valueType.IsResizable) + { + Declare(def.Name, def.Type); + // arrayType[] def = vectorIdent; + + var result = ResizableToArray(valueIdent.Name, def.Type); + _writer.WriteLine("{0} {1} = {2};", targetType, def.Name, result); + + return 0; + } + } + } + + var valueCast = def.Value as CastExpression; + if (valueCast != null && targetType.IsResizable) + { + var valueType = valueCast.Type; + + if (valueType.IsResizable && targetType.Name == valueType.Name) + { + valueType = new ScriptType(valueType.Name, valueType.ArrayDimensions); + + Declare(def.Name, def.Type); + // resizable arrayType[] def = (resizable arrayType[])arrayExpr; + + var result = ArrayToResizable(valueType, valueCast.Value); + _writer.WriteLine("Vector {0} = {1};", def.Name, result); + + return 0; + } + } + } + + if (statement.Final) + _writer.Write("final "); + + if (statement.BaseType.IsResizable) + { + var def = statement.Definitions.Single(); + + Declare(def.Name, def.Type); + + _writer.Write("Vector "); + _writer.Write(def.Name); + + if (def.Value == null) + { + _writer.WriteLine(";"); + return 0; + } + + _writer.Write(" = "); + + if (def.Value is NewExpression) + { + _writer.WriteLine("new Vector();"); + } + else + { + def.Value.Accept(this); + _writer.WriteLine(";"); + } + + return 0; + } + + _writer.Write(statement.BaseType); + _writer.Write(" "); + + var first = true; + foreach (var definition in statement.Definitions) + { + Declare(definition.Name, definition.Type); + + if (!first) + _writer.Write(", "); + + first = false; + + _writer.Write(definition.Name); + + var i = statement.BaseType.ArrayDimensions; + while (definition.Type.ArrayDimensions > i++) + { + _writer.Write("[]"); + } + + if (definition.Value != null) + { + _writer.Write(" = "); + definition.Value.Accept(this); + } + } + + _writer.WriteLine(";"); + return 0; + } + + public int Visit(WhileStatement statement) + { + _writer.Write("while ("); + statement.Condition.Accept(this); + _writer.WriteLine(")"); + statement.Block.Accept(this); + return 0; + } + + #endregion + + #region Expressions + + public int Visit(ArrayInitializerExpression expression) + { + _writer.Write("{ "); + + var first = true; + foreach (var value in expression.Values) + { + if (!first) + _writer.Write(", "); + + first = false; + value.Accept(this); + } + + _writer.Write(" }"); + return 0; + } + + public int Visit(BinaryOperatorExpression expression) + { + if (expression.Operation == ScriptTokenType.EqualTo || + expression.Operation == ScriptTokenType.NotEqualTo) + { + var leftString = expression.Left as StringExpression; + var rightString = expression.Right as StringExpression; + + if ((leftString != null && !leftString.IsSingleQuote) || (rightString != null && !rightString.IsSingleQuote)) + { + if (expression.Operation == ScriptTokenType.NotEqualTo) + _writer.Write("!"); + + expression.Left.Accept(this); + _writer.Write(".equals("); + expression.Right.Accept(this); + _writer.Write(")"); + + return 0; + } + } + + if (expression.Operation == ScriptTokenType.Assign) + { + var leftIndexer = expression.Left as IndexerExpression; + if (leftIndexer != null) + { + var leftIdentifier = leftIndexer.Left as IdentifierExpression; + if (leftIdentifier != null) + { + var type = GetType(leftIdentifier.Name); + if (type != null && type.IsResizable) + { + _writer.Write(leftIdentifier.Name); + _writer.Write(".set("); + leftIndexer.Index.Accept(this); + _writer.Write(", "); + expression.Right.Accept(this); + _writer.Write(")"); + + return 0; + } + } + } + + var leftIdent = expression.Left as IdentifierExpression; + var rightIdent = expression.Right as IdentifierExpression; + if (expression.Parent == null && leftIdent != null && rightIdent != null) + { + var leftType = GetType(leftIdent.Name); + var rightType = GetType(rightIdent.Name); + if (leftType != null && rightType != null) + { + if (leftType.IsResizable && (rightType.IsArray && !rightType.IsResizable)) + { + // = + + var result = ArrayToResizable(rightIdent.Name); + _writer.Write("{0} = {1}", leftIdent.Name, result); + + return 0; + } + + if ((leftType.IsArray && !leftType.IsResizable) && rightType.IsResizable) + { + // = + + var result = ResizableToArray(rightIdent.Name, leftType); + _writer.Write("{0} = {1}", leftIdent.Name, result); + + return 0; + } + } + } + } + + var needParens = expression.Parent != null; + + if (needParens) + _writer.Write("("); + + expression.Left.Accept(this); + _writer.Write(" "); + _writer.Write(BinaryOperatorMap[expression.Operation]); + _writer.Write(" "); + expression.Right.Accept(this); + + if (needParens) + _writer.Write(")"); + + return 0; + } + + public int Visit(BoolExpression expression) + { + _writer.Write(expression.Value ? "true" : "false"); + return 0; + } + + public int Visit(CallExpression expression) + { + expression.Left.Accept(this); + _writer.Write("("); + + var first = true; + foreach (var parameter in expression.Parameters) + { + if (!first) + _writer.Write(", "); + + first = false; + parameter.Accept(this); + } + + _writer.Write(")"); + return 0; + } + + public int Visit(CastExpression expression) + { + _writer.Write("("); + _writer.Write("("); + _writer.Write(expression.Type); + _writer.Write(")"); + expression.Value.Accept(this); + _writer.Write(")"); + return 0; + } + + public int Visit(FieldExpression expression) + { + if (expression.Name == "length") + { + var leftIdentifier = expression.Left as IdentifierExpression; + if (leftIdentifier != null) + { + var type = GetType(leftIdentifier.Name); + if (type != null && type.IsResizable) + { + _writer.Write(leftIdentifier.Name); + _writer.Write(".size()"); + return 0; + } + } + } + expression.Left.Accept(this); + _writer.Write("."); + _writer.Write(expression.Name); + return 0; + } + + public int Visit(IdentifierExpression expression) + { + _writer.Write(expression.Name); + return 0; + } + + public int Visit(IndexerExpression expression) + { + var leftIdentifier = expression.Left as IdentifierExpression; + if (leftIdentifier != null) + { + var type = GetType(leftIdentifier.Name); + if (type != null && type.IsResizable) + { + _writer.Write("(("); + _writer.Write(type.Name); + _writer.Write(")"); + _writer.Write(leftIdentifier.Name); + _writer.Write(".get("); + expression.Index.Accept(this); + _writer.Write("))"); + + return 0; + } + } + + expression.Left.Accept(this); + _writer.Write("["); + expression.Index.Accept(this); + _writer.Write("]"); + return 0; + } + + public int Visit(InstanceOfExpression expression) + { + _writer.Write("("); + expression.Left.Accept(this); + _writer.Write(" instanceof "); + + var typeName = expression.Type.ToString(); + + switch (typeName) + { + case "int": + typeName = "Integer"; + break; + case "float": + typeName = "Float"; + break; + case "boolean": + typeName = "Boolean"; + break; + } + + _writer.Write(typeName); + _writer.Write(")"); + return 0; + } + + public int Visit(NewExpression expression) + { + _writer.Write("new "); + _writer.Write(expression.Type); + _writer.Write(expression.IsArray ? "[" : "("); + + { + var first = true; + foreach (var parameter in expression.Parameters) + { + if (!first) + _writer.Write(", "); + + first = false; + parameter.Accept(this); + } + } + + _writer.Write(expression.IsArray ? "]" : ")"); + + if (expression.IsArray) + { + for (var i = 1; i < expression.ArrayDimensions; i++) + { + _writer.Write("[]"); + } + } + + if (expression.Initializer != null) + { + _writer.Write("{ "); + + var first = true; + foreach (var value in expression.Initializer) + { + if (!first) + _writer.Write(", "); + + first = false; + value.Accept(this); + } + + _writer.Write(" }"); + } + + return 0; + } + + public int Visit(NullExpression expression) + { + _writer.Write("null"); + return 0; + } + + public int Visit(NumberExpression expression) + { + _writer.Write(expression.Value); + return 0; + } + + public int Visit(PostfixOperatorExpression expression) + { + expression.Left.Accept(this); + _writer.Write(PostfixOperatorMap[expression.Operation]); + return 0; + } + + public int Visit(PrefixOperatorExpression expression) + { + _writer.Write(PrefixOperatorMap[expression.Operation]); + expression.Right.Accept(this); + return 0; + } + + public int Visit(StringExpression expression) + { + _writer.Write(expression.IsSingleQuote ? "\'" : "\""); + _writer.Write(expression.Value); + _writer.Write(expression.IsSingleQuote ? "\'" : "\""); + return 0; + } + + public int Visit(TernaryExpression expression) + { + _writer.Write("("); + expression.Condition.Accept(this); + _writer.Write(" ? "); + expression.TrueExpression.Accept(this); + _writer.Write(" : "); + expression.FalseExpression.Accept(this); + _writer.Write(")"); + return 0; + } + + #endregion + + #region Resizable Conversions + + private static int _resizableIndex; + + private string ResizableToArray(ScriptType inputType, Expression inputExpression, ScriptType arrayType) + { + var temp = "__" + _resizableIndex++ + "_expr"; + + _writer.Write("{0} {1} = ", inputType, temp); + inputExpression.Accept(this); + _writer.WriteLine(";"); + + return ResizableToArray(temp, arrayType); + } + + private string ArrayToResizable(ScriptType inputType, Expression inputExpression) + { + var temp = "__" + _resizableIndex++ + "_expr"; + + _writer.Write("{0} {1} = ", inputType, temp); + inputExpression.Accept(this); + _writer.WriteLine(";"); + + return ArrayToResizable(temp); + } + + private string ResizableToArray(string inputIdentifier, ScriptType arrayType) + { + /* + arrayType[] __array = new arrayType[0]; + if (input != null) + { + __array = new arrayType[input.size()]; + for (int __i = 0; __i < input.size(); ++__i) + { + __array[__i] = (arrayType)input.get(__i); + } + } + */ + + var prefix = "__" + (_resizableIndex++) + "_"; + var arrayTemp = prefix + "array"; + var loopTemp = prefix + "i"; + + _writer.WriteLine("{1}[] {0} = new {1}[0];", arrayTemp, arrayType.Name); + _writer.WriteLine("if ({0} != null)", inputIdentifier); + _writer.WriteLine("{"); + _writer.Indent++; + _writer.WriteLine("{0} = new {1}[{2}.size()];", arrayTemp, arrayType.Name, inputIdentifier); + _writer.WriteLine("for (int {0} = 0; {0} < {1}.size(); ++{0})", loopTemp, inputIdentifier); + _writer.WriteLine("{"); + _writer.Indent++; + _writer.WriteLine("{0}[{1}] = ({2}){3}.get({1});", arrayTemp, loopTemp, arrayType.Name, inputIdentifier); + _writer.Indent--; + _writer.WriteLine("}"); + _writer.Indent--; + _writer.WriteLine("}"); + + return arrayTemp; + } + + private string ArrayToResizable(string inputIdentifier) + { + /* + Vector __array = new Vector(); + if (input != null) + { + __array.setSize(input.length); + for (int __i = 0; __i < input.length; ++__i) + { + __array.set(__i, input[__i]); + } + } + */ + + var prefix = "__" + (_resizableIndex++) + "_"; + var vectorTemp = prefix + "vector"; + var loopTemp = prefix + "i"; + + _writer.WriteLine("Vector {0} = new Vector();", vectorTemp); + _writer.WriteLine("if ({0} != null)", inputIdentifier); + _writer.WriteLine("{"); + _writer.Indent++; + _writer.WriteLine("{0}.setSize({1}.length);", vectorTemp, inputIdentifier); + _writer.WriteLine("for (int {0} = 0; {0} < {1}.length; ++{0})", loopTemp, inputIdentifier); + _writer.WriteLine("{"); + _writer.Indent++; + _writer.WriteLine("{0}.set({1}, {2}[{1}]);", vectorTemp, loopTemp, inputIdentifier); + _writer.Indent--; + _writer.WriteLine("}"); + _writer.Indent--; + _writer.WriteLine("}"); + + return vectorTemp; + } + + #endregion + + #region Scope + + private ScriptType _methodReturnType; + private Dictionary _methodParameters; + private Stack> _scopeStack; + + private ScriptType GetType(string name) + { + ScriptType type = null; + + if (_methodParameters != null && _methodParameters.TryGetValue(name, out type)) + return type; + + if (_scopeStack.Any(scope => scope.TryGetValue(name, out type))) + return type; + + // TODO: hopefully nothing inherits a resizable type... + return null; + throw new Exception("undefined variable: " + name); + } + + private void Declare(string name, ScriptType type) + { + _scopeStack.Peek().Add(name, type); + } + + private void PushScope() + { + _scopeStack.Push(new Dictionary()); + } + + private void PopScope() + { + _scopeStack.Pop(); + } + + #endregion + + #region Operator Maps + + private static readonly Dictionary BinaryOperatorMap = new Dictionary + { + { ScriptTokenType.Add, "+" }, + { ScriptTokenType.Subtract, "-" }, + { ScriptTokenType.Multiply, "*" }, + { ScriptTokenType.Divide, "/" }, + { ScriptTokenType.Remainder, "%" }, + { ScriptTokenType.BitwiseShiftLeft, "<<" }, + { ScriptTokenType.BitwiseShiftRight, ">>" }, + { ScriptTokenType.BitwiseAnd, "&" }, + { ScriptTokenType.BitwiseOr, "|" }, + { ScriptTokenType.BitwiseXor, "^" }, + { ScriptTokenType.LogicalAnd, "&&" }, + { ScriptTokenType.LogicalOr, "||" }, + { ScriptTokenType.EqualTo, "==" }, + { ScriptTokenType.NotEqualTo, "!=" }, + { ScriptTokenType.GreaterThan, ">" }, + { ScriptTokenType.GreaterThanOrEqual, ">=" }, + { ScriptTokenType.LessThan, "<" }, + { ScriptTokenType.LessThanOrEqual, "<=" }, + { ScriptTokenType.Assign, "=" }, + { ScriptTokenType.AddAssign, "+=" }, + { ScriptTokenType.SubtractAssign, "-=" }, + { ScriptTokenType.MultiplyAssign, "*=" }, + { ScriptTokenType.DivideAssign, "/=" }, + { ScriptTokenType.RemainderAssign, "%=" }, + { ScriptTokenType.BitwiseShiftLeftAssign, "<<=" }, + { ScriptTokenType.BitwiseShiftRightAssign, ">>=" }, + { ScriptTokenType.BitwiseAndAssign, "&=" }, + { ScriptTokenType.BitwiseOrAssign, "|=" }, + { ScriptTokenType.BitwiseXorAssign, "^=" }, + }; + + private static readonly Dictionary PostfixOperatorMap = new Dictionary + { + { ScriptTokenType.Increment, "++" }, + { ScriptTokenType.Decrement, "--" }, + }; + + private static readonly Dictionary PrefixOperatorMap = new Dictionary + { + { ScriptTokenType.Add, "+" }, + { ScriptTokenType.Subtract, "-" }, + { ScriptTokenType.BitwiseNot, "~" }, + { ScriptTokenType.LogicalNot, "!" }, + { ScriptTokenType.Increment, "++" }, + { ScriptTokenType.Decrement, "--" }, + }; + + #endregion + + #region Event Handlers + + private static readonly Dictionary> EventHandlers = new Dictionary> + { + { + "messageHandler", new List + { + new MethodDeclaration.Parameter(new ScriptType("obj_id"), "self"), + new MethodDeclaration.Parameter(new ScriptType("dictionary"), "params"), + } + }, + { + "commandHandler", new List + { + new MethodDeclaration.Parameter(new ScriptType("obj_id"), "self"), + new MethodDeclaration.Parameter(new ScriptType("obj_id"), "target"), + new MethodDeclaration.Parameter(new ScriptType("String"), "params"), + new MethodDeclaration.Parameter(new ScriptType("float"), "defaultTime"), + } + }, + { + "trigger", new List + { + new MethodDeclaration.Parameter(new ScriptType("obj_id"), "self"), + } + }, + }; + + #endregion + } +} diff --git a/ScriptConverter/CoreAnnotations.cs b/ScriptConverter/CoreAnnotations.cs new file mode 100644 index 0000000..1e0fb02 --- /dev/null +++ b/ScriptConverter/CoreAnnotations.cs @@ -0,0 +1,406 @@ +/* + * Copyright 2007-2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using System.Collections.Generic; + +namespace JetBrains.Annotations +{ + /// + /// Indicates that marked element should be localized or not. + /// + [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)] + public sealed class LocalizationRequiredAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + /// true if a element should be localized; otherwise, false. + public LocalizationRequiredAttribute(bool required) + { + Required = required; + } + + /// + /// Gets a value indicating whether a element should be localized. + /// true if a element should be localized; otherwise, false. + /// + public bool Required { get; set; } + + /// + /// Returns whether the value of the given object is equal to the current . + /// + /// The object to test the value equality of. + /// + /// true if the value of the given object is equal to that of the current; otherwise, false. + /// + public override bool Equals(object obj) + { + var attribute = obj as LocalizationRequiredAttribute; + return attribute != null && attribute.Required == Required; + } + + /// + /// Returns the hash code for this instance. + /// + /// A hash code for the current . + public override int GetHashCode() + { + return base.GetHashCode(); + } + } + + /// + /// Indicates that marked method builds string by format pattern and (optional) arguments. + /// Parameter, which contains format string, should be given in constructor. + /// The format string should be in -like form + /// + [AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public sealed class StringFormatMethodAttribute : Attribute + { + private readonly string myFormatParameterName; + + /// + /// Initializes new instance of StringFormatMethodAttribute + /// + /// Specifies which parameter of an annotated method should be treated as format-string + public StringFormatMethodAttribute(string formatParameterName) + { + myFormatParameterName = formatParameterName; + } + + /// + /// Gets format parameter name + /// + public string FormatParameterName + { + get { return myFormatParameterName; } + } + } + + /// + /// Indicates that the function argument should be string literal and match one of the parameters of the caller function. + /// For example, has such parameter. + /// + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] + public sealed class InvokerParameterNameAttribute : Attribute + { + } + + /// + /// Indicates that the marked method is assertion method, i.e. it halts control flow if one of the conditions is satisfied. + /// To set the condition, mark one of the parameters with attribute + /// + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public sealed class AssertionMethodAttribute : Attribute + { + } + + /// + /// Indicates the condition parameter of the assertion method. + /// The method itself should be marked by attribute. + /// The mandatory argument of the attribute is the assertion type. + /// + /// + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] + public sealed class AssertionConditionAttribute : Attribute + { + private readonly AssertionConditionType myConditionType; + + /// + /// Initializes new instance of AssertionConditionAttribute + /// + /// Specifies condition type + public AssertionConditionAttribute(AssertionConditionType conditionType) + { + myConditionType = conditionType; + } + + /// + /// Gets condition type + /// + public AssertionConditionType ConditionType + { + get { return myConditionType; } + } + } + + /// + /// Specifies assertion type. If the assertion method argument satisifes the condition, then the execution continues. + /// Otherwise, execution is assumed to be halted + /// + public enum AssertionConditionType + { + /// + /// Indicates that the marked parameter should be evaluated to true + /// + IS_TRUE = 0, + + /// + /// Indicates that the marked parameter should be evaluated to false + /// + IS_FALSE = 1, + + /// + /// Indicates that the marked parameter should be evaluated to null value + /// + IS_NULL = 2, + + /// + /// Indicates that the marked parameter should be evaluated to not null value + /// + IS_NOT_NULL = 3, + } + + /// + /// Indicates that the marked method unconditionally terminates control flow execution. + /// For example, it could unconditionally throw exception + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public sealed class TerminatesProgramAttribute : Attribute + { + } + + /// + /// Indicates that the value of marked element could be null sometimes, so the check for null is necessary before its usage + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Delegate | AttributeTargets.Field, AllowMultiple = false, Inherited = true)] + public sealed class CanBeNullAttribute : Attribute + { + } + + /// + /// Indicates that the value of marked element could never be null + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Delegate | AttributeTargets.Field, AllowMultiple = false, Inherited = true)] + public sealed class NotNullAttribute : Attribute + { + } + + /// + /// Indicates that the value of marked type (or its derivatives) cannot be compared using '==' or '!=' operators. + /// There is only exception to compare with null, it is permitted + /// + [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = true)] + public sealed class CannotApplyEqualityOperatorAttribute : Attribute + { + } + + /// + /// When applied to target attribute, specifies a requirement for any type which is marked with + /// target attribute to implement or inherit specific type or types + /// + /// + /// + /// [BaseTypeRequired(typeof(IComponent)] // Specify requirement + /// public class ComponentAttribute : Attribute + /// {} + /// + /// [Component] // ComponentAttribute requires implementing IComponent interface + /// public class MyComponent : IComponent + /// {} + /// + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] + [BaseTypeRequired(typeof(Attribute))] + public sealed class BaseTypeRequiredAttribute : Attribute + { + private readonly Type[] myBaseTypes; + + /// + /// Initializes new instance of BaseTypeRequiredAttribute + /// + /// Specifies which types are required + public BaseTypeRequiredAttribute(Type baseType) + { + myBaseTypes = new[] { baseType }; + } + + /// + /// Gets enumerations of specified base types + /// + public IEnumerable BaseTypes + { + get { return myBaseTypes; } + } + } + + /// + /// Indicates that the marked symbol is used implicitly (e.g. via reflection, in external library), + /// so this symbol will not be marked as unused (as well as by other usage inspections) + /// + [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)] + public sealed class UsedImplicitlyAttribute : Attribute + { + [UsedImplicitly] + public UsedImplicitlyAttribute() + : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) + { + } + + [UsedImplicitly] + public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) + { + UseKindFlags = useKindFlags; + TargetFlags = targetFlags; + } + + [UsedImplicitly] + public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags) + : this(useKindFlags, ImplicitUseTargetFlags.Default) + { + } + + [UsedImplicitly] + public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) + : this(ImplicitUseKindFlags.Default, targetFlags) + { + } + + [UsedImplicitly] + public ImplicitUseKindFlags UseKindFlags { get; private set; } + + /// + /// Gets value indicating what is meant to be used + /// + [UsedImplicitly] + public ImplicitUseTargetFlags TargetFlags { get; private set; } + } + + /// + /// Should be used on attributes and causes ReSharper to not mark symbols marked with such attributes as unused (as well as by other usage inspections) + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] + public sealed class MeansImplicitUseAttribute : Attribute + { + [UsedImplicitly] + public MeansImplicitUseAttribute() + : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) + { + } + + [UsedImplicitly] + public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) + { + UseKindFlags = useKindFlags; + TargetFlags = targetFlags; + } + + [UsedImplicitly] + public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags) + : this(useKindFlags, ImplicitUseTargetFlags.Default) + { + } + + [UsedImplicitly] + public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags) + : this(ImplicitUseKindFlags.Default, targetFlags) + { + } + + [UsedImplicitly] + public ImplicitUseKindFlags UseKindFlags { get; private set; } + + /// + /// Gets value indicating what is meant to be used + /// + [UsedImplicitly] + public ImplicitUseTargetFlags TargetFlags { get; private set; } + } + + [Flags] + public enum ImplicitUseKindFlags + { + Default = Access | Assign | InstantiatedWithFixedConstructorSignature, + + /// + /// Only entity marked with attribute considered used + /// + Access = 1, + + /// + /// Indicates implicit assignment to a member + /// + Assign = 2, + + /// + /// Indicates implicit instantiation of a type with fixed constructor signature. + /// That means any unused constructor parameters won't be reported as such. + /// + InstantiatedWithFixedConstructorSignature = 4, + + /// + /// Indicates implicit instantiation of a type + /// + InstantiatedNoFixedConstructorSignature = 8, + } + + /// + /// Specify what is considered used implicitly when marked with or + /// + [Flags] + public enum ImplicitUseTargetFlags + { + Default = Itself, + + Itself = 1, + + /// + /// Members of entity marked with attribute are considered used + /// + Members = 2, + + /// + /// Entity marked with attribute and all its members considered used + /// + WithMembers = Itself | Members + } + + /// + /// This attribute is intended to mark publicly available API which should not be removed and so is treated as used. + /// + [MeansImplicitUse] + public sealed class PublicAPIAttribute : Attribute + { + public PublicAPIAttribute() + { + } + + // ReSharper disable UnusedParameter.Local + public PublicAPIAttribute(string comment) + // ReSharper restore UnusedParameter.Local + { + } + } + + /// + /// Tells code analysis engine if the parameter is completely handled when the invoked method is on stack. + /// If the parameter is delegate, indicates that delegate is executed while the method is executed. + /// If the parameter is enumerable, indicates that it is enumerated while the method is executed. + /// + [AttributeUsage(AttributeTargets.Parameter, Inherited = true)] + public sealed class InstantHandleAttribute : Attribute + { + } + + /// + /// Indicates that method doesn't contain observable side effects. + /// The same as + /// + [AttributeUsage(AttributeTargets.Method, Inherited = true)] + public sealed class PureAttribute : Attribute { } +} diff --git a/ScriptConverter/GenericComparer.cs b/ScriptConverter/GenericComparer.cs new file mode 100644 index 0000000..e80bc90 --- /dev/null +++ b/ScriptConverter/GenericComparer.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; + +namespace ScriptConverter +{ + public class GenericComparer : IComparer + { + private Func _comparer; + + public GenericComparer(Func comparer) + { + _comparer = comparer; + } + + public int Compare(T x, T y) + { + return _comparer(x, y); + } + } +} diff --git a/ScriptConverter/IndentTextWriter.cs b/ScriptConverter/IndentTextWriter.cs new file mode 100644 index 0000000..2ed4ee1 --- /dev/null +++ b/ScriptConverter/IndentTextWriter.cs @@ -0,0 +1,63 @@ +using System; +using System.Globalization; +using System.IO; +using System.Text; + +namespace ScriptConverter +{ + public class IndentTextWriter : TextWriter + { + private readonly TextWriter _writer; + private readonly string _indentStr; + private bool _shouldIndent; + + public int Indent { get; set; } + + public IndentTextWriter(TextWriter writer, string indentStr = " ") + { + _writer = writer; + _indentStr = indentStr; + _shouldIndent = false; + } + + public override Encoding Encoding + { + get { return Encoding.Unicode; } + } + + public override IFormatProvider FormatProvider + { + get { return CultureInfo.InvariantCulture; } + } + + public override void Write(char value) + { + if (_shouldIndent) + { + _shouldIndent = false; // shouldIndent must be cleared first + WriteIndent(); + } + + _writer.Write(value); + } + + public override void WriteLine() + { + base.WriteLine(); + + _shouldIndent = true; // defer indenting until the next Write + } + + public override void WriteLine(string value) + { + Write(value); + WriteLine(); + } + + public void WriteIndent() + { + for (var i = 0; i < Indent; i++) + Write(_indentStr); + } + } +} diff --git a/ScriptConverter/Parser/CompilerError.cs b/ScriptConverter/Parser/CompilerError.cs new file mode 100644 index 0000000..ab0b65c --- /dev/null +++ b/ScriptConverter/Parser/CompilerError.cs @@ -0,0 +1,13 @@ +namespace ScriptConverter.Parser +{ + static class CompilerError + { + public const string UnterminatedString = "Unterminated string"; + public const string UnexpectedEofString = "Unexpected end of file (bad escape sequence)"; + public const string InvalidEscapeSequence = "Invalid escape sequence '{0}'"; + public const string CharLiteralLength = "Character literals must be one byte long"; + public const string InvalidNumber = "Invalid {0} number '{1}'"; + + public const string ExpectedButFound = "Expected {0} but found {1}"; + } +} diff --git a/ScriptConverter/Parser/CompilerException.cs b/ScriptConverter/Parser/CompilerException.cs new file mode 100644 index 0000000..ca38ab9 --- /dev/null +++ b/ScriptConverter/Parser/CompilerException.cs @@ -0,0 +1,21 @@ +using System; +using JetBrains.Annotations; + +namespace ScriptConverter.Parser +{ + public class CompilerException : Exception + { + protected CompilerException(string message) + : base(message) + { + + } + + [StringFormatMethod("format")] + internal CompilerException(ScriptToken token, string format, params object[] args) + : base(string.Format("{0}({1}): {2}", token.FileName ?? "null", token.RangeString, string.Format(format, args))) + { + + } + } +} diff --git a/ScriptConverter/Parser/Lexer.Static.cs b/ScriptConverter/Parser/Lexer.Static.cs new file mode 100644 index 0000000..8bfae9f --- /dev/null +++ b/ScriptConverter/Parser/Lexer.Static.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace ScriptConverter.Parser +{ + abstract partial class Lexer + { + protected static TToken EofToken; + + static Lexer() + { + EofToken = null; + } + + protected class OperatorDictionary : IEnumerable + { + private readonly GenericComparer> _comparer; + private Dictionary>> _operatorDictionary; + + public OperatorDictionary() + { + _comparer = new GenericComparer>((a, b) => b.Item1.Length - a.Item1.Length); + _operatorDictionary = new Dictionary>>(); + } + + public void Add(string op, T type) + { + List> list; + if (!_operatorDictionary.TryGetValue(op[0], out list)) + { + list = new List>(); + _operatorDictionary.Add(op[0], list); + } + + list.Add(Tuple.Create(op, type)); + list.Sort(_comparer); + } + + public IEnumerable> Lookup(char ch) + { + List> list; + if (!_operatorDictionary.TryGetValue(ch, out list)) + return null; + + return list; + } + + public IEnumerator GetEnumerator() + { + throw new NotSupportedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + } +} diff --git a/ScriptConverter/Parser/Lexer.cs b/ScriptConverter/Parser/Lexer.cs new file mode 100644 index 0000000..62f6ba0 --- /dev/null +++ b/ScriptConverter/Parser/Lexer.cs @@ -0,0 +1,224 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using JetBrains.Annotations; + +namespace ScriptConverter.Parser +{ + abstract partial class Lexer : IEnumerable + where TToken : Token + { + protected delegate bool LexerRule(char ch, out TToken token); + + private readonly IEnumerable _sourceEnumerable; + private IEnumerator _source; + private int _length; + private List _read; + private int _index; + + protected string FileName { get; private set; } + protected int Line { get; private set; } + protected int Column { get; private set; } + protected int StartLine { get; private set; } + protected int StartColumn { get; private set; } + + protected List Rules { get; set; } + + protected bool AtEof + { + get { return _index >= _length; } + } + + protected Lexer(IEnumerable source, string fileName = null) + { + if (source == null) + throw new ArgumentNullException("source"); + + FileName = fileName; + _sourceEnumerable = source; + } + + public IEnumerator GetEnumerator() + { + if (Rules == null) + throw new Exception("Rules must be set"); + + if (EofToken == null) + throw new Exception("EofToken must be set"); + + _length = int.MaxValue; + _source = _sourceEnumerable.GetEnumerator(); + _read = new List(16); + + _index = 0; + + Line = 1; + Column = 1; + + while (_index < _length) + { + SkipWhiteSpace(); + + if (SkipComment()) + continue; + + if (_index >= _length) + break; + + StartLine = Line; + StartColumn = Column; + + var ch = PeekChar(); + TToken token = null; + + if (!Rules.Any(rule => rule(ch, out token))) + throw Error("Unexpected character '{0}'", ch); + + yield return token; + } + + while (true) + yield return EofToken; + } + + protected virtual bool SkipComment() + { + return false; + } + + protected void SkipWhiteSpace() + { + while (_index < _length) + { + var ch = PeekChar(); + + if (!char.IsWhiteSpace(ch)) + break; + + TakeChar(); + } + } + + protected bool TakeIfNext(string value) + { + if (!IsNext(value)) + return false; + + for (var i = 0; i < value.Length; i++) + TakeChar(); + + return true; + } + + protected bool IsNext(string value) + { + if (_index + value.Length > _length) + return false; + + return !value.Where((t, i) => PeekChar(i) != t).Any(); + } + + protected string TakeWhile(Func condition) + { + var sb = new StringBuilder(); + + while (_index < _length) + { + var ch = PeekChar(); + + if (!condition(ch)) + break; + + sb.Append(TakeChar()); + } + + return sb.ToString(); + } + + protected char TakeChar() + { + var result = TakeCharImpl(); + + if (result == '\n') + { + Line++; + Column = 0; + } + + if (result == '\r') + { + if (PeekChar() == '\n') + TakeCharImpl(); + + Line++; + Column = 0; + } + + Column++; + + return result; + } + + private char TakeCharImpl() + { + PeekChar(); + + var result = _read[0]; + _read.RemoveAt(0); + _index++; + + return result; + } + + protected string PeekString(int length) + { + if (length <= 0) + throw new ArgumentOutOfRangeException("length", "distance must be at least 1"); + + var sb = new StringBuilder(length); + + for (var i = 0; i < length; i++) + { + sb.Append(PeekChar(i)); + } + + return sb.ToString(); + } + + protected char PeekChar(int distance = 0) + { + if (distance < 0) + throw new ArgumentOutOfRangeException("distance", "distance can't be negative"); + + while (_read.Count <= distance) + { + var success = _source.MoveNext(); + _read.Add(success ? _source.Current : '\0'); + + if (!success) + _length = _index + _read.Count - 1; + } + + return _read[distance]; + } + + [StringFormatMethod("format")] + protected Exception Error(string format, params object[] args) + { + return new LexerException(FileName, new SourcePosition(Line, Column), format, args); + } + + [StringFormatMethod("format")] + protected Exception ErrorStart(string format, params object[] args) + { + return new LexerException(FileName, new SourcePosition(StartLine, StartColumn), format, args); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/ScriptConverter/Parser/LexerException.cs b/ScriptConverter/Parser/LexerException.cs new file mode 100644 index 0000000..eb656e7 --- /dev/null +++ b/ScriptConverter/Parser/LexerException.cs @@ -0,0 +1,15 @@ +using System; +using JetBrains.Annotations; + +namespace ScriptConverter.Parser +{ + class LexerException : Exception + { + [StringFormatMethod("format")] + internal LexerException(string fileName, SourcePosition pos, string format, params object[] args) + : base(string.Format("{0}({1}): {2}", fileName ?? "null", pos, string.Format(format, args))) + { + + } + } +} diff --git a/ScriptConverter/Parser/Parselets/Declarations/ConstParselet.cs b/ScriptConverter/Parser/Parselets/Declarations/ConstParselet.cs new file mode 100644 index 0000000..00fa030 --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Declarations/ConstParselet.cs @@ -0,0 +1,23 @@ +using ScriptConverter.Ast; +using ScriptConverter.Ast.Declarations; + +namespace ScriptConverter.Parser.Parselets.Declarations +{ + class ConstParselet : IDeclarationParselet + { + public Declaration Parse(ScriptParser parser, ScriptToken token) + { + ScriptType type; + string name; + parser.ParseNamedType(out type, out name); + + parser.Take(ScriptTokenType.Assign); + + var value = parser.ParseExpression(); + + parser.Take(ScriptTokenType.Semicolon); + + return new FieldDeclaration(token, parser.Previous, type, name, value, true, true); + } + } +} diff --git a/ScriptConverter/Parser/Parselets/Declarations/IncludeParselet.cs b/ScriptConverter/Parser/Parselets/Declarations/IncludeParselet.cs new file mode 100644 index 0000000..9712965 --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Declarations/IncludeParselet.cs @@ -0,0 +1,34 @@ +using System; +using System.Linq; +using ScriptConverter.Ast.Declarations; + +namespace ScriptConverter.Parser.Parselets.Declarations +{ + class IncludeParselet : IDeclarationParselet + { + public Declaration Parse(ScriptParser parser, ScriptToken token) + { + var done = false; + var tokens = parser.ParseSeparatedBy(ScriptTokenType.Dot, (_, first) => + { + if (done) + return null; + + if (parser.Match(ScriptTokenType.Multiply)) + { + done = true; + return parser.Take(ScriptTokenType.Multiply); + } + + return parser.Take(ScriptTokenType.Identifier); + }); + + var name = string.Join(".", tokens.Select(t => t.Contents)); + + if (!parser.MatchAndTake(ScriptTokenType.Semicolon)) + Console.WriteLine("JFjngkjasnholjhnaskl"); + + return new IncludeDeclaration(token, parser.Previous, name); + } + } +} diff --git a/ScriptConverter/Parser/Parselets/Declarations/InheritsParselet.cs b/ScriptConverter/Parser/Parselets/Declarations/InheritsParselet.cs new file mode 100644 index 0000000..09cf7dc --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Declarations/InheritsParselet.cs @@ -0,0 +1,21 @@ +using System; +using System.Linq; +using ScriptConverter.Ast.Declarations; + +namespace ScriptConverter.Parser.Parselets.Declarations +{ + class InheritsParselet : IDeclarationParselet + { + public Declaration Parse(ScriptParser parser, ScriptToken token) + { + var tokens = parser.ParseSeparatedBy(ScriptTokenType.Dot, (_, first) => parser.Take(ScriptTokenType.Identifier)); + + var name = string.Join(".", tokens.Select(t => t.Contents)); + + if (!parser.MatchAndTake(ScriptTokenType.Semicolon)) + Console.WriteLine("fkshgnbkashnkhnasb"); + + return new InheritsDeclaration(token, parser.Previous, name); + } + } +} diff --git a/ScriptConverter/Parser/Parselets/Declarations/MethodParselet.cs b/ScriptConverter/Parser/Parselets/Declarations/MethodParselet.cs new file mode 100644 index 0000000..c27f601 --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Declarations/MethodParselet.cs @@ -0,0 +1,43 @@ +using System.Linq; +using ScriptConverter.Ast; +using ScriptConverter.Ast.Declarations; + +namespace ScriptConverter.Parser.Parselets.Declarations +{ + class MethodParselet : IDeclarationParselet + { + public Declaration Parse(ScriptParser parser, ScriptToken token) + { + var returnType = parser.ParseType(token); + var name = parser.Take(ScriptTokenType.Identifier).Contents; + + if (parser.MatchAndTake(ScriptTokenType.Assign)) + { + var value = parser.ParseExpression(); + parser.Take(ScriptTokenType.Semicolon); + + return new FieldDeclaration(token, parser.Previous, returnType, name, value, false, false); + } + + parser.Take(ScriptTokenType.LeftParen); + + var parameters = parser.ParseSeparatedBy(ScriptTokenType.Comma, (_, first) => + { + if (parser.Match(ScriptTokenType.RightParen)) + return null; + + ScriptType paramType; + string paramName; + parser.ParseNamedType(out paramType, out paramName); + + return new MethodDeclaration.Parameter(paramType, paramName); + }).ToList(); + + parser.Take(ScriptTokenType.RightParen); + + var block = parser.ParseBlock(false); + + return new MethodDeclaration(token, parser.Previous, returnType, name, parameters, block); + } + } +} diff --git a/ScriptConverter/Parser/Parselets/Declarations/PublicParselet.cs b/ScriptConverter/Parser/Parselets/Declarations/PublicParselet.cs new file mode 100644 index 0000000..2e69418 --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Declarations/PublicParselet.cs @@ -0,0 +1,23 @@ +using ScriptConverter.Ast; +using ScriptConverter.Ast.Declarations; + +namespace ScriptConverter.Parser.Parselets.Declarations +{ + class PublicParselet : IDeclarationParselet + { + public Declaration Parse(ScriptParser parser, ScriptToken token) + { + ScriptType type; + string name; + parser.ParseNamedType(out type, out name); + + parser.Take(ScriptTokenType.Assign); + + var value = parser.ParseExpression(); + + parser.Take(ScriptTokenType.Semicolon); + + return new FieldDeclaration(token, parser.Previous, type, name, value, false, true); + } + } +} diff --git a/ScriptConverter/Parser/Parselets/Expressions/ArrayInitializerParselet.cs b/ScriptConverter/Parser/Parselets/Expressions/ArrayInitializerParselet.cs new file mode 100644 index 0000000..91ee9f4 --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Expressions/ArrayInitializerParselet.cs @@ -0,0 +1,22 @@ +using System.Linq; +using ScriptConverter.Ast.Expressions; + +namespace ScriptConverter.Parser.Parselets.Expressions +{ + class ArrayInitializerParselet : IPrefixParselet + { + public Expression Parse(ScriptParser parser, ScriptToken token) + { + var values = parser.ParseSeparatedBy(ScriptTokenType.Comma, (_, first) => + { + if (parser.Match(ScriptTokenType.RightBrace)) + return null; + + return parser.ParseExpression(); + }).ToList(); + + parser.Take(ScriptTokenType.RightBrace); + return new ArrayInitializerExpression(token, parser.Previous, values); + } + } +} diff --git a/ScriptConverter/Parser/Parselets/Expressions/BinaryOperatorParselet.cs b/ScriptConverter/Parser/Parselets/Expressions/BinaryOperatorParselet.cs new file mode 100644 index 0000000..3f66999 --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Expressions/BinaryOperatorParselet.cs @@ -0,0 +1,23 @@ +using ScriptConverter.Ast.Expressions; + +namespace ScriptConverter.Parser.Parselets.Expressions +{ + class BinaryOperatorParselet : IInfixParselet + { + private readonly bool _isRight; + + public BinaryOperatorParselet(PrecedenceValue precedence, bool isRight) + { + _isRight = isRight; + Precedence = (int)precedence; + } + + public int Precedence { get; private set; } + + public Expression Parse(ScriptParser parser, Expression left, ScriptToken token) + { + var right = parser.ParseExpression(Precedence - (_isRight ? 1 : 0)); + return new BinaryOperatorExpression(token.Type, left, right); + } + } +} diff --git a/ScriptConverter/Parser/Parselets/Expressions/BoolParselet.cs b/ScriptConverter/Parser/Parselets/Expressions/BoolParselet.cs new file mode 100644 index 0000000..eb8f536 --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Expressions/BoolParselet.cs @@ -0,0 +1,19 @@ +using ScriptConverter.Ast.Expressions; + +namespace ScriptConverter.Parser.Parselets.Expressions +{ + class BoolParselet : IPrefixParselet + { + private readonly bool _value; + + public BoolParselet(bool value) + { + _value = value; + } + + public Expression Parse(ScriptParser parser, ScriptToken token) + { + return new BoolExpression(token, _value); + } + } +} diff --git a/ScriptConverter/Parser/Parselets/Expressions/CallParselet.cs b/ScriptConverter/Parser/Parselets/Expressions/CallParselet.cs new file mode 100644 index 0000000..f259100 --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Expressions/CallParselet.cs @@ -0,0 +1,25 @@ +using System.Linq; +using ScriptConverter.Ast.Expressions; + +namespace ScriptConverter.Parser.Parselets.Expressions +{ + class CallParselet : IInfixParselet + { + public int Precedence { get { return (int)PrecedenceValue.Suffix; } } + + public Expression Parse(ScriptParser parser, Expression left, ScriptToken token) + { + var parameters = parser.ParseSeparatedBy(ScriptTokenType.Comma, (_, first) => + { + if (first && parser.Match(ScriptTokenType.RightParen)) + return null; + + return parser.ParseExpression(); + }).ToList(); + + parser.Take(ScriptTokenType.RightParen); + + return new CallExpression(parser.Previous, left, parameters); + } + } +} diff --git a/ScriptConverter/Parser/Parselets/Expressions/FieldParselet.cs b/ScriptConverter/Parser/Parselets/Expressions/FieldParselet.cs new file mode 100644 index 0000000..5b03e8d --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Expressions/FieldParselet.cs @@ -0,0 +1,15 @@ +using ScriptConverter.Ast.Expressions; + +namespace ScriptConverter.Parser.Parselets.Expressions +{ + class FieldParselet : IInfixParselet + { + public int Precedence { get { return (int)PrecedenceValue.Suffix; } } + + public Expression Parse(ScriptParser parser, Expression left, ScriptToken token) + { + var name = parser.Take(ScriptTokenType.Identifier); + return new FieldExpression(left, name); + } + } +} diff --git a/ScriptConverter/Parser/Parselets/Expressions/GroupParselet.cs b/ScriptConverter/Parser/Parselets/Expressions/GroupParselet.cs new file mode 100644 index 0000000..34e6614 --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Expressions/GroupParselet.cs @@ -0,0 +1,54 @@ +using ScriptConverter.Ast.Expressions; + +namespace ScriptConverter.Parser.Parselets.Expressions +{ + class GroupParselet : IPrefixParselet + { + public Expression Parse(ScriptParser parser, ScriptToken token) + { + if (IsCast(parser)) + { + var type = parser.ParseType(); + parser.Take(ScriptTokenType.RightParen); + var value = parser.ParseExpression((int)PrecedenceValue.Cast - 1); + return new CastExpression(token, type, value); + } + + var expression = parser.ParseExpression(); + parser.Take(ScriptTokenType.RightParen); + return expression; + } + + private static bool IsCast(ScriptParser parser) + { + var token = parser.Peek(); + if (token.Type != ScriptTokenType.Identifier) + return false; + + if (token.Contents == "resizeable") + return true; + + var i = 1; + while (parser.Match(ScriptTokenType.Dot, i) && parser.Match(ScriptTokenType.Identifier, i + 1)) + { + i += 2; + } + + var next1Type = parser.Peek(i).Type; + var next2Type = parser.Peek(i + 1).Type; + + return (next1Type == ScriptTokenType.RightParen && IsCastable(next2Type)) || + (next1Type == ScriptTokenType.LeftSquare && next2Type == ScriptTokenType.RightSquare); + } + + private static bool IsCastable(ScriptTokenType token) + { + return token == ScriptTokenType.Identifier || + token == ScriptTokenType.Number || + token == ScriptTokenType.Null || + token == ScriptTokenType.LeftParen || + token == ScriptTokenType.SingleString || + token == ScriptTokenType.String; + } + } +} diff --git a/ScriptConverter/Parser/Parselets/Expressions/HashParselet.cs b/ScriptConverter/Parser/Parselets/Expressions/HashParselet.cs new file mode 100644 index 0000000..0f2c9f6 --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Expressions/HashParselet.cs @@ -0,0 +1,86 @@ +using System.Linq; +using System.Text; +using ScriptConverter.Ast.Expressions; + +namespace ScriptConverter.Parser.Parselets.Expressions +{ + class HashParselet : IPrefixParselet + { + public Expression Parse(ScriptParser parser, ScriptToken token) + { + var str = parser.Take(ScriptTokenType.String); + var crc = (int)StringtoCrc(str.Contents); + var crcStr = "0x" + crc.ToString("X8"); + return new NumberExpression(new ScriptToken(str, ScriptTokenType.Number, crcStr)); + } + + private static uint StringtoCrc(string input) + { + byte[] bytes = Encoding.ASCII.GetBytes(input.ToArray()); + uint crc = 0xFFFFFFFF; + + for (int i = 0; i < bytes.Length; ++i) + { + crc = _crcTable[(bytes[i] ^ (crc >> 24)) & 0x000000FF] ^ (crc << 8); + } + + return ~crc; + } + + private static uint[] _crcTable = + { + 0x0000000, + 0x04C11DB7, 0x09823B6E, 0x0D4326D9, 0x130476DC, 0x17C56B6B, + 0x1A864DB2, 0x1E475005, 0x2608EDB8, 0x22C9F00F, 0x2F8AD6D6, + 0x2B4BCB61, 0x350C9B64, 0x31CD86D3, 0x3C8EA00A, 0x384FBDBD, + 0x4C11DB70, 0x48D0C6C7, 0x4593E01E, 0x4152FDA9, 0x5F15ADAC, + 0x5BD4B01B, 0x569796C2, 0x52568B75, 0x6A1936C8, 0x6ED82B7F, + 0x639B0DA6, 0x675A1011, 0x791D4014, 0x7DDC5DA3, 0x709F7B7A, + 0x745E66CD, 0x9823B6E0, 0x9CE2AB57, 0x91A18D8E, 0x95609039, + 0x8B27C03C, 0x8FE6DD8B, 0x82A5FB52, 0x8664E6E5, 0xBE2B5B58, + 0xBAEA46EF, 0xB7A96036, 0xB3687D81, 0xAD2F2D84, 0xA9EE3033, + 0xA4AD16EA, 0xA06C0B5D, 0xD4326D90, 0xD0F37027, 0xDDB056FE, + 0xD9714B49, 0xC7361B4C, 0xC3F706FB, 0xCEB42022, 0xCA753D95, + 0xF23A8028, 0xF6FB9D9F, 0xFBB8BB46, 0xFF79A6F1, 0xE13EF6F4, + 0xE5FFEB43, 0xE8BCCD9A, 0xEC7DD02D, 0x34867077, 0x30476DC0, + 0x3D044B19, 0x39C556AE, 0x278206AB, 0x23431B1C, 0x2E003DC5, + 0x2AC12072, 0x128E9DCF, 0x164F8078, 0x1B0CA6A1, 0x1FCDBB16, + 0x018AEB13, 0x054BF6A4, 0x0808D07D, 0x0CC9CDCA, 0x7897AB07, + 0x7C56B6B0, 0x71159069, 0x75D48DDE, 0x6B93DDDB, 0x6F52C06C, + 0x6211E6B5, 0x66D0FB02, 0x5E9F46BF, 0x5A5E5B08, 0x571D7DD1, + 0x53DC6066, 0x4D9B3063, 0x495A2DD4, 0x44190B0D, 0x40D816BA, + 0xACA5C697, 0xA864DB20, 0xA527FDF9, 0xA1E6E04E, 0xBFA1B04B, + 0xBB60ADFC, 0xB6238B25, 0xB2E29692, 0x8AAD2B2F, 0x8E6C3698, + 0x832F1041, 0x87EE0DF6, 0x99A95DF3, 0x9D684044, 0x902B669D, + 0x94EA7B2A, 0xE0B41DE7, 0xE4750050, 0xE9362689, 0xEDF73B3E, + 0xF3B06B3B, 0xF771768C, 0xFA325055, 0xFEF34DE2, 0xC6BCF05F, + 0xC27DEDE8, 0xCF3ECB31, 0xCBFFD686, 0xD5B88683, 0xD1799B34, + 0xDC3ABDED, 0xD8FBA05A, 0x690CE0EE, 0x6DCDFD59, 0x608EDB80, + 0x644FC637, 0x7A089632, 0x7EC98B85, 0x738AAD5C, 0x774BB0EB, + 0x4F040D56, 0x4BC510E1, 0x46863638, 0x42472B8F, 0x5C007B8A, + 0x58C1663D, 0x558240E4, 0x51435D53, 0x251D3B9E, 0x21DC2629, + 0x2C9F00F0, 0x285E1D47, 0x36194D42, 0x32D850F5, 0x3F9B762C, + 0x3B5A6B9B, 0x0315D626, 0x07D4CB91, 0x0A97ED48, 0x0E56F0FF, + 0x1011A0FA, 0x14D0BD4D, 0x19939B94, 0x1D528623, 0xF12F560E, + 0xF5EE4BB9, 0xF8AD6D60, 0xFC6C70D7, 0xE22B20D2, 0xE6EA3D65, + 0xEBA91BBC, 0xEF68060B, 0xD727BBB6, 0xD3E6A601, 0xDEA580D8, + 0xDA649D6F, 0xC423CD6A, 0xC0E2D0DD, 0xCDA1F604, 0xC960EBB3, + 0xBD3E8D7E, 0xB9FF90C9, 0xB4BCB610, 0xB07DABA7, 0xAE3AFBA2, + 0xAAFBE615, 0xA7B8C0CC, 0xA379DD7B, 0x9B3660C6, 0x9FF77D71, + 0x92B45BA8, 0x9675461F, 0x8832161A, 0x8CF30BAD, 0x81B02D74, + 0x857130C3, 0x5D8A9099, 0x594B8D2E, 0x5408ABF7, 0x50C9B640, + 0x4E8EE645, 0x4A4FFBF2, 0x470CDD2B, 0x43CDC09C, 0x7B827D21, + 0x7F436096, 0x7200464F, 0x76C15BF8, 0x68860BFD, 0x6C47164A, + 0x61043093, 0x65C52D24, 0x119B4BE9, 0x155A565E, 0x18197087, + 0x1CD86D30, 0x029F3D35, 0x065E2082, 0x0B1D065B, 0x0FDC1BEC, + 0x3793A651, 0x3352BBE6, 0x3E119D3F, 0x3AD08088, 0x2497D08D, + 0x2056CD3A, 0x2D15EBE3, 0x29D4F654, 0xC5A92679, 0xC1683BCE, + 0xCC2B1D17, 0xC8EA00A0, 0xD6AD50A5, 0xD26C4D12, 0xDF2F6BCB, + 0xDBEE767C, 0xE3A1CBC1, 0xE760D676, 0xEA23F0AF, 0xEEE2ED18, + 0xF0A5BD1D, 0xF464A0AA, 0xF9278673, 0xFDE69BC4, 0x89B8FD09, + 0x8D79E0BE, 0x803AC667, 0x84FBDBD0, 0x9ABC8BD5, 0x9E7D9662, + 0x933EB0BB, 0x97FFAD0C, 0xAFB010B1, 0xAB710D06, 0xA6322BDF, + 0xA2F33668, 0xBCB4666D, 0xB8757BDA, 0xB5365D03, 0xB1F740B4 + }; + } +} diff --git a/ScriptConverter/Parser/Parselets/Expressions/IdentifierParselet.cs b/ScriptConverter/Parser/Parselets/Expressions/IdentifierParselet.cs new file mode 100644 index 0000000..7177042 --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Expressions/IdentifierParselet.cs @@ -0,0 +1,12 @@ +using ScriptConverter.Ast.Expressions; + +namespace ScriptConverter.Parser.Parselets.Expressions +{ + class IdentifierParselet : IPrefixParselet + { + public Expression Parse(ScriptParser parser, ScriptToken token) + { + return new IdentifierExpression(token); + } + } +} diff --git a/ScriptConverter/Parser/Parselets/Expressions/IndexerParselet.cs b/ScriptConverter/Parser/Parselets/Expressions/IndexerParselet.cs new file mode 100644 index 0000000..2929133 --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Expressions/IndexerParselet.cs @@ -0,0 +1,18 @@ +using ScriptConverter.Ast.Expressions; + +namespace ScriptConverter.Parser.Parselets.Expressions +{ + class IndexerParselet : IInfixParselet + { + public int Precedence { get { return (int)PrecedenceValue.Suffix; } } + + public Expression Parse(ScriptParser parser, Expression left, ScriptToken token) + { + var index = parser.ParseExpression(); + + parser.Take(ScriptTokenType.RightSquare); + + return new IndexerExpression(parser.Previous, left, index); + } + } +} diff --git a/ScriptConverter/Parser/Parselets/Expressions/InstanceOfParselet.cs b/ScriptConverter/Parser/Parselets/Expressions/InstanceOfParselet.cs new file mode 100644 index 0000000..31a91fb --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Expressions/InstanceOfParselet.cs @@ -0,0 +1,15 @@ +using ScriptConverter.Ast.Expressions; + +namespace ScriptConverter.Parser.Parselets.Expressions +{ + class InstanceOfParselet : IInfixParselet + { + public int Precedence { get { return (int)PrecedenceValue.Relational; } } + + public Expression Parse(ScriptParser parser, Expression left, ScriptToken token) + { + var type = parser.ParseType(); + return new InstanceOfExpression(parser.Previous, left, type); + } + } +} diff --git a/ScriptConverter/Parser/Parselets/Expressions/NewParselet.cs b/ScriptConverter/Parser/Parselets/Expressions/NewParselet.cs new file mode 100644 index 0000000..4d2bd64 --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Expressions/NewParselet.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using System.Linq; +using ScriptConverter.Ast.Expressions; + +namespace ScriptConverter.Parser.Parselets.Expressions +{ + class NewParselet : IPrefixParselet + { + public Expression Parse(ScriptParser parser, ScriptToken token) + { + var type = string.Join(".", parser.ParseSeparatedBy(ScriptTokenType.Dot, (_, first) => parser.Take(ScriptTokenType.Identifier).Contents)); + + if (type == "string") + type = "String"; + + var isArray = parser.Match(ScriptTokenType.LeftSquare); + + parser.Take(isArray ? ScriptTokenType.LeftSquare : ScriptTokenType.LeftParen); + + var parameters = parser.ParseSeparatedBy(ScriptTokenType.Comma, (_, first) => + { + if (first && parser.Match(isArray ? ScriptTokenType.RightSquare : ScriptTokenType.RightParen)) + return null; + + return parser.ParseExpression(); + }).ToList(); + + parser.Take(isArray ? ScriptTokenType.RightSquare : ScriptTokenType.RightParen); + + List values = null; + if (isArray && parser.MatchAndTake(ScriptTokenType.LeftBrace)) + { + values = parser.ParseSeparatedBy(ScriptTokenType.Comma, (_, first) => + { + if (parser.Match(ScriptTokenType.RightBrace)) + return null; + + return parser.ParseExpression(); + }).ToList(); + + parser.MatchAndTake(ScriptTokenType.RightBrace); + } + + int arrayDimensions = isArray ? 1 : 0; + if (isArray) + { + while (parser.Match(ScriptTokenType.LeftSquare) && parser.Match(ScriptTokenType.RightSquare, 1)) + { + parser.Take(ScriptTokenType.LeftSquare); + parser.Take(ScriptTokenType.RightSquare); + arrayDimensions++; + } + } + + return new NewExpression(token, parser.Previous, type, arrayDimensions, parameters, values); + } + } +} diff --git a/ScriptConverter/Parser/Parselets/Expressions/NullParselet.cs b/ScriptConverter/Parser/Parselets/Expressions/NullParselet.cs new file mode 100644 index 0000000..42b79fd --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Expressions/NullParselet.cs @@ -0,0 +1,12 @@ +using ScriptConverter.Ast.Expressions; + +namespace ScriptConverter.Parser.Parselets.Expressions +{ + class NullParselet : IPrefixParselet + { + public Expression Parse(ScriptParser parser, ScriptToken token) + { + return new NullExpression(token); + } + } +} diff --git a/ScriptConverter/Parser/Parselets/Expressions/NumberParselet.cs b/ScriptConverter/Parser/Parselets/Expressions/NumberParselet.cs new file mode 100644 index 0000000..529b8f4 --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Expressions/NumberParselet.cs @@ -0,0 +1,12 @@ +using ScriptConverter.Ast.Expressions; + +namespace ScriptConverter.Parser.Parselets.Expressions +{ + class NumberParselet : IPrefixParselet + { + public Expression Parse(ScriptParser parser, ScriptToken token) + { + return new NumberExpression(token); + } + } +} diff --git a/ScriptConverter/Parser/Parselets/Expressions/PostfixOperatorParselet.cs b/ScriptConverter/Parser/Parselets/Expressions/PostfixOperatorParselet.cs new file mode 100644 index 0000000..a2b195c --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Expressions/PostfixOperatorParselet.cs @@ -0,0 +1,19 @@ +using ScriptConverter.Ast.Expressions; + +namespace ScriptConverter.Parser.Parselets.Expressions +{ + class PostfixOperatorParselet : IInfixParselet + { + public PostfixOperatorParselet(PrecedenceValue precedence) + { + Precedence = (int)precedence; + } + + public int Precedence { get; private set; } + + public Expression Parse(ScriptParser parser, Expression left, ScriptToken token) + { + return new PostfixOperatorExpression(token, left); + } + } +} diff --git a/ScriptConverter/Parser/Parselets/Expressions/PrefixOperatorParselet.cs b/ScriptConverter/Parser/Parselets/Expressions/PrefixOperatorParselet.cs new file mode 100644 index 0000000..55d3596 --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Expressions/PrefixOperatorParselet.cs @@ -0,0 +1,20 @@ +using ScriptConverter.Ast.Expressions; + +namespace ScriptConverter.Parser.Parselets.Expressions +{ + class PrefixOperatorParselet : IPrefixParselet + { + private readonly int _precedence; + + public PrefixOperatorParselet(PrecedenceValue precedence) + { + _precedence = (int)precedence; + } + + public Expression Parse(ScriptParser parser, ScriptToken token) + { + var right = parser.ParseExpression(_precedence); + return new PrefixOperatorExpression(token, right); + } + } +} diff --git a/ScriptConverter/Parser/Parselets/Expressions/StringParselet.cs b/ScriptConverter/Parser/Parselets/Expressions/StringParselet.cs new file mode 100644 index 0000000..2a8bb98 --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Expressions/StringParselet.cs @@ -0,0 +1,19 @@ +using ScriptConverter.Ast.Expressions; + +namespace ScriptConverter.Parser.Parselets.Expressions +{ + class StringParselet : IPrefixParselet + { + private readonly bool _singleQuote; + + public StringParselet(bool singleQuote) + { + _singleQuote = singleQuote; + } + + public Expression Parse(ScriptParser parser, ScriptToken token) + { + return new StringExpression(token, _singleQuote); + } + } +} diff --git a/ScriptConverter/Parser/Parselets/Expressions/TernaryParselet.cs b/ScriptConverter/Parser/Parselets/Expressions/TernaryParselet.cs new file mode 100644 index 0000000..edf176c --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Expressions/TernaryParselet.cs @@ -0,0 +1,18 @@ +using ScriptConverter.Ast.Expressions; + +namespace ScriptConverter.Parser.Parselets.Expressions +{ + class TernaryParselet : IInfixParselet + { + public int Precedence { get { return (int)PrecedenceValue.Ternary; } } + + public Expression Parse(ScriptParser parser, Expression left, ScriptToken token) + { + var trueExpr = parser.ParseExpression(); + parser.Take(ScriptTokenType.Colon); + var falseExpr = parser.ParseExpression(); + + return new TernaryExpression(left, trueExpr, falseExpr); + } + } +} diff --git a/ScriptConverter/Parser/Parselets/IDeclarationParselet.cs b/ScriptConverter/Parser/Parselets/IDeclarationParselet.cs new file mode 100644 index 0000000..130fbd8 --- /dev/null +++ b/ScriptConverter/Parser/Parselets/IDeclarationParselet.cs @@ -0,0 +1,9 @@ +using ScriptConverter.Ast.Declarations; + +namespace ScriptConverter.Parser.Parselets +{ + interface IDeclarationParselet + { + Declaration Parse(ScriptParser parser, ScriptToken token); + } +} diff --git a/ScriptConverter/Parser/Parselets/IInfixParselet.cs b/ScriptConverter/Parser/Parselets/IInfixParselet.cs new file mode 100644 index 0000000..937d9a0 --- /dev/null +++ b/ScriptConverter/Parser/Parselets/IInfixParselet.cs @@ -0,0 +1,11 @@ +using ScriptConverter.Ast.Expressions; + +namespace ScriptConverter.Parser.Parselets +{ + interface IInfixParselet + { + int Precedence { get; } + + Expression Parse(ScriptParser parser, Expression left, ScriptToken token); + } +} diff --git a/ScriptConverter/Parser/Parselets/IPrefixParselet.cs b/ScriptConverter/Parser/Parselets/IPrefixParselet.cs new file mode 100644 index 0000000..37895d6 --- /dev/null +++ b/ScriptConverter/Parser/Parselets/IPrefixParselet.cs @@ -0,0 +1,9 @@ +using ScriptConverter.Ast.Expressions; + +namespace ScriptConverter.Parser.Parselets +{ + interface IPrefixParselet + { + Expression Parse(ScriptParser parser, ScriptToken token); + } +} diff --git a/ScriptConverter/Parser/Parselets/IStatementParselet.cs b/ScriptConverter/Parser/Parselets/IStatementParselet.cs new file mode 100644 index 0000000..1c59905 --- /dev/null +++ b/ScriptConverter/Parser/Parselets/IStatementParselet.cs @@ -0,0 +1,9 @@ +using ScriptConverter.Ast.Statements; + +namespace ScriptConverter.Parser.Parselets +{ + interface IStatementParselet + { + Statement Parse(ScriptParser parser, ScriptToken token, out bool trailingSemicolon); + } +} diff --git a/ScriptConverter/Parser/Parselets/Statements/BlockParselet.cs b/ScriptConverter/Parser/Parselets/Statements/BlockParselet.cs new file mode 100644 index 0000000..5f6e21b --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Statements/BlockParselet.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using ScriptConverter.Ast.Statements; + +namespace ScriptConverter.Parser.Parselets.Statements +{ + class BlockParselet : IStatementParselet + { + public Statement Parse(ScriptParser parser, ScriptToken token, out bool trailingSemicolon) + { + trailingSemicolon = false; + + var statements = new List(); + + while (!parser.Match(ScriptTokenType.RightBrace)) + { + statements.Add(parser.ParseStatement()); + } + + parser.Take(ScriptTokenType.RightBrace); + + return new BlockStatement(token, parser.Previous, statements); + } + } +} diff --git a/ScriptConverter/Parser/Parselets/Statements/BreakParselet.cs b/ScriptConverter/Parser/Parselets/Statements/BreakParselet.cs new file mode 100644 index 0000000..fe673a5 --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Statements/BreakParselet.cs @@ -0,0 +1,13 @@ +using ScriptConverter.Ast.Statements; + +namespace ScriptConverter.Parser.Parselets.Statements +{ + class BreakParselet : IStatementParselet + { + public Statement Parse(ScriptParser parser, ScriptToken token, out bool trailingSemicolon) + { + trailingSemicolon = true; + return new BreakStatement(token); + } + } +} diff --git a/ScriptConverter/Parser/Parselets/Statements/ConstVariableParselet.cs b/ScriptConverter/Parser/Parselets/Statements/ConstVariableParselet.cs new file mode 100644 index 0000000..5c050c2 --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Statements/ConstVariableParselet.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using ScriptConverter.Ast; +using ScriptConverter.Ast.Statements; + +namespace ScriptConverter.Parser.Parselets.Statements +{ + class ConstVariableParselet : IStatementParselet + { + public Statement Parse(ScriptParser parser, ScriptToken token, out bool trailingSemicolon) + { + trailingSemicolon = true; + + ScriptType type; + string name; + parser.ParseNamedType(out type, out name); + + parser.Take(ScriptTokenType.Assign); + + var value = parser.ParseExpression(); + + var definitions = new List() + { + new VariableStatement.Definition(type, name, value) + }; + + return new VariableStatement(token, parser.Previous, type, true, definitions); + } + } +} diff --git a/ScriptConverter/Parser/Parselets/Statements/ContinueParselet.cs b/ScriptConverter/Parser/Parselets/Statements/ContinueParselet.cs new file mode 100644 index 0000000..fd75b3f --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Statements/ContinueParselet.cs @@ -0,0 +1,13 @@ +using ScriptConverter.Ast.Statements; + +namespace ScriptConverter.Parser.Parselets.Statements +{ + class ContinueParselet : IStatementParselet + { + public Statement Parse(ScriptParser parser, ScriptToken token, out bool trailingSemicolon) + { + trailingSemicolon = true; + return new ContinueStatement(token); + } + } +} diff --git a/ScriptConverter/Parser/Parselets/Statements/DoParselet.cs b/ScriptConverter/Parser/Parselets/Statements/DoParselet.cs new file mode 100644 index 0000000..7e5c4a3 --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Statements/DoParselet.cs @@ -0,0 +1,23 @@ +using ScriptConverter.Ast.Statements; + +namespace ScriptConverter.Parser.Parselets.Statements +{ + class DoParselet : IStatementParselet + { + public Statement Parse(ScriptParser parser, ScriptToken token, out bool trailingSemicolon) + { + trailingSemicolon = true; + + var block = parser.ParseBlock(); + + parser.Take(ScriptTokenType.While); + parser.Take(ScriptTokenType.LeftParen); + + var condition = parser.ParseExpression(); + + parser.Take(ScriptTokenType.RightParen); + + return new DoStatement(token, parser.Previous, condition, block); + } + } +} diff --git a/ScriptConverter/Parser/Parselets/Statements/EmptyParselet.cs b/ScriptConverter/Parser/Parselets/Statements/EmptyParselet.cs new file mode 100644 index 0000000..3a49a85 --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Statements/EmptyParselet.cs @@ -0,0 +1,13 @@ +using ScriptConverter.Ast.Statements; + +namespace ScriptConverter.Parser.Parselets.Statements +{ + class EmptyParselet : IStatementParselet + { + public Statement Parse(ScriptParser parser, ScriptToken token, out bool trailingSemicolon) + { + trailingSemicolon = false; + return new EmptyStatement(token); + } + } +} diff --git a/ScriptConverter/Parser/Parselets/Statements/ForParselet.cs b/ScriptConverter/Parser/Parselets/Statements/ForParselet.cs new file mode 100644 index 0000000..eb67cda --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Statements/ForParselet.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using System.Linq; +using ScriptConverter.Ast.Expressions; +using ScriptConverter.Ast.Statements; + +namespace ScriptConverter.Parser.Parselets.Statements +{ + class ForParselet : IStatementParselet + { + public Statement Parse(ScriptParser parser, ScriptToken token, out bool trailingSemicolon) + { + trailingSemicolon = false; + + parser.Take(ScriptTokenType.LeftParen); + + List initializers = null; + if (!parser.Match(ScriptTokenType.Semicolon)) + { + initializers = parser.ParseSeparatedBy(ScriptTokenType.Comma, (_, first) => + { + var stmt = parser.ParseStatement(false); + + if (!(stmt is VariableStatement) && !(stmt is NakedStatement)) + throw new CompilerException(token, "bad for loop initializer"); + + return stmt; + }).ToList(); + } + + parser.Take(ScriptTokenType.Semicolon); + + Expression condition = null; + if (!parser.Match(ScriptTokenType.Semicolon)) + condition = parser.ParseExpression(); + + parser.Take(ScriptTokenType.Semicolon); + + List increment = null; + if (!parser.Match(ScriptTokenType.RightParen)) + increment = parser.ParseSeparatedBy(ScriptTokenType.Comma, (_, first) => parser.ParseExpression()).ToList(); + + parser.Take(ScriptTokenType.RightParen); + + var block = parser.ParseBlock(); + + return new ForStatement(token, parser.Previous, initializers, condition, increment, block); + } + } +} diff --git a/ScriptConverter/Parser/Parselets/Statements/IfParselet.cs b/ScriptConverter/Parser/Parselets/Statements/IfParselet.cs new file mode 100644 index 0000000..80c99a0 --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Statements/IfParselet.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using ScriptConverter.Ast.Expressions; +using ScriptConverter.Ast.Statements; + +namespace ScriptConverter.Parser.Parselets.Statements +{ + class IfParselet : IStatementParselet + { + public Statement Parse(ScriptParser parser, ScriptToken token, out bool trailingSemicolon) + { + trailingSemicolon = false; + + var first = true; + var branches = new List(); + IfStatement.Branch defaultBranch = null; + + do + { + var isDefaultBranch = !first && !parser.MatchAndTake(ScriptTokenType.If); + first = false; + + Expression condition = null; + if (!isDefaultBranch) + { + parser.Take(ScriptTokenType.LeftParen); + + condition = parser.ParseExpression(); + + parser.Take(ScriptTokenType.RightParen); + } + + var block = parser.ParseBlock(); + var branch = new IfStatement.Branch(condition, block); + + if (isDefaultBranch) + defaultBranch = branch; + else + branches.Add(branch); + + if (isDefaultBranch) + break; + } while (parser.MatchAndTake(ScriptTokenType.Else)); + + return new IfStatement(token, parser.Previous, branches, defaultBranch); + } + } +} diff --git a/ScriptConverter/Parser/Parselets/Statements/ReturnParselet.cs b/ScriptConverter/Parser/Parselets/Statements/ReturnParselet.cs new file mode 100644 index 0000000..67fc120 --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Statements/ReturnParselet.cs @@ -0,0 +1,18 @@ +using ScriptConverter.Ast.Statements; + +namespace ScriptConverter.Parser.Parselets.Statements +{ + class ReturnParselet : IStatementParselet + { + public Statement Parse(ScriptParser parser, ScriptToken token, out bool trailingSemicolon) + { + trailingSemicolon = true; + + if (parser.Match(ScriptTokenType.Semicolon)) + return new ReturnStatement(token, parser.Previous, null); + + var value = parser.ParseExpression(); + return new ReturnStatement(token, parser.Previous, value); + } + } +} diff --git a/ScriptConverter/Parser/Parselets/Statements/SwitchParselet.cs b/ScriptConverter/Parser/Parselets/Statements/SwitchParselet.cs new file mode 100644 index 0000000..edfa777 --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Statements/SwitchParselet.cs @@ -0,0 +1,84 @@ +using System.Collections.Generic; +using ScriptConverter.Ast.Expressions; +using ScriptConverter.Ast.Statements; + +namespace ScriptConverter.Parser.Parselets.Statements +{ + class SwitchParselet : IStatementParselet + { + public Statement Parse(ScriptParser parser, ScriptToken token, out bool trailingSemicolon) + { + trailingSemicolon = false; + + parser.Take(ScriptTokenType.LeftParen); + + var expression = parser.ParseExpression(); + + parser.Take(ScriptTokenType.RightParen); + parser.Take(ScriptTokenType.LeftBrace); + + var hasDefault = false; + var branches = new List(); + + while (!parser.Match(ScriptTokenType.RightBrace)) + { + var conditions = new List(); + + while (true) + { + if (parser.MatchAndTake(ScriptTokenType.Case)) + { + var condition = parser.ParseExpression(); + conditions.Add(condition); + + parser.Take(ScriptTokenType.Colon); + continue; + } + + if (!parser.Match(ScriptTokenType.Default)) + break; + + var defaultToken = parser.Take(ScriptTokenType.Default); + + if (hasDefault) + throw new CompilerException(defaultToken, "Multiple default labels"); + + conditions.Add(null); // special default condition + hasDefault = true; + + parser.Take(ScriptTokenType.Colon); + } + + if (conditions.Count > 0) + { + var block = ParseBlock(parser); + var branch = new SwitchStatement.Branch(conditions, block); + branches.Add(branch); + continue; + } + + var errorToken = parser.Peek(); + throw new CompilerException(errorToken, "Expected Case or Default but found {0}", errorToken); + } + + parser.Take(ScriptTokenType.RightBrace); + + return new SwitchStatement(token, parser.Previous, expression, branches); + } + + private static BlockStatement ParseBlock(ScriptParser parser) + { + var statements = new List(); + var start = parser.Peek(); + + while (!parser.Match(ScriptTokenType.Case) && + !parser.Match(ScriptTokenType.Default) && + !parser.Match(ScriptTokenType.RightBrace)) + { + statements.Add(parser.ParseStatement()); + } + + return new BlockStatement(start, parser.Previous, statements, true); + } + } +} diff --git a/ScriptConverter/Parser/Parselets/Statements/ThrowParselet.cs b/ScriptConverter/Parser/Parselets/Statements/ThrowParselet.cs new file mode 100644 index 0000000..3d68e4c --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Statements/ThrowParselet.cs @@ -0,0 +1,15 @@ +using ScriptConverter.Ast.Statements; + +namespace ScriptConverter.Parser.Parselets.Statements +{ + class ThrowParselet : IStatementParselet + { + public Statement Parse(ScriptParser parser, ScriptToken token, out bool trailingSemicolon) + { + trailingSemicolon = true; + + var value = parser.ParseExpression(); + return new ThrowStatement(token, value); + } + } +} diff --git a/ScriptConverter/Parser/Parselets/Statements/TryParselet.cs b/ScriptConverter/Parser/Parselets/Statements/TryParselet.cs new file mode 100644 index 0000000..0e8f116 --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Statements/TryParselet.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using ScriptConverter.Ast; +using ScriptConverter.Ast.Statements; + +namespace ScriptConverter.Parser.Parselets.Statements +{ + class TryParselet : IStatementParselet + { + public Statement Parse(ScriptParser parser, ScriptToken token, out bool trailingSemicolon) + { + trailingSemicolon = false; + + var mainBlock = parser.ParseBlock(false); + var catches = new List(2); + + do + { + parser.Take(ScriptTokenType.Catch); + parser.Take(ScriptTokenType.LeftParen); + + ScriptType type; + string name; + parser.ParseNamedType(out type, out name); + + parser.Take(ScriptTokenType.RightParen); + + var block = parser.ParseBlock(false); + catches.Add(new TryStatement.Branch(type, name, block)); + } while (parser.Match(ScriptTokenType.Catch)); + + return new TryStatement(token, parser.Previous, mainBlock, catches); + } + } +} diff --git a/ScriptConverter/Parser/Parselets/Statements/VariableParselet.cs b/ScriptConverter/Parser/Parselets/Statements/VariableParselet.cs new file mode 100644 index 0000000..7d2bab0 --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Statements/VariableParselet.cs @@ -0,0 +1,42 @@ +using System.Linq; +using ScriptConverter.Ast; +using ScriptConverter.Ast.Expressions; +using ScriptConverter.Ast.Statements; + +namespace ScriptConverter.Parser.Parselets.Statements +{ + class VariableParselet : IStatementParselet + { + public Statement Parse(ScriptParser parser, ScriptToken token, out bool trailingSemicolon) + { + trailingSemicolon = true; + + var baseType = parser.ParseType(token); + + var definitions = parser.ParseSeparatedBy(ScriptTokenType.Comma, (_, first) => + { + var name = parser.Take(ScriptTokenType.Identifier).Contents; + + var arrayDims = 0; + while (parser.MatchAndTake(ScriptTokenType.LeftSquare)) + { + parser.Take(ScriptTokenType.RightSquare); + arrayDims++; + } + + var type = baseType; + + if (arrayDims > 0) + type = new ScriptType(baseType.Name, baseType.ArrayDimensions + arrayDims, baseType.IsResizable); + + Expression value = null; + if (parser.MatchAndTake(ScriptTokenType.Assign)) + value = parser.ParseExpression(); + + return new VariableStatement.Definition(type, name, value); + }).ToList(); + + return new VariableStatement(token, parser.Previous, baseType, false, definitions); + } + } +} diff --git a/ScriptConverter/Parser/Parselets/Statements/WhileParselet.cs b/ScriptConverter/Parser/Parselets/Statements/WhileParselet.cs new file mode 100644 index 0000000..d35eb3f --- /dev/null +++ b/ScriptConverter/Parser/Parselets/Statements/WhileParselet.cs @@ -0,0 +1,20 @@ +using ScriptConverter.Ast.Statements; + +namespace ScriptConverter.Parser.Parselets.Statements +{ + class WhileParselet : IStatementParselet + { + public Statement Parse(ScriptParser parser, ScriptToken token, out bool trailingSemicolon) + { + trailingSemicolon = false; + + parser.Take(ScriptTokenType.LeftParen); + var condition = parser.ParseExpression(); + parser.Take(ScriptTokenType.RightParen); + + var block = parser.ParseBlock(); + + return new WhileStatement(token, parser.Previous, condition, block); + } + } +} diff --git a/ScriptConverter/Parser/Parser.cs b/ScriptConverter/Parser/Parser.cs new file mode 100644 index 0000000..1da8276 --- /dev/null +++ b/ScriptConverter/Parser/Parser.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; + +namespace ScriptConverter.Parser +{ + abstract class Parser + { + private IEnumerator _tokens; + private List _read; + + protected Parser(IEnumerable tokens) + { + _tokens = tokens.GetEnumerator(); + _read = new List(8); + } + + /// + /// Returns the token that was most recently taken. + /// + public ScriptToken Previous { get; private set; } + + public IEnumerable ParseSeparatedBy(ScriptTokenType separator, Func parseFunc) where T : class + { + var first = parseFunc(this, true); + + if (first == null) + yield break; + + yield return first; + + while (MatchAndTake(separator)) + { + var next = parseFunc(this, false); + + if (next == null) + yield break; + + yield return next; + } + } + + /// + /// Check if the next token matches the given type. If they match, take the token. + /// + public bool MatchAndTake(ScriptTokenType type) + { + var isMatch = Match(type); + if (isMatch) + Take(); + + return isMatch; + } + + /// + /// Check if the next token matches the given type. + /// + public bool Match(ScriptTokenType type, int distance = 0) + { + return Peek(distance).Type == type; + } + + /// + /// Take a token from the stream. Throws an exception if the given type does not match the token type. + /// + public ScriptToken Take(ScriptTokenType type) + { + var token = Take(); + + if (token.Type != type) + throw new CompilerException(token, CompilerError.ExpectedButFound, type, token); + + return token; + } + + /// + /// Take a token from the stream. + /// + public ScriptToken Take() + { + Peek(); + + var result = _read[0]; + _read.RemoveAt(0); + + Previous = result; + + return result; + } + + /// + /// Peek at future tokens in the stream. Distance is the number of tokens from the current one. + /// + public ScriptToken Peek(int distance = 0) + { + if (distance < 0) + throw new ArgumentOutOfRangeException("distance", "distance can't be negative"); + + while (_read.Count <= distance) + { + _tokens.MoveNext(); + _read.Add(_tokens.Current); + + //Console.WriteLine(_tokens.Current.Type); + } + + return _read[distance]; + } + } +} diff --git a/ScriptConverter/Parser/PrecedenceValue.cs b/ScriptConverter/Parser/PrecedenceValue.cs new file mode 100644 index 0000000..fff14f1 --- /dev/null +++ b/ScriptConverter/Parser/PrecedenceValue.cs @@ -0,0 +1,22 @@ +namespace ScriptConverter.Parser +{ + enum PrecedenceValue + { + Invalid, + Assignment, + Ternary, + LogicalOr, + LogicalAnd, + BitwiseOr, + BitwiseXor, + BitwiseAnd, + Equality, + Relational, + BitwiseShift, + Additive, + Multiplicative, + Cast, + Prefix, + Suffix + } +} diff --git a/ScriptConverter/Parser/ScriptLexer.Static.cs b/ScriptConverter/Parser/ScriptLexer.Static.cs new file mode 100644 index 0000000..405cbc6 --- /dev/null +++ b/ScriptConverter/Parser/ScriptLexer.Static.cs @@ -0,0 +1,105 @@ +using System.Collections.Generic; + +namespace ScriptConverter.Parser +{ + partial class ScriptLexer + { + private static OperatorDictionary _operators; + private static Dictionary _keywords; + + static ScriptLexer() + { + EofToken = new ScriptToken(null, default(SourcePosition), default(SourcePosition), ScriptTokenType.Eof, null); + + _operators = new OperatorDictionary + { + { ";", ScriptTokenType.Semicolon }, + { ",", ScriptTokenType.Comma }, + { ".", ScriptTokenType.Dot }, + { "=", ScriptTokenType.Assign }, + { "?", ScriptTokenType.QuestionMark }, + { ":", ScriptTokenType.Colon }, + + { "(", ScriptTokenType.LeftParen }, + { ")", ScriptTokenType.RightParen }, + + { "{", ScriptTokenType.LeftBrace }, + { "}", ScriptTokenType.RightBrace }, + + { "[", ScriptTokenType.LeftSquare }, + { "]", ScriptTokenType.RightSquare }, + + { "+", ScriptTokenType.Add }, + { "+=", ScriptTokenType.AddAssign }, + { "-", ScriptTokenType.Subtract }, + { "-=", ScriptTokenType.SubtractAssign }, + { "*", ScriptTokenType.Multiply }, + { "*=", ScriptTokenType.MultiplyAssign }, + { "/", ScriptTokenType.Divide }, + { "/=", ScriptTokenType.DivideAssign }, + { "%", ScriptTokenType.Remainder }, + { "%=", ScriptTokenType.RemainderAssign }, + { "++", ScriptTokenType.Increment }, + { "--", ScriptTokenType.Decrement }, + + { "==", ScriptTokenType.EqualTo }, + { "!=", ScriptTokenType.NotEqualTo }, + { ">", ScriptTokenType.GreaterThan }, + { ">=", ScriptTokenType.GreaterThanOrEqual }, + { "<", ScriptTokenType.LessThan }, + { "<=", ScriptTokenType.LessThanOrEqual }, + + { "&&", ScriptTokenType.LogicalAnd }, + { "||", ScriptTokenType.LogicalOr }, + { "!", ScriptTokenType.LogicalNot }, + + { "~", ScriptTokenType.BitwiseNot }, + { "&", ScriptTokenType.BitwiseAnd }, + { "&=", ScriptTokenType.BitwiseAndAssign }, + { "|", ScriptTokenType.BitwiseOr }, + { "|=", ScriptTokenType.BitwiseOrAssign }, + { "^", ScriptTokenType.BitwiseXor }, + { "^=", ScriptTokenType.BitwiseXorAssign }, + { "<<", ScriptTokenType.BitwiseShiftLeft }, + { "<<=", ScriptTokenType.BitwiseShiftLeftAssign }, + { ">>", ScriptTokenType.BitwiseShiftRight }, + { ">>=", ScriptTokenType.BitwiseShiftRightAssign }, + + { "##", ScriptTokenType.Hash }, + + { "#define", ScriptTokenType.PreprocessDefine }, + { "#ifdef", ScriptTokenType.PreprocessIfDef }, + { "#endif", ScriptTokenType.PreprocessEndIf }, + }; + + _keywords = new Dictionary + { + { "null", ScriptTokenType.Null }, + { "true", ScriptTokenType.True }, + { "false", ScriptTokenType.False }, + + { "return", ScriptTokenType.Return }, + { "if", ScriptTokenType.If }, + { "else", ScriptTokenType.Else }, + { "for", ScriptTokenType.For }, + { "while", ScriptTokenType.While }, + { "do", ScriptTokenType.Do }, + { "break", ScriptTokenType.Break }, + { "continue", ScriptTokenType.Continue }, + { "switch", ScriptTokenType.Switch }, + { "case", ScriptTokenType.Case }, + { "default", ScriptTokenType.Default }, + + { "inherits", ScriptTokenType.Inherits }, + { "include", ScriptTokenType.Include }, + { "const", ScriptTokenType.Const }, + { "public", ScriptTokenType.Public }, + { "new", ScriptTokenType.New }, + { "instanceof", ScriptTokenType.InstanceOf }, + { "try", ScriptTokenType.Try }, + { "catch", ScriptTokenType.Catch }, + { "throw", ScriptTokenType.Throw }, + }; + } + } +} diff --git a/ScriptConverter/Parser/ScriptLexer.cs b/ScriptConverter/Parser/ScriptLexer.cs new file mode 100644 index 0000000..66bb87b --- /dev/null +++ b/ScriptConverter/Parser/ScriptLexer.cs @@ -0,0 +1,254 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace ScriptConverter.Parser +{ + partial class ScriptLexer : Lexer + { + public ScriptLexer(IEnumerable source, string fileName = null) + : base(source, fileName) + { + Rules = new List + { + TryLexNumber, + TryLexOperator, + TryLexString, + TryLexWord, + }; + } + + protected override bool SkipComment() + { + if (TakeIfNext("//")) + { + while (true) + { + var ch = TakeChar(); + if (ch == '\0' || ch == '\n' || ch == '\r') + return true; + } + } + + if (TakeIfNext("/*")) + { + while (true) + { + if (TakeIfNext("*/")) + return true; + + var ch = TakeChar(); + if (ch == '\0') + return true; + } + } + + return false; + } + + private bool TryLexOperator(char ch, out ScriptToken token) + { + var opList = _operators.Lookup(ch); + if (opList != null) + { + var op = opList.FirstOrDefault(o => TakeIfNext(o.Item1)); + + if (op != null) + { + token = Token(op.Item2, op.Item1); + return true; + } + } + + token = null; + return false; + } + + private bool TryLexString(char ch, out ScriptToken token) + { + if (ch == '"' || ch == '\'') + { + TakeChar(); + + var stringTerminator = ch; + var stringContentsBuilder = new StringBuilder(); + + while (true) + { + if (AtEof) + throw ErrorStart(CompilerError.UnterminatedString); + + ch = TakeChar(); + + if (ch == stringTerminator) + break; + + if (ch != '\\') + { + switch (ch) + { + case '\n': + stringContentsBuilder.Append("\\n"); + continue; + case '\r': + stringContentsBuilder.Append("\\r"); + continue; + } + + if (ch < ' ' || ch > '~') + { + stringContentsBuilder.AppendFormat("\\u{0:X4}", (int)ch); + continue; + } + + stringContentsBuilder.Append(ch); + continue; + } + + // escape sequence + ch = TakeChar(); + + if (AtEof) + throw Error(CompilerError.UnexpectedEofString); + + switch (ch) + { + case '\\': + stringContentsBuilder.Append("\\\\"); + break; + + case '"': + stringContentsBuilder.Append("\\\""); + break; + + case '\'': + stringContentsBuilder.Append("\\'"); + break; + + case 'r': + stringContentsBuilder.Append("\\r"); + break; + + case 'n': + stringContentsBuilder.Append("\\n"); + break; + + case 't': + stringContentsBuilder.Append("\\t"); + break; + + case '0': + stringContentsBuilder.Append("\\0"); + break; + + // TODO: more escape sequences + + default: + throw Error(CompilerError.InvalidEscapeSequence, ch); + } + } + + var stringContents = stringContentsBuilder.ToString(); + + if (stringTerminator == '\'') + { + token = Token(ScriptTokenType.SingleString, stringContents); + return true; + } + + token = Token(ScriptTokenType.String, stringContents); + return true; + } + + token = null; + return false; + } + + private bool TryLexWord(char ch, out ScriptToken token) + { + if (char.IsLetter(ch) || ch == '_') + { + var wordContents = TakeWhile(c => char.IsLetterOrDigit(c) || c == '_'); + + ScriptTokenType keywordType; + var isKeyword = _keywords.TryGetValue(wordContents, out keywordType); + + // HACK: allow using enum as an identifier + if (!isKeyword && wordContents == "enum") + wordContents = "enum_"; + + token = Token(isKeyword ? keywordType : ScriptTokenType.Identifier, wordContents); + return true; + } + + token = null; + return false; + } + + private bool TryLexNumber(char ch, out ScriptToken token) + { + if (char.IsDigit(ch) || (ch == '.' && char.IsDigit(PeekChar(1)))) + { + var format = NumberFormat.Decimal; + var hasDecimal = false; + var prefix = ""; + + if (ch == '0') + { + var nextChar = PeekChar(1); + + if (nextChar == 'x' || nextChar == 'X') + format = NumberFormat.Hexadecimal; + + if (nextChar == 'b' || nextChar == 'B') + format = NumberFormat.Binary; + + if (format != NumberFormat.Decimal) + { + prefix = "" + TakeChar() + TakeChar(); + } + } + + if (ch == '.') + { + hasDecimal = true; + prefix = "" + TakeChar(); + } + + Func isHexLetter = c => (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); + Func isDigit = c => char.IsDigit(c) || (format == NumberFormat.Hexadecimal && isHexLetter(c)); + + var numberContents = TakeWhile(c => + { + if (format == NumberFormat.Decimal && c == '.' && isDigit(PeekChar(1)) && !hasDecimal) + { + hasDecimal = true; + return true; + } + + if (format == NumberFormat.Decimal && (c == 'f' || c == 'd' || c == 'L') && !isDigit(PeekChar(1))) + return true; + + return isDigit(c); + }); + + token = Token(ScriptTokenType.Number, prefix + numberContents); + return true; + } + + token = null; + return false; + } + + enum NumberFormat + { + Decimal, Hexadecimal, Binary + } + + private ScriptToken Token(ScriptTokenType type, string contents) + { + return new ScriptToken(FileName, new SourcePosition(StartLine, StartColumn), new SourcePosition(Line, Column - 1), type, contents); + } + } +} diff --git a/ScriptConverter/Parser/ScriptParser.Static.cs b/ScriptConverter/Parser/ScriptParser.Static.cs new file mode 100644 index 0000000..b3628a2 --- /dev/null +++ b/ScriptConverter/Parser/ScriptParser.Static.cs @@ -0,0 +1,122 @@ +using System.Collections.Generic; +using ScriptConverter.Parser.Parselets; +using ScriptConverter.Parser.Parselets.Declarations; +using ScriptConverter.Parser.Parselets.Expressions; +using ScriptConverter.Parser.Parselets.Statements; + +namespace ScriptConverter.Parser +{ + partial class ScriptParser + { + private static Dictionary _declarationParselets; + private static Dictionary _statementParselets; + private static Dictionary _prefixParselets; + private static Dictionary _infixParselets; + + static ScriptParser() + { + _declarationParselets = new Dictionary(); + _statementParselets = new Dictionary(); + _prefixParselets = new Dictionary(); + _infixParselets = new Dictionary(); + + // declarations + RegisterDeclaration(ScriptTokenType.Const, new ConstParselet()); + RegisterDeclaration(ScriptTokenType.Include, new IncludeParselet()); + RegisterDeclaration(ScriptTokenType.Inherits, new InheritsParselet()); + RegisterDeclaration(ScriptTokenType.Identifier, new MethodParselet()); + RegisterDeclaration(ScriptTokenType.Public, new PublicParselet()); + + // statements + RegisterStatement(ScriptTokenType.LeftBrace, new BlockParselet()); + RegisterStatement(ScriptTokenType.Break, new BreakParselet()); + RegisterStatement(ScriptTokenType.Const, new ConstVariableParselet()); + RegisterStatement(ScriptTokenType.Continue, new ContinueParselet()); + RegisterStatement(ScriptTokenType.Do, new DoParselet()); + RegisterStatement(ScriptTokenType.Semicolon, new EmptyParselet()); + RegisterStatement(ScriptTokenType.For, new ForParselet()); + RegisterStatement(ScriptTokenType.If, new IfParselet()); + RegisterStatement(ScriptTokenType.Return, new ReturnParselet()); + RegisterStatement(ScriptTokenType.Switch, new SwitchParselet()); + RegisterStatement(ScriptTokenType.Throw, new ThrowParselet()); + RegisterStatement(ScriptTokenType.Try, new TryParselet()); + RegisterStatement(ScriptTokenType.Identifier, new VariableParselet()); + RegisterStatement(ScriptTokenType.While, new WhileParselet()); + + // expressions + RegisterPrefix(ScriptTokenType.LeftBrace, new ArrayInitializerParselet()); + RegisterInfix(ScriptTokenType.Add, new BinaryOperatorParselet(PrecedenceValue.Additive, false)); + RegisterInfix(ScriptTokenType.Subtract, new BinaryOperatorParselet(PrecedenceValue.Additive, false)); + RegisterInfix(ScriptTokenType.Multiply, new BinaryOperatorParselet(PrecedenceValue.Multiplicative, false)); + RegisterInfix(ScriptTokenType.Divide, new BinaryOperatorParselet(PrecedenceValue.Multiplicative, false)); + RegisterInfix(ScriptTokenType.Remainder, new BinaryOperatorParselet(PrecedenceValue.Multiplicative, false)); + RegisterInfix(ScriptTokenType.BitwiseShiftLeft, new BinaryOperatorParselet(PrecedenceValue.BitwiseShift, false)); + RegisterInfix(ScriptTokenType.BitwiseShiftRight, new BinaryOperatorParselet(PrecedenceValue.BitwiseShift, false)); + RegisterInfix(ScriptTokenType.BitwiseAnd, new BinaryOperatorParselet(PrecedenceValue.BitwiseAnd, false)); + RegisterInfix(ScriptTokenType.BitwiseOr, new BinaryOperatorParselet(PrecedenceValue.BitwiseOr, false)); + RegisterInfix(ScriptTokenType.BitwiseXor, new BinaryOperatorParselet(PrecedenceValue.BitwiseXor, false)); + RegisterInfix(ScriptTokenType.LogicalAnd, new BinaryOperatorParselet(PrecedenceValue.LogicalAnd, false)); + RegisterInfix(ScriptTokenType.LogicalOr, new BinaryOperatorParselet(PrecedenceValue.LogicalOr, false)); + RegisterInfix(ScriptTokenType.EqualTo, new BinaryOperatorParselet(PrecedenceValue.Equality, false)); + RegisterInfix(ScriptTokenType.NotEqualTo, new BinaryOperatorParselet(PrecedenceValue.Equality, false)); + RegisterInfix(ScriptTokenType.GreaterThan, new BinaryOperatorParselet(PrecedenceValue.Relational, false)); + RegisterInfix(ScriptTokenType.GreaterThanOrEqual, new BinaryOperatorParselet(PrecedenceValue.Relational, false)); + RegisterInfix(ScriptTokenType.LessThan, new BinaryOperatorParselet(PrecedenceValue.Relational, false)); + RegisterInfix(ScriptTokenType.LessThanOrEqual, new BinaryOperatorParselet(PrecedenceValue.Relational, false)); + RegisterInfix(ScriptTokenType.Assign, new BinaryOperatorParselet(PrecedenceValue.Assignment, true)); + RegisterInfix(ScriptTokenType.AddAssign, new BinaryOperatorParselet(PrecedenceValue.Assignment, true)); + RegisterInfix(ScriptTokenType.SubtractAssign, new BinaryOperatorParselet(PrecedenceValue.Assignment, true)); + RegisterInfix(ScriptTokenType.MultiplyAssign, new BinaryOperatorParselet(PrecedenceValue.Assignment, true)); + RegisterInfix(ScriptTokenType.DivideAssign, new BinaryOperatorParselet(PrecedenceValue.Assignment, true)); + RegisterInfix(ScriptTokenType.RemainderAssign, new BinaryOperatorParselet(PrecedenceValue.Assignment, true)); + RegisterInfix(ScriptTokenType.BitwiseShiftLeftAssign, new BinaryOperatorParselet(PrecedenceValue.Assignment, true)); + RegisterInfix(ScriptTokenType.BitwiseShiftRightAssign, new BinaryOperatorParselet(PrecedenceValue.Assignment, true)); + RegisterInfix(ScriptTokenType.BitwiseAndAssign, new BinaryOperatorParselet(PrecedenceValue.Assignment, true)); + RegisterInfix(ScriptTokenType.BitwiseOrAssign, new BinaryOperatorParselet(PrecedenceValue.Assignment, true)); + RegisterInfix(ScriptTokenType.BitwiseXorAssign, new BinaryOperatorParselet(PrecedenceValue.Assignment, true)); + RegisterPrefix(ScriptTokenType.True, new BoolParselet(true)); + RegisterPrefix(ScriptTokenType.False, new BoolParselet(false)); + RegisterInfix(ScriptTokenType.LeftParen, new CallParselet()); + RegisterInfix(ScriptTokenType.Dot, new FieldParselet()); + RegisterPrefix(ScriptTokenType.LeftParen, new GroupParselet()); + RegisterPrefix(ScriptTokenType.Hash, new HashParselet()); + RegisterPrefix(ScriptTokenType.Identifier, new IdentifierParselet()); + RegisterInfix(ScriptTokenType.LeftSquare, new IndexerParselet()); + RegisterInfix(ScriptTokenType.InstanceOf, new InstanceOfParselet()); + RegisterPrefix(ScriptTokenType.New, new NewParselet()); + RegisterPrefix(ScriptTokenType.Null, new NullParselet()); + RegisterPrefix(ScriptTokenType.Number, new NumberParselet()); + RegisterInfix(ScriptTokenType.Increment, new PostfixOperatorParselet(PrecedenceValue.Suffix)); + RegisterInfix(ScriptTokenType.Decrement, new PostfixOperatorParselet(PrecedenceValue.Suffix)); + RegisterPrefix(ScriptTokenType.Add, new PrefixOperatorParselet(PrecedenceValue.Prefix)); + RegisterPrefix(ScriptTokenType.Subtract, new PrefixOperatorParselet(PrecedenceValue.Prefix)); + RegisterPrefix(ScriptTokenType.BitwiseNot, new PrefixOperatorParselet(PrecedenceValue.Prefix)); + RegisterPrefix(ScriptTokenType.LogicalNot, new PrefixOperatorParselet(PrecedenceValue.Prefix)); + RegisterPrefix(ScriptTokenType.Increment, new PrefixOperatorParselet(PrecedenceValue.Prefix)); + RegisterPrefix(ScriptTokenType.Decrement, new PrefixOperatorParselet(PrecedenceValue.Prefix)); + RegisterPrefix(ScriptTokenType.String, new StringParselet(false)); + RegisterPrefix(ScriptTokenType.SingleString, new StringParselet(true)); + RegisterInfix(ScriptTokenType.QuestionMark, new TernaryParselet()); + } + + static void RegisterDeclaration(ScriptTokenType type, IDeclarationParselet parselet) + { + _declarationParselets.Add(type, parselet); + } + + static void RegisterStatement(ScriptTokenType type, IStatementParselet parselet) + { + _statementParselets.Add(type, parselet); + } + + static void RegisterPrefix(ScriptTokenType type, IPrefixParselet parselet) + { + _prefixParselets.Add(type, parselet); + } + + static void RegisterInfix(ScriptTokenType type, IInfixParselet parselet) + { + _infixParselets.Add(type, parselet); + } + } +} diff --git a/ScriptConverter/Parser/ScriptParser.cs b/ScriptConverter/Parser/ScriptParser.cs new file mode 100644 index 0000000..c74493d --- /dev/null +++ b/ScriptConverter/Parser/ScriptParser.cs @@ -0,0 +1,237 @@ +using System; +using System.Collections.Generic; +using ScriptConverter.Ast; +using ScriptConverter.Ast.Declarations; +using ScriptConverter.Ast.Expressions; +using ScriptConverter.Ast.Statements; +using ScriptConverter.Parser.Parselets; + +namespace ScriptConverter.Parser +{ + partial class ScriptParser : Parser + { + public ScriptParser(IEnumerable tokens) + : base(tokens) + { + + } + + public void ParseNamedType(out ScriptType type, out string name) + { + ParseNamedType(Take(), out type, out name); + } + + public void ParseNamedType(ScriptToken token, out ScriptType type, out string name) + { + type = ParseType(token); + name = Take(ScriptTokenType.Identifier).Contents; + + var arrayDims = 0; + while (MatchAndTake(ScriptTokenType.LeftSquare)) + { + Take(ScriptTokenType.RightSquare); + arrayDims++; + } + + if (arrayDims == 0) + return; + + type = new ScriptType(type.Name, type.ArrayDimensions + arrayDims, type.IsResizable); + } + + public ScriptType ParseType() + { + return ParseType(Take()); + } + + public ScriptType ParseType(ScriptToken token) + { + if (token.Type != ScriptTokenType.Identifier) + throw new CompilerException(token, CompilerError.ExpectedButFound, ScriptTokenType.Identifier, token); + + var isResizable = false; + var arrayDims = 0; + + if (token.Contents == "resizeable") // yes + { + isResizable = true; + token = Take(ScriptTokenType.Identifier); + } + + var typeName = token.Contents; + + if (Match(ScriptTokenType.Dot)) + { + typeName += "." + string.Join(".", ParseSeparatedBy(ScriptTokenType.Dot, (_, first) => + { + if (first) + Take(ScriptTokenType.Dot); + + return Take(ScriptTokenType.Identifier).Contents; + })); + } + + if (isResizable) + { + Take(ScriptTokenType.LeftSquare); + Take(ScriptTokenType.RightSquare); + } + else + { + while (MatchAndTake(ScriptTokenType.LeftSquare)) + { + Take(ScriptTokenType.RightSquare); + arrayDims++; + } + } + + return new ScriptType(typeName, arrayDims, isResizable); + } + + /// + /// Parse an expression into an expression tree. You can think of expressions as sub-statements. + /// + public Expression ParseExpression(int precendence = 0) + { + var token = Take(); + + IPrefixParselet prefixParselet; + _prefixParselets.TryGetValue(token.Type, out prefixParselet); + + if (prefixParselet == null) + throw new CompilerException(token, CompilerError.ExpectedButFound, "Expression", token); + + var left = prefixParselet.Parse(this, token); + + while (GetPrecedence() > precendence) + { + token = Take(); + + IInfixParselet infixParselet; + _infixParselets.TryGetValue(token.Type, out infixParselet); + + if (infixParselet == null) + throw new Exception("probably can't happen"); + + left = infixParselet.Parse(this, left, token); + } + + return left; + } + + /// + /// Parse a statement into an expression tree. + /// + public Statement ParseStatement(bool takeTrailingSemicolon = true) + { + var token = Peek(); + + IStatementParselet statementParselet; + _statementParselets.TryGetValue(token.Type, out statementParselet); + + // HACK: workaround for variables + if (token.Type == ScriptTokenType.Identifier && !IsVariableStatement()) + statementParselet = null; + + if (statementParselet == null) + { + var expr = ParseExpression(); + + if (takeTrailingSemicolon) + Take(ScriptTokenType.Semicolon); + + return new NakedStatement(expr); + } + + token = Take(); + + bool hasTrailingSemicolon; + var result = statementParselet.Parse(this, token, out hasTrailingSemicolon); + + if (takeTrailingSemicolon && hasTrailingSemicolon) + Take(ScriptTokenType.Semicolon); + + return result; + } + + /// + /// Parse a block of code into an expression tree. Blocks can either be a single statement or + /// multiple surrounded by braces. + /// + public BlockStatement ParseBlock(bool allowSingle = true) + { + ScriptToken start; + ScriptToken end; + var statements = new List(); + + if (allowSingle && !Match(ScriptTokenType.LeftBrace)) + { + start = Peek(); + + statements.Add(ParseStatement()); + + end = Previous; + + return new BlockStatement(start, end, statements); + } + + start = Take(ScriptTokenType.LeftBrace); + + while (!Match(ScriptTokenType.RightBrace)) + { + statements.Add(ParseStatement()); + } + + end = Take(ScriptTokenType.RightBrace); + + return new BlockStatement(start, end, statements); + } + + /// + /// Parses declarations until there are no more tokens available. + /// + public Document ParseAll() + { + var declarations = new List(); + + while (!Match(ScriptTokenType.Eof)) + { + var token = Take(); + + if (token.Type == ScriptTokenType.Semicolon) + continue; + + IDeclarationParselet declarationParselet; + if (!_declarationParselets.TryGetValue(token.Type, out declarationParselet)) + throw new CompilerException(token, "expected declaration"); // TODO + + declarations.Add(declarationParselet.Parse(this, token)); + } + + return new Document(declarations); + } + + private bool IsVariableStatement() + { + if (!Match(ScriptTokenType.Identifier)) + return false; + + var i = 1; + while (Match(ScriptTokenType.Dot, i) && Match(ScriptTokenType.Identifier, i + 1)) + { + i += 2; + } + + return Match(ScriptTokenType.Identifier, i) || + (Match(ScriptTokenType.LeftSquare, i) && Match(ScriptTokenType.RightSquare, i + 1)); + } + + private int GetPrecedence() + { + IInfixParselet infixParselet; + _infixParselets.TryGetValue(Peek().Type, out infixParselet); + + return infixParselet != null ? infixParselet.Precedence : 0; + } + } +} diff --git a/ScriptConverter/Parser/SourcePosition.cs b/ScriptConverter/Parser/SourcePosition.cs new file mode 100644 index 0000000..8a896a5 --- /dev/null +++ b/ScriptConverter/Parser/SourcePosition.cs @@ -0,0 +1,35 @@ +using System.Globalization; +using JetBrains.Annotations; + +namespace ScriptConverter.Parser +{ + public struct SourcePosition + { + public readonly int Line; + public readonly int Column; + + public SourcePosition(int line, int column = -1) + { + Line = line; + Column = column; + } + + [Pure] + public override string ToString() + { + if (Column <= 0) + return Line.ToString("G", CultureInfo.InvariantCulture); + + return string.Format(CultureInfo.InvariantCulture, "{0:G}:{1:G}", Line, Column); + } + + [Pure] + public string ToRangeString(SourcePosition end) + { + if (Line == end.Line && Column == end.Column) + return ToString(); + + return string.Format(CultureInfo.InvariantCulture, "{0}-{1}", this, end); + } + } +} diff --git a/ScriptConverter/Parser/Token.cs b/ScriptConverter/Parser/Token.cs new file mode 100644 index 0000000..c387cd1 --- /dev/null +++ b/ScriptConverter/Parser/Token.cs @@ -0,0 +1,172 @@ +namespace ScriptConverter.Parser +{ + abstract class Token + { + public readonly string FileName; + public readonly SourcePosition Start; + public readonly SourcePosition End; + + public readonly T Type; + public readonly string Contents; + + protected Token(string fileName, SourcePosition start, SourcePosition end, T type, string contents) + { + FileName = fileName; + Start = start; + End = end; + + Type = type; + Contents = contents; + } + + protected Token(Token token, T type, string contents) + : this(token.FileName, token.Start, token.End, type, contents) + { + + } + + protected Token(T type, string contents) + : this(null, new SourcePosition(-1), new SourcePosition(-1), type, contents) + { + + } + + public string RangeString + { + get { return Start.ToRangeString(End); } + } + } + + enum ScriptTokenType + { + Identifier, + + Number, + SingleString, + String, + + Null, + True, + False, + + Return, + If, + Else, + For, + While, + Do, + Break, + Continue, + Switch, + Case, + Default, + + Hash, + Inherits, + Include, + Const, + Public, + New, + InstanceOf, + Try, + Catch, + Throw, + + Semicolon, + Comma, + Dot, + Assign, + QuestionMark, + Colon, + + LeftParen, + RightParen, + + LeftBrace, + RightBrace, + + LeftSquare, + RightSquare, + + Add, + AddAssign, + Subtract, + SubtractAssign, + Multiply, + MultiplyAssign, + Divide, + DivideAssign, + Remainder, + RemainderAssign, + Increment, + Decrement, + + EqualTo, + NotEqualTo, + GreaterThan, + GreaterThanOrEqual, + LessThan, + LessThanOrEqual, + + LogicalAnd, + LogicalOr, + LogicalNot, + + BitwiseNot, + BitwiseAnd, + BitwiseAndAssign, + BitwiseOr, + BitwiseOrAssign, + BitwiseXor, + BitwiseXorAssign, + BitwiseShiftLeft, + BitwiseShiftLeftAssign, + BitwiseShiftRight, + BitwiseShiftRightAssign, + + PreprocessDefine, + PreprocessIfDef, + PreprocessEndIf, + + Eof + } + + class ScriptToken : Token + { + public ScriptToken(string fileName, SourcePosition start, SourcePosition end, ScriptTokenType type, string contents) + : base(fileName, start, end, type, contents) + { + + } + + public ScriptToken(Token token, ScriptTokenType type, string contents) + : base(token, type, contents) + { + + } + + public ScriptToken(ScriptTokenType type, string contents) + : base(type, contents) + { + + } + + public override string ToString() + { + switch (Type) + { + case ScriptTokenType.Identifier: + case ScriptTokenType.Number: + case ScriptTokenType.String: + var contentsStr = Contents; + if (contentsStr.Length > 16) + contentsStr = contentsStr.Substring(0, 13) + "..."; + + return string.Format("{0}('{1}')", Type, contentsStr); + + default: + return Type.ToString(); + } + } + } +} diff --git a/ScriptConverter/Program.cs b/ScriptConverter/Program.cs new file mode 100644 index 0000000..d12a115 --- /dev/null +++ b/ScriptConverter/Program.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using ScriptConverter.Ast; +using ScriptConverter.Parser; + +namespace ScriptConverter +{ + class Program + { + private static Dictionary _astCache = new Dictionary(); + private static string _basePath; + + static void Main(string[] args) + { + _basePath = Path.GetFullPath(".").TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar; + + /*var text = File.ReadAllText(@"E:\Desktop\ScriptConverter\ScriptConverter\bin\Debug\script\systems\combat\combat_base.script"); + var lexer = new ScriptLexer(text, "combat_base"); + var parser = new ScriptParser(Preprocessor(lexer, "DEBUG")); + var document = parser.ParseAll(); + + using (var output = new StreamWriter("output.java")) + { + var printer = new AstPrinter(output, "package", "name", false); + printer.Visit(document); + }*/ + + var scriptPaths = + Directory.EnumerateFiles(_basePath, "*.script", SearchOption.AllDirectories) + .Concat(Directory.EnumerateFiles(_basePath, "*.scriptlib", SearchOption.AllDirectories)) + .Select(p => p.Substring(_basePath.Length)) + .Where(p => !p.Contains(".deps")); + + foreach (var path in scriptPaths) + { + Console.WriteLine(path); + + var outputPath = Path.GetDirectoryName(path); + var outputName = Path.GetFileNameWithoutExtension(path); + var outputFileName = outputName + ".java"; + var isLibrary = Path.GetExtension(path) == ".scriptlib"; + + if (outputPath == null) + throw new Exception(); + + var document = Load(path); + + using (var output = new StreamWriter(Path.Combine(outputPath, outputFileName))) + { + var printer = new AstPrinter(output, outputPath.Replace('\\', '.'), outputName, isLibrary); + printer.Visit(document); + } + } + } + + static Document Load(string path) + { + Document document; + if (_astCache.TryGetValue(path, out document)) + return document; + + var fullPath = Path.Combine(_basePath, path); + var source = File.ReadAllText(fullPath); + + var lexer = new ScriptLexer(source, Path.GetFileName(path)); + var parser = new ScriptParser(Preprocessor(lexer, "DEBUG")); + + document = parser.ParseAll(); + //_astCache.Add(path, document); + + return document; + } + + static IEnumerable Preprocessor(IEnumerable tokens, params string[] define) + { + var defines = new HashSet(define); + var enumerator = tokens.GetEnumerator(); + + var skipping = false; + + while (enumerator.MoveNext()) + { + var token = enumerator.Current; + + if (skipping && token.Type != ScriptTokenType.PreprocessEndIf) + continue; + + switch (token.Type) + { + case ScriptTokenType.PreprocessDefine: + { + if (!enumerator.MoveNext()) + throw new Exception("#define has no name"); + + defines.Add(enumerator.Current.Contents); + + if (!enumerator.MoveNext()) // TODO: this should eat the rest of the line or something + throw new Exception("#define has no value"); + + continue; + } + + case ScriptTokenType.PreprocessIfDef: + { + if (!enumerator.MoveNext()) + throw new Exception("ifdef has no value"); + + skipping = !defines.Contains(enumerator.Current.Contents); + continue; + } + + case ScriptTokenType.PreprocessEndIf: + { + skipping = false; + continue; + } + } + + yield return token; + } + } + } +} diff --git a/ScriptConverter/Properties/AssemblyInfo.cs b/ScriptConverter/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..b22e21c --- /dev/null +++ b/ScriptConverter/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ScriptConverter")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ScriptConverter")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("97200135-4e2c-4a67-9d34-dd319b066f96")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ScriptConverter/ScriptConverter.csproj b/ScriptConverter/ScriptConverter.csproj new file mode 100644 index 0000000..0b65fd8 --- /dev/null +++ b/ScriptConverter/ScriptConverter.csproj @@ -0,0 +1,151 @@ + + + + + Debug + AnyCPU + {3F560E64-ACCE-4520-B3E7-EA6502E20F49} + Exe + Properties + ScriptConverter + ScriptConverter + v4.5 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file