Skip to content

Commit

Permalink
Completely replaced the Superpower based parser with a new `Pegasus…
Browse files Browse the repository at this point in the history
…` based parser which should properly handle precedence (and is just generally much more maintainable)
  • Loading branch information
martindevans committed Apr 21, 2020
1 parent 40b0e79 commit 88ab4e4
Show file tree
Hide file tree
Showing 21 changed files with 544 additions and 367 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ _ReSharper.Caches
/YololEmulator.Tests/*.ncrunchproject
/Yololc/Properties/PublishProfiles/*.pubxml
/Yolol/nuget-push.ps1
/Yolol/*.ncrunchproject
1 change: 1 addition & 0 deletions Yolol.Cylon/Serialisation/AstSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ private JToken SerializeExpression(BaseExpression expr)
LessThanEqualTo cmp => SerializeBinary(cmp, "less_than_or_equal_to"),
EqualTo cmp => SerializeBinary(cmp, "equal_to"),
NotEqualTo cmp => SerializeBinary(cmp, "not_equal_to"),
Sqrt sqrt => new JObject {["type"] = "expression::unary_op::sqrt", ["operand"] = SerializeExpression(sqrt.Parameter)},
_ => throw new NotSupportedException($"Cannot serialize expression type `{expr.GetType().Name}`")
};
}
Expand Down
4 changes: 1 addition & 3 deletions Yolol/Grammar/AST/Expressions/Unary/Abs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ public class Abs

public override bool IsBoolean => false;

public override bool IsConstant => Parameter.IsConstant;

public Abs(BaseExpression parameter)
: base(parameter)
{
Expand All @@ -33,7 +31,7 @@ public override bool Equals(BaseExpression? other)

public override string ToString()
{
return $"ABS({Parameter})";
return $"ABS {Parameter}";
}
}
}
2 changes: 1 addition & 1 deletion Yolol/Grammar/AST/Expressions/Unary/BaseTrigonometry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ protected BaseTrigonometry(BaseExpression parameter, string name)

public override string ToString()
{
return $"{_name}({Parameter})";
return $"{_name} {Parameter}";
}
}
}
46 changes: 46 additions & 0 deletions Yolol/Grammar/AST/Expressions/Unary/Factorial.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Text;
using Yolol.Execution;

namespace Yolol.Grammar.AST.Expressions.Unary
{
public class Factorial
: BaseUnaryExpression
{
public override bool IsBoolean => false;

public override bool CanRuntimeError => true;

public Factorial(BaseExpression parameter)
: base(parameter)
{
}


public bool Equals(Factorial? other)
{
return other != null
&& other.Parameter.Equals(Parameter);
}

public override bool Equals(BaseExpression? other)
{
return other is Factorial fac
&& fac.Equals(this);
}

public override string ToString()
{
return $"{Parameter}!";
}

protected override Value Evaluate(Value value)
{
if (value.Type == Execution.Type.String)
throw new ExecutionException("Attempted to apply factorial to a string");
else
throw new NotImplementedException();
}
}
}
2 changes: 1 addition & 1 deletion Yolol/Grammar/AST/Expressions/Unary/Sqrt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public override bool Equals(BaseExpression? other)

public override string ToString()
{
return $"SQRT({Parameter})";
return $"SQRT {Parameter}";
}
}
}
247 changes: 67 additions & 180 deletions Yolol/Grammar/Parser.cs
Original file line number Diff line number Diff line change
@@ -1,196 +1,83 @@
using System;
using Superpower;
using Superpower.Model;
using Superpower.Parsers;
using Pegasus.Common;
using Yolol.Grammar.AST;
using Yolol.Grammar.AST.Expressions;
using Yolol.Grammar.AST.Expressions.Binary;
using Yolol.Grammar.AST.Expressions.Unary;
using Yolol.Grammar.AST.Statements;

namespace Yolol.Grammar
{
public static class Parser
public class Parser
{
private static readonly TokenListParser<YololToken, YololBinaryOp> Add = Token.EqualTo(YololToken.Plus).Value(YololBinaryOp.Add);
private static readonly TokenListParser<YololToken, YololBinaryOp> Subtract = Token.EqualTo(YololToken.Subtract).Value(YololBinaryOp.Subtract);
private static readonly TokenListParser<YololToken, YololBinaryOp> Multiply = Token.EqualTo(YololToken.Multiply).Value(YololBinaryOp.Multiply);
private static readonly TokenListParser<YololToken, YololBinaryOp> Divide = Token.EqualTo(YololToken.Divide).Value(YololBinaryOp.Divide);
private static readonly TokenListParser<YololToken, YololBinaryOp> Modulo = Token.EqualTo(YololToken.Modulo).Value(YololBinaryOp.Modulo);
private static readonly TokenListParser<YololToken, YololBinaryOp> Exponent = Token.EqualTo(YololToken.Exponent).Value(YololBinaryOp.Exponent);

private static readonly TokenListParser<YololToken, YololBinaryOp> And = Token.EqualTo(YololToken.And).Value(YololBinaryOp.And);
private static readonly TokenListParser<YololToken, YololBinaryOp> Or = Token.EqualTo(YololToken.Or).Value(YololBinaryOp.Or);

private static readonly TokenListParser<YololToken, YololBinaryOp> CompoundAdd = Token.EqualTo(YololToken.CompoundPlus).Value(YololBinaryOp.Add);
private static readonly TokenListParser<YololToken, YololBinaryOp> CompoundSubtract = Token.EqualTo(YololToken.CompoundSubtract).Value(YololBinaryOp.Subtract);
private static readonly TokenListParser<YololToken, YololBinaryOp> CompoundMultiply = Token.EqualTo(YololToken.CompoundMultiply).Value(YololBinaryOp.Multiply);
private static readonly TokenListParser<YololToken, YololBinaryOp> CompoundDivide = Token.EqualTo(YololToken.CompoundDivide).Value(YololBinaryOp.Divide);
private static readonly TokenListParser<YololToken, YololBinaryOp> CompoundModulo = Token.EqualTo(YololToken.CompoundModulo).Value(YololBinaryOp.Modulo);

private static readonly TokenListParser<YololToken, YololBinaryOp> LessThan = Token.EqualTo(YololToken.LessThan).Value(YololBinaryOp.LessThan);
private static readonly TokenListParser<YololToken, YololBinaryOp> GreaterThan = Token.EqualTo(YololToken.GreaterThan).Value(YololBinaryOp.GreaterThan);
private static readonly TokenListParser<YololToken, YololBinaryOp> LessThanEqualTo = Token.EqualTo(YololToken.LessThanEqualTo).Value(YololBinaryOp.LessThanEqualTo);
private static readonly TokenListParser<YololToken, YololBinaryOp> GreaterThanEqualTo = Token.EqualTo(YololToken.GreaterThanEqualTo).Value(YololBinaryOp.GreaterThanEqualTo);
private static readonly TokenListParser<YololToken, YololBinaryOp> NotEqualTo = Token.EqualTo(YololToken.NotEqualTo).Value(YololBinaryOp.NotEqualTo);
private static readonly TokenListParser<YololToken, YololBinaryOp> EqualTo = Token.EqualTo(YololToken.EqualTo).Value(YololBinaryOp.EqualTo);

private static readonly TokenListParser<YololToken, BaseExpression> Not =
from not in Token.EqualTo(YololToken.Not)
from exp in Parse.Ref(() => Expression)
select (BaseExpression)new Not(exp);

private static readonly TokenListParser<YololToken, VariableName> VariableName = Token.EqualTo(YololToken.Identifier).Select(n => new VariableName(n.ToStringValue()));
private static readonly TokenListParser<YololToken, VariableName> ExternalVariableName = Token.EqualTo(YololToken.ExternalIdentifier).Select(n => new VariableName(n.ToStringValue()));

private static readonly TokenListParser<YololToken, BaseExpression> PositiveNumExpression = Token.EqualTo(YololToken.Number).Select(n => (BaseExpression)new ConstantNumber(decimal.Parse(n.ToStringValue())));
private static readonly TokenListParser<YololToken, BaseExpression> NegativeNumExpression =
from neg in Token.EqualTo(YololToken.Subtract)
from num in Token.EqualTo(YololToken.Number)
select (BaseExpression)new ConstantNumber(-decimal.Parse(num.ToStringValue()));

private static readonly TokenListParser<YololToken, BaseExpression> ConstantNumExpression = PositiveNumExpression.Or(NegativeNumExpression);

private static readonly TokenListParser<YololToken, BaseExpression> ConstantStrExpression = Token.EqualTo(YololToken.String).Select(n => (BaseExpression)new ConstantString(n.ToStringValue().Trim('"')));
private static readonly TokenListParser<YololToken, BaseExpression> VariableExpression = from name in VariableName select (BaseExpression)new Variable(name);
private static readonly TokenListParser<YololToken, BaseExpression> ExternalVariableExpression = from name in ExternalVariableName select (BaseExpression)new Variable(name);

private static readonly TokenListParser<YololToken, BaseExpression> PreIncrementExpr =
from inc in Token.EqualTo(YololToken.Increment)
from var in VariableName
select (BaseExpression)new PreIncrement(var);

private static readonly TokenListParser<YololToken, BaseExpression> PostIncrementExpr =
from var in VariableName
from inc in Token.EqualTo(YololToken.Increment)
select (BaseExpression)new PostIncrement(var);

private static readonly TokenListParser<YololToken, BaseExpression> PreDecrementExpr =
from dec in Token.EqualTo(YololToken.Decrement)
from var in VariableName
select (BaseExpression)new PreDecrement(var);

private static readonly TokenListParser<YololToken, BaseExpression> PostDecrementExpr =
from var in VariableName
from dec in Token.EqualTo(YololToken.Decrement)
select (BaseExpression)new PostDecrement(var);

private static readonly TokenListParser<YololToken, BaseStatement> PreIncrementStat = PreIncrementExpr.Select(a => (BaseStatement)new ExpressionWrapper(a));
private static readonly TokenListParser<YololToken, BaseStatement> PostIncrementStat = PostIncrementExpr.Select(a => (BaseStatement)new ExpressionWrapper(a));
private static readonly TokenListParser<YololToken, BaseStatement> PreDecrementStat = PreDecrementExpr.Select(a => (BaseStatement)new ExpressionWrapper(a));
private static readonly TokenListParser<YololToken, BaseStatement> PostDecrementStat = PostDecrementExpr.Select(a => (BaseStatement)new ExpressionWrapper(a));

private static TokenListParser<YololToken, BaseExpression> MkFunction(YololToken token, Func<BaseExpression, BaseExpression> make)
public static Result<Program, ParseError> ParseProgram(string program)
{
return from nm in Token.EqualTo(token)
from lp in Token.EqualTo(YololToken.LParen)
from exp in Expression
from rp in Token.EqualTo(YololToken.RParen)
select make(exp);
try
{
var p = new YololParser();
return new Result<Program, ParseError>(p.Parse(program));
}
catch (FormatException e)
{
var c = (Cursor)e.Data["cursor"];
return new Result<Program, ParseError>(new ParseError(c, e.Message));
}
}

private static readonly TokenListParser<YololToken, BaseExpression> SqrtExpression = MkFunction(YololToken.Sqrt, a => new Sqrt(a));
private static readonly TokenListParser<YololToken, BaseExpression> AbsExpression = MkFunction(YololToken.Abs, a => new Abs(a));
private static readonly TokenListParser<YololToken, BaseExpression> SineExpression = MkFunction(YololToken.Sine, a => new Sine(a));
private static readonly TokenListParser<YololToken, BaseExpression> CosineExpression = MkFunction(YololToken.Cosine, a => new Cosine(a));
private static readonly TokenListParser<YololToken, BaseExpression> TangentExpression = MkFunction(YololToken.Tangent, a => new Tangent(a));
private static readonly TokenListParser<YololToken, BaseExpression> ArcSineExpression = MkFunction(YololToken.ArcSin, a => new ArcSine(a));
private static readonly TokenListParser<YololToken, BaseExpression> ArcCosExpression = MkFunction(YololToken.ArcCos, a => new ArcCos(a));
private static readonly TokenListParser<YololToken, BaseExpression> ArcTanExpression = MkFunction(YololToken.ArcTan, a => new ArcTan(a));

private static readonly TokenListParser<YololToken, BaseExpression> FunctionCall =
SqrtExpression.Try()
.Or(AbsExpression.Try())
.Or(SineExpression.Try())
.Or(CosineExpression.Try())
.Or(TangentExpression.Try())
.Or(ArcSineExpression.Try())
.Or(ArcCosExpression.Try())
.Or(ArcTanExpression.Try());

private static readonly TokenListParser<YololToken, BaseExpression> Factor =
(from lparen in Token.EqualTo(YololToken.LParen)
from expr in Parse.Ref(() => Expression)
from rparen in Token.EqualTo(YololToken.RParen)
select (BaseExpression)new Bracketed(expr))
.Or(FunctionCall.Try())
.Or(PostDecrementExpr.Try())
.Or(PreIncrementExpr.Try())
.Or(PostIncrementExpr.Try())
.Or(PreDecrementExpr.Try())
.Or(ConstantNumExpression.Try())
.Or(ConstantStrExpression.Try())
.Or(VariableExpression.Try())
.Or(ExternalVariableExpression.Try());

private static readonly TokenListParser<YololToken, BaseExpression> Operand =
ConstantNumExpression
.Try().Or(from sign in Token.EqualTo(YololToken.Subtract)
from factor in Factor
select (BaseExpression)new Negate(factor))
.Or(Not.Try())
.Or(Factor.Try())
.Named("expression");

private static readonly TokenListParser<YololToken, BaseExpression> Term =
Parse.Chain(Multiply.Or(Divide).Or(Modulo).Or(Exponent), Operand, BaseBinaryExpression.Create).Try();

private static readonly TokenListParser<YololToken, BaseExpression> Expression =
Parse.Chain(Add.Or(Subtract).Or(LessThan).Or(GreaterThan).Or(LessThanEqualTo).Or(GreaterThanEqualTo).Or(NotEqualTo).Or(EqualTo).Or(And).Or(Or), Term, BaseBinaryExpression.Create);

private static readonly TokenListParser<YololToken, BaseStatement> Assignment =
from lhs in VariableName.Or(ExternalVariableName)
from op in Token.EqualTo(YololToken.Assignment)
from rhs in Expression
select (BaseStatement)new Assignment(lhs, rhs);

private static readonly TokenListParser<YololToken, BaseStatement> CompoundAssignment =
from lhs in VariableName.Or(ExternalVariableName)
from op in CompoundAdd.Or(CompoundSubtract.Try()).Or(CompoundMultiply.Try()).Or(CompoundDivide.Try()).Or(CompoundModulo.Try())
from rhs in Expression
select (BaseStatement)new CompoundAssignment(lhs, op, rhs);

private static readonly TokenListParser<YololToken, BaseStatement> Goto =
from @goto in Token.EqualTo(YololToken.Goto)
from destination in Expression
select (BaseStatement)new Goto(destination);

private static readonly TokenListParser<YololToken, BaseStatement> If =
from @if in Token.EqualTo(YololToken.If)
from cond in Expression
from then in Token.EqualTo(YololToken.Then)
from trueBranch in Parse.Ref(() => Statement.Many())
from falseBranch in (Token.EqualTo(YololToken.Else).IgnoreThen(Parse.Ref(() => Statement.Many())).OptionalOrDefault(Array.Empty<BaseStatement>()))
from end in Token.EqualTo(YololToken.End)
select (BaseStatement)new If(cond, new StatementList(trueBranch), new StatementList(falseBranch));

private static readonly TokenListParser<YololToken, BaseStatement> Statement =
Assignment.Try()
.Or(If.Try())
.Or(CompoundAssignment.Try())
.Or(Goto.Try())
.Or(PreIncrementStat.Try())
.Or(PreDecrementStat.Try())
.Or(PostIncrementStat.Try())
.Or(PostDecrementStat);

private static readonly TokenListParser<YololToken, Line> Line =
from statements in Statement.Many()
from nl in Token.EqualTo(YololToken.NewLine)
select new Line(new StatementList(statements));

private static readonly TokenListParser<YololToken, Program> Program =
from lines in Line.Many().AtEnd()
select new Program(lines);

public static TokenListParserResult<YololToken, Line> TryParseLine(TokenList<YololToken> tokens)
public readonly struct Result<TOk, TErr>
where TOk : class
where TErr : class
{
return Line.TryParse(tokens);
private readonly TOk? _ok;
private readonly TErr? _err;

public TOk Ok
{
get
{
if (IsOk)
return _ok!;
throw new InvalidOperationException("Cannot get an ok value from an error result");
}
}

public TErr Err {
get
{
if (!IsOk)
return _err!;
throw new InvalidOperationException("Cannot get an error value from ok result");
}
}

public bool IsOk { get; }

public Result(TOk ok)
{
IsOk = true;
_ok = ok;
_err = null;
}

public Result(TErr err)
{
IsOk = false;
_ok = null;
_err = err;
}
}

public static TokenListParserResult<YololToken, Program> TryParseProgram(TokenList<YololToken> tokens)
public class ParseError
{
return Program.TryParse(tokens);
public Cursor Cursor { get; }
public string Message { get; }

public ParseError(Cursor cursor, string message)
{
Cursor = cursor;
Message = message;
}

public override string ToString()
{
return $"{Message} (Ln{Cursor.Line}, Ch{Cursor.Column})";
}
}
}
}
Loading

0 comments on commit 88ab4e4

Please sign in to comment.