Skip to content

Commit

Permalink
"if ... then ... else"
Browse files Browse the repository at this point in the history
  • Loading branch information
julianhyde committed Jan 18, 2019
1 parent 497056f commit 18eb9b5
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 6 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,20 @@ Implemented:
* Operators: + - * / ^ `andalso` `orelse`
* Type derivation (for simple types)
* `fn`, function values, and function application
* `if`

Not implemented:
* `fun` declaration
* Generic types
* Tuple types and record types
* `let rec`
* `type`
* `datatype`
* `local`
* `handle`
* `raise`, `handle`
* `exception`
* `while`
* `case`
* Patterns in `let` and `fn`
* References, and operators `!` and `:=`
* Operators: div mod ^ :: @ `before` `not`
Expand Down
20 changes: 20 additions & 0 deletions src/main/java/net/hydromatic/sml/ast/Ast.java
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,26 @@ public InfixCall(Pos pos, Op op, Exp a0, Exp a1) {
}
}

/** If ... else expression. */
public static class If extends Exp {
public final Exp condition;
public final Exp ifTrue;
public final Exp ifFalse;

public If(Pos pos, Exp condition, Exp ifTrue, Exp ifFalse) {
super(pos, Op.IF);
this.condition = Objects.requireNonNull(condition);
this.ifTrue = Objects.requireNonNull(ifTrue);
this.ifFalse = Objects.requireNonNull(ifFalse);
}

@Override AstWriter unparse(AstWriter w, int left, int right) {
return w.append("if ").append(condition, 0, 0)
.append(" then ").append(ifTrue, 0, 0)
.append(" else ").append(ifFalse, 0, right);
}
}

/** "Let" expression. */
public static class LetExp extends Exp {
public final VarDecl decl;
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/net/hydromatic/sml/ast/AstBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ public Ast.Fn fn(Pos pos, Ast.Match match) {
public Ast.Apply apply(Ast.Exp fn, Ast.Exp arg) {
return new Ast.Apply(fn.pos.plus(arg.pos), fn, arg);
}

public Ast.Exp ifThenElse(Pos pos, Ast.Exp condition, Ast.Exp ifTrue,
Ast.Exp ifFalse) {
return new Ast.If(pos, condition, ifTrue, ifFalse);
}
}

// End AstBuilder.java
3 changes: 2 additions & 1 deletion src/main/java/net/hydromatic/sml/ast/Op.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ public enum Op {
LET,
MATCH,
FN,
APPLY;
APPLY,
IF;

/** Padded name, e.g. " : ". */
public final String padded;
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/net/hydromatic/sml/compile/Compiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ private Type deduceType(Environment env, AstNode node) {
throw new AssertionError("not found: " + id.name);
}
return binding.type;
case IF:
// TODO: check that condition has type boolean
// TODO: check that ifTrue has same type as ifFalse
final Ast.If if_ = (Ast.If) node;
return deduceType(env, if_.ifTrue);
case FN:
final Ast.Fn fn = (Ast.Fn) node;
return deduceType(env, fn.match);
Expand Down Expand Up @@ -155,6 +160,12 @@ public Code compile(Environment env, Ast.Exp expression) {
case ID:
final Ast.Id id = (Ast.Id) expression;
return Codes.get(id.name);
case IF:
final Ast.If if_ = (Ast.If) expression;
final Code conditionCode = compile(env, if_.condition);
final Code trueCode = compile(env, if_.ifTrue);
final Code falseCode = compile(env, if_.ifFalse);
return Codes.ifThenElse(conditionCode, trueCode, falseCode);
case LET:
final Ast.LetExp let = (Ast.LetExp) expression;
final List<Codes.NameTypeCode> varCodes = new ArrayList<>();
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/net/hydromatic/sml/eval/Codes.java
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,14 @@ public static Code apply(Code fnCode, Code argCode) {
};
}

public static Code ifThenElse(Code condition, Code ifTrue,
Code ifFalse) {
return env -> {
final boolean b = (Boolean) condition.eval(env);
return (b ? ifTrue : ifFalse).eval(env);
};
}

/** A (name, type, code) triple. */
public static class NameTypeCode {
public final String name;
Expand Down
21 changes: 21 additions & 0 deletions src/main/javacc/SmlParser.jj
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,22 @@ Id identifier() :
}
}

/** Parses a "if ... then ... else ..." expression. */
Exp ifThenElse() :
{
final Span span;
final Exp condition;
final Exp ifTrue;
final Exp ifFalse;
}
{
<IF> { span = Span.of(getPos()); } condition = expression()
<THEN> ifTrue = expression()
<ELSE> ifFalse = expression() {
return ast.ifThenElse(span.end(this), condition, ifTrue, ifFalse);
}
}

/** Parses a "let ... in expression end" expression. */
Exp let() :
{
Expand Down Expand Up @@ -300,6 +316,8 @@ Exp atom() :
e = let() { return e; }
|
e = fn() { return e; }
|
e = ifThenElse() { return e; }
|
<LPAREN> e = expression() <RPAREN> { return e; }
}
Expand Down Expand Up @@ -412,11 +430,14 @@ AstNode statementSemicolon() :
{
< AND: "AND" >
| < ANDALSO: "ANDALSO" >
| < ELSE: "ELSE" >
| < END: "END" >
| < FN: "FN" >
| < IF: "IF" >
| < IN: "IN" >
| < LET: "LET" >
| < ORELSE: "ORELSE" >
| < THEN: "THEN" >
| < VAL: "VAL" >
}

Expand Down
30 changes: 26 additions & 4 deletions src/test/java/net/hydromatic/sml/MainTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,19 @@ public class MainTest {
checkStmt("1 + (2 + (3 + (4)))",
isAst(AstNode.class, "1 + (2 + (3 + 4))"));

checkStmt("let val x = 2 in x + (3 + x) + x end",
isAst(AstNode.class, "let val x = 2 in x + (3 + x) + x end"));
assertParseSame("let val x = 2 in x + (3 + x) + x end");

checkStmt("let val x = 2 and y = 3 in x + y end",
isAst(AstNode.class, "let val x = 2 and y = 3 in x + y end"));
assertParseSame("let val x = 2 and y = 3 in x + y end");

// if
assertParseSame("if true then 1 else 2");

// if ... else if
assertParseSame("if true then 1 else if false then 2 else 3");
}

private void assertParseSame(String ml) {
checkStmt(ml, isAst(AstNode.class, ml));
}

@Test public void testType() {
Expand All @@ -117,6 +125,7 @@ public class MainTest {
assertType("true andalso false", is("bool"));
assertType("fn x => x + 1", is("int -> int"));
assertType("fn x => fn y => x + y", is("int -> int -> int"));
assertType("if true then 1.0 else 2.0", is("real"));
}

private void assertType(String ml, Matcher<String> matcher) {
Expand Down Expand Up @@ -150,6 +159,19 @@ private void assertType(String ml, Matcher<String> matcher) {
checkEval("false andalso false orelse true", is(true));
checkEval("false andalso true orelse true", is(true));

// if
checkEval("if true then 1 else 2", is(1));
checkEval("if false then 1 else if true then 2 else 3", is(2));
checkEval("if false\n"
+ "then\n"
+ " if true then 2 else 3\n"
+ "else 4", is(4));
checkEval("if false\n"
+ "then\n"
+ " if true then 2 else 3\n"
+ "else\n"
+ " if false then 4 else 5", is(5));

// let
checkEval("let val x = 1 in x + 2 end", is(3));

Expand Down

0 comments on commit 18eb9b5

Please sign in to comment.