Last active
February 17, 2019 06:16
-
-
Save tmatz/2596b139d348b4699e0388be421f9183 to your computer and use it in GitHub Desktop.
LL(1) Recursive Descent Parser
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// AST Generator & Evaluator | |
// based on Recursive Descent Parser without back-tracking | |
// | |
// Syntax | |
// | |
// E := T { [ '+' '-'' ] E } | |
// T := U { [ '*' '/' '%' ] T } | |
// U := [ '+' '-' '' ] F | |
// F := '(' E ')' | [ '0' '1' ... '9' ] | |
// | |
using System; | |
using System.Linq; | |
namespace SoloLearn | |
{ | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
var perser = new ExprParser(); | |
var expr = Console.ReadLine(); | |
if (!string.IsNullOrEmpty(expr)) | |
{ | |
ExprTree ast = perser.Parse(expr); | |
dynamic result = ast.Eval(); | |
Console.WriteLine($"Result: {result}"); | |
} | |
} | |
} | |
public class ExprParser | |
{ | |
private const char End = '\0'; | |
private class Context | |
{ | |
private string _expr; | |
private int _pos; | |
public char Token | |
=> _expr | |
.Skip(_pos) | |
.DefaultIfEmpty(End) | |
.First(); | |
public Context(string expr) | |
{ | |
_expr = expr; | |
_pos = expr | |
.TakeWhile(x => x == ' ') | |
.Count(); | |
} | |
public void NextToken() | |
{ | |
_pos = Math.Min(_pos + 1, _expr.Length); | |
_pos += _expr | |
.Skip(_pos) | |
.TakeWhile(x => x == ' ') | |
.Count(); | |
} | |
} | |
public ExprTree Parse(string expr) | |
{ | |
var ctx = new Context(expr); | |
var ast = new ExprTree(); | |
E(ctx, ast); | |
if (ctx.Token != End) throw Error(); | |
ast.Flatten(); | |
return ast; | |
} | |
private Exception Error() | |
=> new ArgumentException("syntax errer"); | |
private INode E(Context ctx, ExprTree ast) | |
{ | |
var acc = T(ctx, ast); | |
while (true) | |
{ | |
switch (ctx.Token) | |
{ | |
case '+': | |
ctx.NextToken(); | |
acc = ast.Add(acc, E(ctx, ast)); | |
break; | |
case '-': | |
ctx.NextToken(); | |
acc = ast.Subtract(acc, E(ctx, ast)); | |
break; | |
default: | |
return acc; | |
} | |
} | |
} | |
private INode T(Context ctx, ExprTree ast) | |
{ | |
var acc = U(ctx, ast); | |
while (true) | |
{ | |
switch (ctx.Token) | |
{ | |
case '*': | |
ctx.NextToken(); | |
acc = ast.Multiply(acc, T(ctx, ast)); | |
break; | |
case '/': | |
ctx.NextToken(); | |
acc = ast.Divide(acc, T(ctx, ast)); | |
break; | |
case '%': | |
ctx.NextToken(); | |
acc = ast.Modulo(acc, T(ctx, ast)); | |
break; | |
default: | |
return acc; | |
} | |
} | |
} | |
private INode U(Context ctx, ExprTree ast) | |
{ | |
var t = ctx.Token; | |
switch (ctx.Token) | |
{ | |
case '+': | |
case '-': | |
ctx.NextToken(); | |
break; | |
} | |
var v = F(ctx, ast); | |
if (t == '-') v = ast.Negate(v); | |
return v; | |
} | |
private INode F(Context ctx, ExprTree ast) | |
{ | |
switch (ctx.Token) | |
{ | |
case '(': | |
{ | |
ctx.NextToken(); | |
var v = E(ctx, ast); | |
if (ctx.Token != ')') throw Error(); | |
ctx.NextToken(); | |
return v; | |
} | |
case '0': | |
case '1': | |
case '2': | |
case '3': | |
case '4': | |
case '5': | |
case '6': | |
case '7': | |
case '8': | |
case '9': | |
{ | |
var v = ast.Constant(ctx.Token - '0'); | |
ctx.NextToken(); | |
return v; | |
} | |
default: | |
throw Error(); | |
} | |
} | |
} | |
public interface INode | |
{ | |
dynamic Eval(ExprTree.Context ctx); | |
} | |
public class ExprTree | |
{ | |
private INode _root; | |
public class Context | |
{} | |
public ExprTree() | |
{} | |
public void Flatten() | |
{} | |
// | |
// TODO: Impliment without recursion. | |
// | |
public dynamic Eval() | |
{ | |
var ctx = new Context(); | |
return _root.Eval(ctx); | |
} | |
public INode Add(INode a, INode b) | |
=> _root = new AddNode(a, b); | |
public INode Subtract(INode a, INode b) | |
=> _root = new SubtractNode(a, b); | |
public INode Multiply(INode a, INode b) | |
=> _root = new MultiplyNode(a, b); | |
public INode Divide(INode a, INode b) | |
=> _root = new DivideNode(a, b); | |
public INode Modulo(INode a, INode b) | |
=> _root = new ModuloNode(a, b); | |
public INode Negate(INode a) | |
=> _root = new NegateNode(a); | |
public INode Constant(int c) | |
=> _root = new ConstantNode(c); | |
private abstract class Binary | |
{ | |
public INode A { get; } | |
public INode B { get; } | |
public Binary(INode a, INode b) | |
{ | |
A = a; | |
B = b; | |
} | |
} | |
private abstract class Unary | |
{ | |
public INode A { get; } | |
public Unary(INode a) | |
{ | |
A = a; | |
} | |
} | |
private class AddNode : Binary, INode | |
{ | |
public AddNode(INode a, INode b) | |
: base(a, b) | |
{} | |
public dynamic Eval(Context ctx) | |
=> A.Eval(ctx) + B.Eval(ctx); | |
} | |
private class SubtractNode : Binary, INode | |
{ | |
public SubtractNode(INode a, INode b) | |
: base(a, b) | |
{} | |
public dynamic Eval(Context ctx) | |
=> A.Eval(ctx) - B.Eval(ctx); | |
} | |
private class MultiplyNode : Binary, INode | |
{ | |
public MultiplyNode(INode a, INode b) | |
: base(a, b) | |
{} | |
public dynamic Eval(Context ctx) | |
=> A.Eval(ctx) * B.Eval(ctx); | |
} | |
private class DivideNode : Binary, INode | |
{ | |
public DivideNode(INode a, INode b) | |
: base(a, b) | |
{} | |
public dynamic Eval(Context ctx) | |
=> A.Eval(ctx) / B.Eval(ctx); | |
} | |
private class ModuloNode : Binary, INode | |
{ | |
public ModuloNode(INode a, INode b) | |
: base(a, b) | |
{} | |
public dynamic Eval(Context ctx) | |
=> A.Eval(ctx) % B.Eval(ctx); | |
} | |
private class NegateNode : Unary, INode | |
{ | |
public NegateNode(INode a) | |
: base(a) | |
{} | |
public dynamic Eval(Context ctx) | |
=> -A.Eval(ctx); | |
} | |
private class ConstantNode : INode | |
{ | |
public dynamic C { get; } | |
public ConstantNode(int c) | |
{ | |
C = (decimal)c; | |
} | |
public dynamic Eval(Context ctx) => C; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Recursive Descent Parser | |
// without back-tracking | |
// | |
// Syntax | |
// | |
// E := T { [ '+' '-'' ] E } | |
// T := U { [ '*' '/' '%' ] T } | |
// U := [ '+' '-' '' ] F | |
// F := '(' E ')' | [ '0' '1' ... '9' ] | |
// | |
using System; | |
using System.Linq; | |
namespace SoloLearn | |
{ | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
var calculator = new Calculator(); | |
var expr = Console.ReadLine(); | |
if (!string.IsNullOrEmpty(expr)) | |
{ | |
var result = calculator.Calc(expr); | |
Console.WriteLine($"Result: {result}"); | |
} | |
} | |
} | |
class Calculator | |
{ | |
private const char End = '\0'; | |
private string _expr; | |
private int _pos; | |
private char Token | |
=> _expr | |
.Skip(_pos) | |
.DefaultIfEmpty(End) | |
.First(); | |
public int Calc(string expr) | |
{ | |
Initialize(expr); | |
var v = E(); | |
if (Token != End) throw Error(); | |
return v; | |
} | |
private void Initialize(string expr) | |
{ | |
_expr = expr; | |
_pos = expr | |
.TakeWhile(x => x == ' ') | |
.Count(); | |
} | |
private void NextToken() | |
{ | |
_pos = Math.Min(_pos + 1, _expr.Length); | |
_pos += _expr | |
.Skip(_pos) | |
.TakeWhile(x => x == ' ') | |
.Count(); | |
} | |
private Exception Error() | |
=> new ArgumentException("syntax errer"); | |
private int E() | |
{ | |
var acc = T(); | |
while (true) | |
{ | |
switch (Token) | |
{ | |
case '+': | |
NextToken(); | |
acc += E(); | |
break; | |
case '-': | |
NextToken(); | |
acc -= E(); | |
break; | |
default: | |
return acc; | |
} | |
} | |
} | |
private int T() | |
{ | |
var acc = U(); | |
while (true) | |
{ | |
switch (Token) | |
{ | |
case '*': | |
NextToken(); | |
acc *= T(); | |
break; | |
case '/': | |
NextToken(); | |
acc /= T(); | |
break; | |
case '%': | |
NextToken(); | |
acc %= T(); | |
break; | |
default: | |
return acc; | |
} | |
} | |
} | |
private int U() | |
{ | |
var t = Token; | |
switch (Token) | |
{ | |
case '+': | |
case '-': | |
NextToken(); | |
break; | |
} | |
var v = F(); | |
if (t == '-') v = -v; | |
return v; | |
} | |
private int F() | |
{ | |
switch (Token) | |
{ | |
case '(': | |
{ | |
NextToken(); | |
var v = E(); | |
if (Token != ')') throw Error(); | |
NextToken(); | |
return v; | |
} | |
case '0': | |
case '1': | |
case '2': | |
case '3': | |
case '4': | |
case '5': | |
case '6': | |
case '7': | |
case '8': | |
case '9': | |
{ | |
var v = Token - '0'; | |
NextToken(); | |
return v; | |
} | |
default: | |
throw Error(); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment