commit 0693698f2836249d2c319201d9041f44ab45e166 Author: Deukhoofd Date: Sat Nov 10 13:11:36 2018 +0100 initial commit diff --git a/.directory b/.directory new file mode 100644 index 0000000..d0e0634 --- /dev/null +++ b/.directory @@ -0,0 +1,6 @@ +[Dolphin] +Timestamp=2018,11,10,13,9,40 +Version=4 + +[Settings] +HiddenFilesShown=true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4d13c54 --- /dev/null +++ b/.gitignore @@ -0,0 +1,334 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ diff --git a/Upsilon.sln b/Upsilon.sln new file mode 100644 index 0000000..a1d6b1d --- /dev/null +++ b/Upsilon.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Upsilon", "Upsilon\Upsilon.csproj", "{030DBAFB-4E55-427E-82F9-1FD042F96B8F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yc", "Yc\Yc.csproj", "{EF232B73-CDD1-491A-A931-99A9704686E4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UpsilonTests", "UpsilonTests\UpsilonTests.csproj", "{5CB3C59D-96A1-419E-803B-DE4A7DF806FD}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {030DBAFB-4E55-427E-82F9-1FD042F96B8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {030DBAFB-4E55-427E-82F9-1FD042F96B8F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {030DBAFB-4E55-427E-82F9-1FD042F96B8F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {030DBAFB-4E55-427E-82F9-1FD042F96B8F}.Release|Any CPU.Build.0 = Release|Any CPU + {EF232B73-CDD1-491A-A931-99A9704686E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EF232B73-CDD1-491A-A931-99A9704686E4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EF232B73-CDD1-491A-A931-99A9704686E4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EF232B73-CDD1-491A-A931-99A9704686E4}.Release|Any CPU.Build.0 = Release|Any CPU + {5CB3C59D-96A1-419E-803B-DE4A7DF806FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5CB3C59D-96A1-419E-803B-DE4A7DF806FD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5CB3C59D-96A1-419E-803B-DE4A7DF806FD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5CB3C59D-96A1-419E-803B-DE4A7DF806FD}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Upsilon/Evaluator/Evaluator.cs b/Upsilon/Evaluator/Evaluator.cs new file mode 100644 index 0000000..bda0ad7 --- /dev/null +++ b/Upsilon/Evaluator/Evaluator.cs @@ -0,0 +1,79 @@ +using System; +using Upsilon.Parser; + +namespace Upsilon.Evaluator +{ + public static class Evaluator + { + public static object Evaluate(this ScriptSyntax e) + { + return EvaluateExpression(e.Statement); + } + + public static object Evaluate(this ExpressionSyntax e) + { + return EvaluateExpression(e); + } + + private static object EvaluateExpression(ExpressionSyntax e) + { + switch (e.Kind) + { + case SyntaxKind.UnaryExpression: + return EvaluateUnaryExpression((UnaryExpressionSyntax) e); + case SyntaxKind.BinaryExpression: + return EvaluateBinaryExpression((BinaryExpressionSyntax) e); + case SyntaxKind.LiteralExpression: + return ((LiteralExpressionSyntax) e).Value; + case SyntaxKind.ParenthesizedExpression: + return ((ParenthesizedExpressionSyntax) e).Expression.Evaluate(); + default: + throw new Exception("Invalid expression: " + e.Kind); + } + } + + private static object EvaluateUnaryExpression(UnaryExpressionSyntax e) + { + var operand = EvaluateExpression(e.Expression); + switch (e.Operator.Kind) + { + case SyntaxKind.Plus: + return operand; + case SyntaxKind.Minus: + return -(double) operand; + case SyntaxKind.NotKeyword: + return !(bool)operand; + default: + throw new Exception("Invalid Unary Operator: " + e.Operator.Kind); + } + } + + private static object EvaluateBinaryExpression(BinaryExpressionSyntax e) + { + var left = EvaluateExpression(e.Left); + var right = EvaluateExpression(e.Right); + switch (e.Operator.Kind) + { + case SyntaxKind.Plus: + return (double)left + (double)right; + case SyntaxKind.Minus: + return (double)left - (double)right; + case SyntaxKind.Star: + return (double)left * (double)right; + case SyntaxKind.Slash: + return (double)left / (double)right; + case SyntaxKind.AndKeyword: + return (bool)left && (bool)right; + case SyntaxKind.OrKeyword: + return (bool)left || (bool)right; + case SyntaxKind.EqualsEquals: + return Equals(left, right); + case SyntaxKind.TildeEquals: + return !Equals(left, right); + default: + throw new Exception("Invalid Binary Operator: " + e.Operator.Kind); + } + } + + } +} \ No newline at end of file diff --git a/Upsilon/Parser/ExpressionSyntax/BinaryExpressionSyntax.cs b/Upsilon/Parser/ExpressionSyntax/BinaryExpressionSyntax.cs new file mode 100644 index 0000000..5b4522d --- /dev/null +++ b/Upsilon/Parser/ExpressionSyntax/BinaryExpressionSyntax.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; + +namespace Upsilon.Parser +{ + public class BinaryExpressionSyntax : ExpressionSyntax + { + public BinaryExpressionSyntax(ExpressionSyntax left, SyntaxToken @operator, ExpressionSyntax right) + { + Left = left; + Operator = @operator; + Right = right; + } + + public override SyntaxKind Kind => SyntaxKind.BinaryExpression; + + public ExpressionSyntax Left { get; } + public SyntaxToken Operator { get; } + public ExpressionSyntax Right { get; } + + public override IEnumerable ChildNodes() + { + yield return Left; + yield return Operator; + yield return Right; + } + } +} \ No newline at end of file diff --git a/Upsilon/Parser/ExpressionSyntax/ExpressionSyntax.cs b/Upsilon/Parser/ExpressionSyntax/ExpressionSyntax.cs new file mode 100644 index 0000000..c01f4b3 --- /dev/null +++ b/Upsilon/Parser/ExpressionSyntax/ExpressionSyntax.cs @@ -0,0 +1,7 @@ +namespace Upsilon.Parser +{ + public abstract class ExpressionSyntax : SyntaxNode + { + + } +} \ No newline at end of file diff --git a/Upsilon/Parser/ExpressionSyntax/LiteralExpressionSyntax.cs b/Upsilon/Parser/ExpressionSyntax/LiteralExpressionSyntax.cs new file mode 100644 index 0000000..1a09b1d --- /dev/null +++ b/Upsilon/Parser/ExpressionSyntax/LiteralExpressionSyntax.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; + +namespace Upsilon.Parser +{ + public class LiteralExpressionSyntax : ExpressionSyntax + { + public LiteralExpressionSyntax(SyntaxToken literal, object value) + { + Literal = literal; + Value = value; + } + + public override SyntaxKind Kind => SyntaxKind.LiteralExpression; + public SyntaxToken Literal { get; } + public object Value { get; } + + public override IEnumerable ChildNodes() + { + yield return Literal; + } + } +} \ No newline at end of file diff --git a/Upsilon/Parser/ExpressionSyntax/ParenthesizedExpressionSyntax.cs b/Upsilon/Parser/ExpressionSyntax/ParenthesizedExpressionSyntax.cs new file mode 100644 index 0000000..a95eff1 --- /dev/null +++ b/Upsilon/Parser/ExpressionSyntax/ParenthesizedExpressionSyntax.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; + +namespace Upsilon.Parser +{ + public class ParenthesizedExpressionSyntax : ExpressionSyntax + { + public ParenthesizedExpressionSyntax(SyntaxToken openParenthesis, ExpressionSyntax expression, SyntaxToken closeParenthesis) + { + OpenParenthesis = openParenthesis; + Expression = expression; + CloseParenthesis = closeParenthesis; + } + + public override SyntaxKind Kind => SyntaxKind.ParenthesizedExpression; + + public SyntaxToken OpenParenthesis { get; } + public ExpressionSyntax Expression { get; } + public SyntaxToken CloseParenthesis { get; } + + public override IEnumerable ChildNodes() + { + yield return OpenParenthesis; + yield return Expression; + yield return CloseParenthesis; + } + } +} \ No newline at end of file diff --git a/Upsilon/Parser/ExpressionSyntax/ScriptSyntax.cs b/Upsilon/Parser/ExpressionSyntax/ScriptSyntax.cs new file mode 100644 index 0000000..c050fcc --- /dev/null +++ b/Upsilon/Parser/ExpressionSyntax/ScriptSyntax.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; + +namespace Upsilon.Parser +{ + public class ScriptSyntax : SyntaxNode + { + public ScriptSyntax(ExpressionSyntax statement, SyntaxToken endOfFileToken) + { + Statement = statement; + EndOfFileToken = endOfFileToken; + } + + public override SyntaxKind Kind => SyntaxKind.ScriptUnit; + + public ExpressionSyntax Statement { get; } + public SyntaxToken EndOfFileToken { get; } + + public override IEnumerable ChildNodes() + { + yield return Statement; + yield return EndOfFileToken; + } + } +} \ No newline at end of file diff --git a/Upsilon/Parser/ExpressionSyntax/UnaryExpressionSyntax.cs b/Upsilon/Parser/ExpressionSyntax/UnaryExpressionSyntax.cs new file mode 100644 index 0000000..4ac84d9 --- /dev/null +++ b/Upsilon/Parser/ExpressionSyntax/UnaryExpressionSyntax.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; + +namespace Upsilon.Parser +{ + public class UnaryExpressionSyntax : ExpressionSyntax + { + public UnaryExpressionSyntax(SyntaxToken @operator, ExpressionSyntax expression) + { + Operator = @operator; + Expression = expression; + } + + public override SyntaxKind Kind => SyntaxKind.UnaryExpression; + + public SyntaxToken Operator { get; } + public ExpressionSyntax Expression { get; } + + public override IEnumerable ChildNodes() + { + yield return Operator; + yield return Expression; + } + } +} \ No newline at end of file diff --git a/Upsilon/Parser/Lexer.cs b/Upsilon/Parser/Lexer.cs new file mode 100644 index 0000000..2fa89c1 --- /dev/null +++ b/Upsilon/Parser/Lexer.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections.Immutable; +using System.Text; + +namespace Upsilon.Parser +{ + public class Lexer + { + private readonly string _text; + private int _position; + + private Lexer(string text) + { + _text = text; + } + + public static ImmutableArray Lex(string text) + { + var lexer = new Lexer(text); + return lexer.Lex(); + } + + private char Current + { + get + { + if (_position >= _text.Length) + return '\0'; + return _text[_position]; + } + } + + private char Next + { + get + { + if (_position + 1 >= _text.Length) + return '\0'; + return _text[_position + 1]; + } + } + + + private ImmutableArray Lex() + { + var array = ImmutableArray.CreateBuilder(); + while (true) + { + var next = LexNext(); + if (next.Kind != SyntaxKind.WhiteSpace) + { + array.Add(next); + if (next.Kind == SyntaxKind.EndOfFile) + break; + } + _position++; + } + return array.ToImmutable(); + } + + private SyntaxToken LexNext() + { + switch (Current) + { + case '\0': + return new SyntaxToken(SyntaxKind.EndOfFile, _position, "\0", null); + case ' ': case '\t': case '\r': case '\n': + return new SyntaxToken(SyntaxKind.WhiteSpace, _position, Current.ToString(), null); + case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': + return LexNumber(); + case '+': + return new SyntaxToken(SyntaxKind.Plus, _position, "+", null); + case '-': + return new SyntaxToken(SyntaxKind.Minus, _position, "-", null); + case '*': + return new SyntaxToken(SyntaxKind.Star, _position, "*", null); + case '/': + return new SyntaxToken(SyntaxKind.Slash, _position, "/", null); + case '(': + return new SyntaxToken(SyntaxKind.OpenParenthesis, _position, "(", null); + case ')': + return new SyntaxToken(SyntaxKind.CloseParenthesis, _position, ")", null); + case '=': + if (Next == '=') + { + _position++; + return new SyntaxToken(SyntaxKind.EqualsEquals, _position - 1, "==", null); + } + return new SyntaxToken(SyntaxKind.Equals, _position, "=", null); + case '~': + if (Next == '=') + { + _position++; + return new SyntaxToken(SyntaxKind.TildeEquals, _position - 1, "~=", null); + } + return new SyntaxToken(SyntaxKind.Tilde, _position, "~", null); + default: + if (char.IsLetter(Current)) + return LexIdentifierOrKeyword(); + throw new Exception("Unknown token character: " + Current); + } + } + + private SyntaxToken LexNumber() + { + var start = _position; + var hasDecimalPoint = false; + var numStr = new StringBuilder(); + numStr.Append(Current); + while (char.IsDigit(Next) || Next == '.' || Next == '_') + { + if (Next == '.') + { + if (hasDecimalPoint) + { + throw new Exception("No second decimal allowed there"); + } + hasDecimalPoint = true; + } + numStr.Append(Next); + _position++; + } + var i = double.Parse(numStr.ToString()); + return new SyntaxToken(SyntaxKind.Number, start, numStr.ToString(), i); + } + + private SyntaxToken LexIdentifierOrKeyword() + { + var start = _position; + var numStr = new StringBuilder(); + numStr.Append(Current); + while (char.IsLetterOrDigit(Next) || Next == '_') + { + numStr.Append(Next); + _position++; + } + + var kind = SyntaxKeyWords.GetSyntaxKind(numStr.ToString()); + return new SyntaxToken(kind, start, numStr.ToString(), null); + } + } +} \ No newline at end of file diff --git a/Upsilon/Parser/Parser.cs b/Upsilon/Parser/Parser.cs new file mode 100644 index 0000000..9e4b828 --- /dev/null +++ b/Upsilon/Parser/Parser.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Immutable; + +namespace Upsilon.Parser +{ + public class Parser + { + private readonly ImmutableArray _tokens; + private int _position; + + private Parser(ImmutableArray tokens) + { + _tokens = tokens; + } + + public static ScriptSyntax Parse(string text) + { + var tokens = Lexer.Lex(text); + return new Parser(tokens).ParseScriptSyntax(); + } + + private SyntaxToken Current => Get(0); + private SyntaxToken Next => Get(1); + + private SyntaxToken Get(int offset) + { + if (_position + offset >= _tokens.Length) + return new SyntaxToken(SyntaxKind.EndOfFile, _position + offset, "\0", null); + return _tokens[_position + offset]; + } + + private SyntaxToken NextToken() + { + var current = Current; + _position++; + return current; + } + + private SyntaxToken MatchToken(SyntaxKind kind) + { + if (Current.Kind == kind) + return NextToken(); + + return new SyntaxToken(kind, Current.Span.Start, "", null); + } + + public ScriptSyntax ParseScriptSyntax() + { + var expression = ParseExpression(); + var eof = MatchToken(SyntaxKind.EndOfFile); + return new ScriptSyntax(expression, eof); + } + + public ExpressionSyntax ParseExpression() + { + return ParseBinaryExpression(); + } + + private ExpressionSyntax ParseBinaryExpression(SyntaxKindPrecedence.Precedence parentPrecedence = SyntaxKindPrecedence.Precedence.None) + { + ExpressionSyntax left; + var unaryOperatorPrecedence = Current.Kind.UnaryOperatorPrecedence(); + if (unaryOperatorPrecedence != SyntaxKindPrecedence.Precedence.None + && unaryOperatorPrecedence >= parentPrecedence) + { + var operatorToken = NextToken(); + var operand = ParseBinaryExpression(unaryOperatorPrecedence); + left = new UnaryExpressionSyntax(operatorToken, operand); + } + else + { + left = ParsePrimaryExpression(); + } + + while (true) + { + var precedence = Current.Kind.BinaryOperatorPrecedence(); + if (precedence == SyntaxKindPrecedence.Precedence.None || precedence <= parentPrecedence) + break; + + var op = NextToken(); + var right = ParseBinaryExpression(precedence); + left = new BinaryExpressionSyntax(left, op, right); + } + return left; + } + + private ExpressionSyntax ParsePrimaryExpression() + { + switch (Current.Kind) + { + case SyntaxKind.OpenParenthesis: + return ParseParenthesizedExpression(); + case SyntaxKind.Number: + return ParseNumber(); + case SyntaxKind.TrueKeyword: + case SyntaxKind.FalseKeyword: + return ParseBoolean(); + default: + throw new Exception("Unknown primary expression type: " + Current.Kind); + } + } + + private ExpressionSyntax ParseParenthesizedExpression() + { + var l = MatchToken(SyntaxKind.OpenParenthesis); + var e = ParseExpression(); + var r = MatchToken(SyntaxKind.CloseParenthesis); + return new ParenthesizedExpressionSyntax(l, e, r); + } + + private ExpressionSyntax ParseNumber() + { + var numberToken = MatchToken(SyntaxKind.Number); + return new LiteralExpressionSyntax(numberToken, numberToken.Value); + } + + private ExpressionSyntax ParseBoolean() + { + var isTrue = Current.Kind == SyntaxKind.TrueKeyword; + var token = MatchToken(isTrue ? SyntaxKind.TrueKeyword : SyntaxKind.FalseKeyword); + return new LiteralExpressionSyntax(token, isTrue); + } + + } +} \ No newline at end of file diff --git a/Upsilon/Parser/SyntaxKeyWords.cs b/Upsilon/Parser/SyntaxKeyWords.cs new file mode 100644 index 0000000..b23c933 --- /dev/null +++ b/Upsilon/Parser/SyntaxKeyWords.cs @@ -0,0 +1,24 @@ +namespace Upsilon.Parser +{ + public static class SyntaxKeyWords + { + public static SyntaxKind GetSyntaxKind(string s) + { + switch (s) + { + case "true": + return SyntaxKind.TrueKeyword; + case "false": + return SyntaxKind.FalseKeyword; + case "not": + return SyntaxKind.NotKeyword; + case "and": + return SyntaxKind.AndKeyword; + case "or": + return SyntaxKind.OrKeyword; + default: + return SyntaxKind.Identifier; + } + } + } +} \ No newline at end of file diff --git a/Upsilon/Parser/SyntaxKind.cs b/Upsilon/Parser/SyntaxKind.cs new file mode 100644 index 0000000..cf016f3 --- /dev/null +++ b/Upsilon/Parser/SyntaxKind.cs @@ -0,0 +1,39 @@ +namespace Upsilon.Parser +{ + public enum SyntaxKind + { + // tokens + EndOfFile, + WhiteSpace, + + Number, + Plus, + Minus, + Star, + Slash, + OpenParenthesis, + CloseParenthesis, + Equals, + EqualsEquals, + Tilde, + TildeEquals, + + // key words + TrueKeyword, + FalseKeyword, + NotKeyword, + AndKeyword, + OrKeyword, + + Identifier, + + // Expressions + UnaryExpression, + BinaryExpression, + LiteralExpression, + ParenthesizedExpression, + + // script unit + ScriptUnit, + } +} \ No newline at end of file diff --git a/Upsilon/Parser/SyntaxKindPrecedence.cs b/Upsilon/Parser/SyntaxKindPrecedence.cs new file mode 100644 index 0000000..d323f7b --- /dev/null +++ b/Upsilon/Parser/SyntaxKindPrecedence.cs @@ -0,0 +1,53 @@ +namespace Upsilon.Parser +{ + public static class SyntaxKindPrecedence + { + public enum Precedence + { + None = 0, + Or, + And, + Equality, + PlusMinus, + StarSlash, + Unary, + } + + public static Precedence UnaryOperatorPrecedence(this SyntaxKind kind) + { + switch (kind) + { + case SyntaxKind.Plus: + case SyntaxKind.Minus: + case SyntaxKind.NotKeyword: + return Precedence.Unary; + default: + return Precedence.None; + } + } + + public static Precedence BinaryOperatorPrecedence(this SyntaxKind kind) + { + switch (kind) + { + case SyntaxKind.EqualsEquals: + return Precedence.Equality; + case SyntaxKind.TildeEquals: + return Precedence.Equality; + case SyntaxKind.AndKeyword: + return Precedence.And; + case SyntaxKind.OrKeyword: + return Precedence.Or; + case SyntaxKind.Plus: + case SyntaxKind.Minus: + return Precedence.PlusMinus; + case SyntaxKind.Star: + case SyntaxKind.Slash: + return Precedence.StarSlash; + default: + return Precedence.None; + } + + } + } +} \ No newline at end of file diff --git a/Upsilon/Parser/SyntaxNode.cs b/Upsilon/Parser/SyntaxNode.cs new file mode 100644 index 0000000..d359a92 --- /dev/null +++ b/Upsilon/Parser/SyntaxNode.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using Upsilon.Text; + +namespace Upsilon.Parser +{ + public abstract class SyntaxNode + { + public abstract SyntaxKind Kind { get; } + + + public virtual TextSpan Span { get; set; } + + public abstract IEnumerable ChildNodes(); + } +} \ No newline at end of file diff --git a/Upsilon/Parser/SyntaxToken.cs b/Upsilon/Parser/SyntaxToken.cs new file mode 100644 index 0000000..b676a72 --- /dev/null +++ b/Upsilon/Parser/SyntaxToken.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using Upsilon.Text; + +namespace Upsilon.Parser +{ + public sealed class SyntaxToken : SyntaxNode + { + public SyntaxToken(SyntaxKind kind, int position, string text, object value) + { + Kind = kind; + Span = new TextSpan(position, text.Length); + Value = value; + } + + public override SyntaxKind Kind { get; } + public object Value { get; } + + public override IEnumerable ChildNodes() + { + yield break; + } + } +} \ No newline at end of file diff --git a/Upsilon/Text/TextSpan.cs b/Upsilon/Text/TextSpan.cs new file mode 100644 index 0000000..8990676 --- /dev/null +++ b/Upsilon/Text/TextSpan.cs @@ -0,0 +1,15 @@ +namespace Upsilon.Text +{ + public struct TextSpan + { + public TextSpan(int start, int length) + { + Start = start; + Length = length; + } + + public int Start { get; } + public int Length { get; } + public int End => Start + End; + } +} \ No newline at end of file diff --git a/Upsilon/Upsilon.csproj b/Upsilon/Upsilon.csproj new file mode 100644 index 0000000..98f8dec --- /dev/null +++ b/Upsilon/Upsilon.csproj @@ -0,0 +1,13 @@ + + + + netstandard2.0 + + + + + ..\..\..\..\..\usr\share\dotnet\sdk\NuGetFallbackFolder\microsoft.netcore.app\2.1.0\ref\netcoreapp2.1\System.Collections.Immutable.dll + + + + diff --git a/Upsilon/Upsilon.csproj.DotSettings b/Upsilon/Upsilon.csproj.DotSettings new file mode 100644 index 0000000..3366c42 --- /dev/null +++ b/Upsilon/Upsilon.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/Upsilon/Utilities/NodeStringFormatter.cs b/Upsilon/Utilities/NodeStringFormatter.cs new file mode 100644 index 0000000..da6be95 --- /dev/null +++ b/Upsilon/Utilities/NodeStringFormatter.cs @@ -0,0 +1,27 @@ +using System.Text; +using Upsilon.Parser; + +namespace Upsilon.Utilities +{ + public static class NodeStringFormatter + { + public static string Print(this SyntaxNode token, int depth = 0) + { + var sb = new StringBuilder(); + var tabs = depth - 1; + for (var i = 0; i < tabs; i++) + sb.Append("\t"); + if (depth > 0) + { + sb.Append("|-- "); + } + sb.Append(token.Kind); + foreach (var syntaxNode in token.ChildNodes()) + { + sb.Append("\n"); + sb.Append(syntaxNode.Print(depth + 1)); + } + return sb.ToString(); + } + } +} \ No newline at end of file diff --git a/UpsilonTests/BasicMathExpressions.cs b/UpsilonTests/BasicMathExpressions.cs new file mode 100644 index 0000000..a8fc6ea --- /dev/null +++ b/UpsilonTests/BasicMathExpressions.cs @@ -0,0 +1,57 @@ +using Upsilon.Evaluator; +using Upsilon.Parser; +using Xunit; + +namespace UpsilonTests +{ + public class BasicMathExpressions + { + [Theory] + [InlineData("1+1", 2)] + [InlineData("1000+1", 1001)] + [InlineData("1+1000", 1001)] + [InlineData("1 + 1000", 1001)] + [InlineData("8612648+6153205", 14765853)] + [InlineData("0.5 + 2", 2.5)] + [InlineData("0.005 + 2.2", 2.205)] + public void Addition(string input, double expectedOutput) + { + var actual = (double)Parser.Parse(input).Evaluate(); + Assert.Equal(expectedOutput, actual, 8); + } + + [Theory] + [InlineData("1-1", 0)] + [InlineData("100-45", 55)] + [InlineData("1-1200", -1199)] + [InlineData("341564-5646843", -5305279)] + [InlineData("1-0.5", 0.5)] + [InlineData("10.256-2.8546", 7.4014)] + public void Subtraction(string input, double expectedOutput) + { + var actual = (double)Parser.Parse(input).Evaluate(); + Assert.Equal(expectedOutput, actual, 8); + } + + [Theory] + [InlineData("1*1", 1)] + [InlineData("100 * 100", 10000)] + [InlineData("21312 * 41684", 888369408)] + public void Multiplication(string input, double expectedOutput) + { + var actual = (double)Parser.Parse(input).Evaluate(); + Assert.Equal(expectedOutput, actual, 8); + } + + [Theory] + [InlineData("1/1", 1)] + [InlineData("1000 / 10", 100)] + [InlineData("656486 / 5146", 127.57209483)] + public void Divison(string input, double expectedOutput) + { + var actual = (double)Parser.Parse(input).Evaluate(); + Assert.Equal(expectedOutput, actual, 8); + } + + } +} \ No newline at end of file diff --git a/UpsilonTests/MathPrecedence.cs b/UpsilonTests/MathPrecedence.cs new file mode 100644 index 0000000..716c720 --- /dev/null +++ b/UpsilonTests/MathPrecedence.cs @@ -0,0 +1,28 @@ +using Upsilon.Evaluator; +using Upsilon.Parser; +using Xunit; + +namespace UpsilonTests +{ + public class MathPrecedence + { + [Theory] + [InlineData("5 * (10 + 5)", 75)] + [InlineData("(10 + 5) * 5", 75)] + public void Parenthesis(string input, double expectedOutput) + { + var actual = (double)Parser.Parse(input).Evaluate(); + Assert.Equal(expectedOutput, actual, 8); + } + + [Theory] + [InlineData("5 * 10 + 5", 55)] + [InlineData("5 + 10 * 5", 55)] + public void MultiplicationBeforeAddition(string input, double expectedOutput) + { + var actual = (double)Parser.Parse(input).Evaluate(); + Assert.Equal(expectedOutput, actual, 8); + } + + } +} \ No newline at end of file diff --git a/UpsilonTests/UpsilonTests.csproj b/UpsilonTests/UpsilonTests.csproj new file mode 100644 index 0000000..b78f421 --- /dev/null +++ b/UpsilonTests/UpsilonTests.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp2.1 + + false + + + + + + + + + + + + + diff --git a/Yc/Program.cs b/Yc/Program.cs new file mode 100644 index 0000000..8679282 --- /dev/null +++ b/Yc/Program.cs @@ -0,0 +1,27 @@ +using System; +using Upsilon.Evaluator; +using Upsilon.Parser; + +namespace Yc +{ + static class Program + { + static void Main(string[] args) + { + Console.WriteLine("Upsilon REPL"); + while (true) + { + Console.Write("» "); + var input = Console.ReadLine(); + if (input == "exit") + { + return; + } + + var parser = Parser.Parse(input); + //Console.WriteLine(parser.Print()); + Console.WriteLine(parser.Evaluate()); + } + } + } +} \ No newline at end of file diff --git a/Yc/Yc.csproj b/Yc/Yc.csproj new file mode 100644 index 0000000..17d46f1 --- /dev/null +++ b/Yc/Yc.csproj @@ -0,0 +1,12 @@ + + + + Exe + netcoreapp2.1 + + + + + + +