Skip to content

Commit

Permalink
Merge pull request #10 from anaelChardan/parser-binop
Browse files Browse the repository at this point in the history
Add binary arithmetic operation in parser
  • Loading branch information
clementgarbay authored Nov 28, 2017
2 parents e16eaf4 + f31994f commit e35a0d0
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 65 deletions.
44 changes: 25 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,26 @@ L'ensemble des logs des différentes sessions et réunions est disponible [ici](
Dans l'objectif de réaliser un parseur pour un REPL, nous avons créé un grammaire BNF qui représente le langage pour lequel nous validons le type.

```
<Program> ::=
<Expression> | '(' <Expression> ')'
<Expression> ::=
<Application> | <Lambda> | <Let> | <Literal>
<BasicExpression> ::=
<Identifier> | <Literal> | <Application> | <Lambda> | <Let> | <ArithmeticOperation>
<Expression> ::=
<BasicExpression> | '(' <BasicExpression> ')'
<Operator> ::=
'+' | '-' | '*' | '/'
<ArithmeticOperation> ::=
'op' <Expression> <Operator> <Expression>
<Application> ::=
(<Program> | <Identifier>) <Program>
'app' <Expression> <Expression>
<Lambda> ::=
'\' (<Identifier>)+ '->' <Program>
'\' (<Identifier>)+ '->' <Expression>
<Let> ::=
'let' <Identifier> '=' <Program> 'in' <Program>
'let' <Identifier> '=' <Expression> 'in' <Expression>
<Literal> ::=
<BoolLiteral> | <IntLiteral>
Expand All @@ -41,31 +47,31 @@ Dans l'objectif de réaliser un parseur pour un REPL, nous avons créé un gramm
('a'..'z' | 'A'..'Z' | '_')('a'..'z' | 'A'..'Z' | '_' | '0'..'9')*
```

Here are some example of expression that can be parsed :
Here is an example of an expression that can be parsed :
```
let f = (\x -> x) in (\a b -> b) (f True) (f 1)
let f = (\x -> x) in (app (app (\a b -> b) (app f True)) (app f 1))
f => Identifier
(\x -> x) => Program
(\x -> x) => Expression
\x -> x => Lambda
x => Identifier
x => Identifier
(\a b -> b) (f True) (f 1) => Application
(\a b -> b) (f True) => Application
(\a b -> b) => Program
app (app (\a b -> b) (app f True)) (app f 1) => Application
app (\a b -> b) (app f True) => Application
(\a b -> b) => Expression
\a b -> b => Lambda
a => Identifier
b => Identifier
b => Identifier
(f True) => Program
(app f True) => Expression
f True => Application
f => Identifier
True => BoolLiteral
(f 1) => Program
f 1 => Application
(app f 1) => Expression
app f 1 => Application
f => Identifier
1 => IntLiteral
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
import fr.imt.inference.type.IntegerType;
import fr.imt.inference.type.Type;

public class ArithmeticOperation implements Expression {
public class BinaryArithmeticOperation implements Expression {

public final Expression left;
public final Expression right;
public final Operator operator;

public ArithmeticOperation(Expression left, Expression right, Operator operator) {
public BinaryArithmeticOperation(Expression left, Expression right, Operator operator) {
this.left = left;
this.right = right;
this.operator = operator;
Expand All @@ -30,10 +30,10 @@ public Type infer(Environment env, ConstraintCollection constraintCollection) {

@Override
public boolean equals(Object o) {
return o instanceof ArithmeticOperation
&& ((ArithmeticOperation) o).right.equals(this.right)
&& ((ArithmeticOperation) o).left.equals(this.left)
&& ((ArithmeticOperation) o).operator.equals(this.operator);
return o instanceof BinaryArithmeticOperation
&& ((BinaryArithmeticOperation) o).right.equals(this.right)
&& ((BinaryArithmeticOperation) o).left.equals(this.left)
&& ((BinaryArithmeticOperation) o).operator.equals(this.operator);
}

@Override
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/fr/imt/inference/ast/Operator.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package fr.imt.inference.ast;

import io.vavr.collection.List;

public enum Operator {
PLUS("+"), MINUS("-"), TIME("*"), DIVIDE("/");

Expand All @@ -9,6 +11,14 @@ public enum Operator {
this.operator = operator;
}

public static List<Operator> all() {
return List.of(PLUS, MINUS, TIME, DIVIDE);
}

public Character toChar() {
return operator.charAt(0);
}

@Override
public String toString() {
return operator;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ public static Let Let(Variable identifier, Expression definition, Expression bod
return new Let(identifier, definition, body);
}

public static ArithmeticOperation Ope(Expression left, Expression right, Operator operator) {
return new ArithmeticOperation(left, right, operator);
public static BinaryArithmeticOperation Ope(Expression left, Expression right, Operator operator) {
return new BinaryArithmeticOperation(left, right, operator);
}

public static TBoolean Bool(Boolean value) {
Expand Down
119 changes: 82 additions & 37 deletions src/main/java/fr/imt/parser/ExpressionParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,52 +18,92 @@ public class ExpressionParser implements Parsable<Expression> {
public Either<String, Expression> parse(String str) {
Parser<Character, Unit> eof = eof();

return parser.bind(p -> eof.then(retn(p)))
.parse(Input.of(str))
.match(
parsed -> Either.right(parsed.result),
error -> Either.left(error.getMsg())
);
try {
return parser.bind(p -> eof.then(retn(p)))
.parse(Input.of(str))
.match(
parsed -> Either.right(parsed.result),
error -> Either.left(error.getMsg())
);
} catch (Throwable e) {
return Either.left("Cannot parse input");
}
}

private static Parser<Character, Variable> variableParser() {
/**
* Alphanumeric string
*/
private Parser<Character, Variable> variableParser() {
return alphaNum.bind(var -> retn(new Variable(var)));
}

/**
* Int
*/
private Parser<Character, TInteger> integerParser() {
return intr.bind(intL -> retn(new TInteger(intL)));
}

/**
* True | False
*/
private Parser<Character, TBoolean> boolParser() {
Parser<Character, TBoolean> boolTrue = string("True").then(retn(new TBoolean(true)));
Parser<Character, TBoolean> boolFalse = string("False").then(retn(new TBoolean(false)));
return choice(boolTrue, boolFalse);
}

/**
* Int | True | False
*/
private static Parser<Character, Literal> literalParser() {
Parser<Character, TInteger> intLit = intr.bind(intL -> retn(new TInteger(intL)));
Parser<Character, TBoolean> boolTrueLit = string("True").then(retn(new TBoolean(true)));
Parser<Character, TBoolean> boolFalseLit = string("False").then(retn(new TBoolean(false)));
Parser<Character, TBoolean> boolLit = choice(boolTrueLit, boolFalseLit);
private Parser<Character, Literal> literalParser() {
return choice(boolParser(), integerParser());
}

/**
* + | - | * | /
*/
private Parser<Character, Operator> arithmeticOperatorParser() {
return choice(IList.of(Operator.all().map(operator -> chr(operator.toChar()).bind(c -> retn(operator)))));
}

return choice(boolLit, intLit);
/**
* op <exp> <Operator> <exp>
*/
private Parser<Character, BinaryArithmeticOperation> arithmeticOperationParser(Parser<Character, Expression> expression) {
return
string("op").then(
space(
expression.bind(left ->
space(
arithmeticOperatorParser().bind(operator ->
space(
expression.bind(right ->
retn(new BinaryArithmeticOperation(left, right, operator)))))))));
}

/**
* \ <var+> -> <exp | var>
* \ <var+> -> <exp>
*/
private static Parser<Character, Lambda> lambdaParser(Parser<Character, Variable> variable, Parser<Character, Expression> expression) {
private Parser<Character, Lambda> lambdaParser(Parser<Character, Expression> expression) {
return
chr('\\').then(
space(
variable.bind(id ->
variableParser().bind(id ->
space(retn(id))
).many().bind(ids ->
space(
string("->").then(
space(
choice(variable, expression).bind(body ->
expression.bind(body ->
space(
retn(toLambda(ids, body))))))))));
}

/**
* Helper to create Lambda expression from multiple identifiers
*/
private static Lambda toLambda(IList<Variable> ids, Expression body) {
private Lambda toLambda(IList<Variable> ids, Expression body) {
return (ids.size() == 1)
? new Lambda(ids.head(), body)
: new Lambda(ids.head(), toLambda(ids.tail(), body));
Expand All @@ -72,11 +112,11 @@ private static Lambda toLambda(IList<Variable> ids, Expression body) {
/**
* let <var> = <exp> in <exp>
*/
private static Parser<Character, Let> letParser(Parser<Character, Variable> variable, Parser<Character, Expression> expression) {
private Parser<Character, Let> letParser(Parser<Character, Expression> expression) {
return
string("let").then(
space(
variable.bind(id ->
variableParser().bind(id ->
space(
chr('=').then(
space(
Expand All @@ -89,46 +129,51 @@ private static Parser<Character, Let> letParser(Parser<Character, Variable> vari
}

/**
* app <exp | var> <exp>
* app <exp> <exp>
*/
private static Parser<Character, Application> appParser(Parser<Character, Variable> variable, Parser<Character, Expression> expression) {
private Parser<Character, Application> appParser(Parser<Character, Expression> expression) {
return
string("app").then(
space(
choice(variable, expression).bind(body ->
expression.bind(body ->
space(
expression.bind(arg ->
retn(new Application(body, arg)))))));
}

private static Parser<Character, Expression> expressionParser() {
private Parser<Character, Expression> expressionParser() {
Parser.Ref<Character, Expression> expression = Parser.ref();

// Build a basic expression (i.e. without parentheses)
Parser.Ref<Character, Expression> basicExpression = Parser.ref();
Parser<Character, Variable> variable = variableParser();
Parser<Character, Literal> literal = literalParser();
Parser<Character, Lambda> lambda = lambdaParser(variable, expression);
Parser<Character, Let> let = letParser(variable, expression);
Parser<Character, Application> app = appParser(variable, expression);
basicExpression.set(choice(literal, lambda, let, app));
Parser<Character, BinaryArithmeticOperation> arithmeticOperation = attempt(arithmeticOperationParser(expression));
Parser<Character, Lambda> lambda = lambdaParser(expression);
Parser<Character, Let> let = attempt(letParser(expression));
Parser<Character, Application> app = attempt(appParser(expression));
basicExpression.set(choice(arithmeticOperation, literal, lambda, let, app, variable));

// Build a parenthesized expression (i.e. expression with parentheses)
Parser<Character, Expression> parenthesizedExpression =
chr('(').then(
space(
basicExpression.bind(exp ->
space(
chr(')').then(
retn(exp))))));
// Build a addParentheses expression (i.e. expression with parentheses)
Parser<Character, Expression> parenthesizedExpression = addParentheses(basicExpression);

// An expression is either a parenthesized or basic expression
expression.set(choice(basicExpression, parenthesizedExpression));

return expression;
}

private static <T> Parser<Character, T> space(Parser<Character, T> parser) {
private <T> Parser<Character, T> addParentheses(Parser<Character, T> expression) {
return
chr('(').then(
space(
expression.bind(exp ->
space(
chr(')').then(
retn(exp))))));
}

private <T> Parser<Character, T> space(Parser<Character, T> parser) {
return wspaces.then(parser);
}

Expand Down
17 changes: 16 additions & 1 deletion src/test/scala/fr/imt/test/parser/ExpressionParserSpec.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package fr.imt.test.parser

import fr.imt.inference.ast.factory.ExpressionFactory.{App, Bool, Int, Lamb, Let, Var}
import fr.imt.inference.ast.Operator
import fr.imt.inference.ast.factory.ExpressionFactory.{App, Bool, Int, Lamb, Let, Ope, Var}
import fr.imt.parser.ExpressionParser
import org.scalatest.{BeforeAndAfter, Matchers, WordSpec}

Expand All @@ -21,6 +22,20 @@ class ExpressionParserSpec extends WordSpec with Matchers with BeforeAndAfter {
res.get() should equal (expected)
}

"parse a binary arithmetic operation" in {
val res = new ExpressionParser().parse("op 4 + 6")
val expected = Ope(Int(4), Int(6), Operator.PLUS)

res.get() should equal (expected)
}

"parse a complex binary arithmetic operation" in {
val res = new ExpressionParser().parse("op (op 4 + 6) * 3")
val expected = Ope(Ope(Int(4), Int(6), Operator.PLUS), Int(3), Operator.TIME)

res.get() should equal (expected)
}

"parse a lambda expression with one identifier" in {
val res = new ExpressionParser().parse("\\ f -> f")
val expected = Lamb(Var("f"), Var("f"))
Expand Down

0 comments on commit e35a0d0

Please sign in to comment.