Browse Source

complete lambda-service v1.0

superfree 5 years ago
parent
commit
b24dcc4e3a
28 changed files with 1399 additions and 127 deletions
  1. BIN
      .DS_Store
  2. 65 0
      lambda-service/src/main/java/com/example/lambda/service/interpreter/BasicPattern.java
  3. 187 0
      lambda-service/src/main/java/com/example/lambda/service/interpreter/Interpreter.java
  4. 0 5
      lambda-service/src/main/java/com/example/lambda/service/interpreter/InterpreterState.java
  5. 0 16
      lambda-service/src/main/java/com/example/lambda/service/interpreter/LambdaInterpreter.java
  6. 126 0
      lambda-service/src/main/java/com/example/lambda/service/interpreter/Lexer.java
  7. 135 0
      lambda-service/src/main/java/com/example/lambda/service/interpreter/Parser.java
  8. 0 36
      lambda-service/src/main/java/com/example/lambda/service/interpreter/Result.java
  9. 8 0
      lambda-service/src/main/java/com/example/lambda/service/interpreter/error/ErrorType.java
  10. 61 0
      lambda-service/src/main/java/com/example/lambda/service/interpreter/error/InterpreterError.java
  11. 22 0
      lambda-service/src/main/java/com/example/lambda/service/interpreter/error/InterpreterException.java
  12. 65 0
      lambda-service/src/main/java/com/example/lambda/service/interpreter/result/InterpreterResult.java
  13. 7 0
      lambda-service/src/main/java/com/example/lambda/service/interpreter/result/InterpreterState.java
  14. 29 11
      lambda-service/src/main/java/com/example/lambda/service/interpreter/type/Abstract.java
  15. 16 1
      lambda-service/src/main/java/com/example/lambda/service/interpreter/type/Application.java
  16. 4 7
      lambda-service/src/main/java/com/example/lambda/service/interpreter/type/Ast.java
  17. 257 0
      lambda-service/src/main/java/com/example/lambda/service/interpreter/type/AstUtils.java
  18. 20 5
      lambda-service/src/main/java/com/example/lambda/service/interpreter/type/Identifier.java
  19. 35 0
      lambda-service/src/main/java/com/example/lambda/service/interpreter/type/Token.java
  20. 20 0
      lambda-service/src/main/java/com/example/lambda/service/interpreter/type/TokenType.java
  21. 27 31
      lambda-service/src/main/java/com/example/lambda/service/web/LambdaService.java
  22. 8 8
      lambda-service/src/main/java/com/example/lambda/service/web/typedata/IRAtom.java
  23. 8 6
      lambda-service/src/main/java/com/example/lambda/service/web/utils/ResponseUtils.java
  24. 1 1
      lambda-service/src/main/java/com/example/lambda/service/web/utils/SVGRenderer.java
  25. 22 0
      lambda-service/src/test/java/com/example/lambda/service/interpreter/BasicPatternTest.java
  26. 102 0
      lambda-service/src/test/java/com/example/lambda/service/interpreter/InterpreterTest.java
  27. 71 0
      lambda-service/src/test/java/com/example/lambda/service/interpreter/ParserTest.java
  28. 103 0
      lambda-service/src/test/java/com/example/lambda/service/interpreter/type/AstUtilsTest.java

BIN
.DS_Store


+ 65 - 0
lambda-service/src/main/java/com/example/lambda/service/interpreter/BasicPattern.java

@@ -0,0 +1,65 @@
+package com.example.lambda.service.interpreter;
+
+public enum BasicPattern {
+    /* 数字 & 算数运算符 */
+    ZERO("\\f.\\x.x"),
+    ONE("\\f.\\x.f x"),
+    TWO("\\f.\\x.f (f x)"),
+    THREE("\\f.\\x.f (f (f x))"),
+    FOUR("\\f.\\x.f (f (f (f x)))"),
+    FIVE("\\f.\\x.f (f (f (f (f x))))"),
+    SIX("\\f.\\x.f (f (f (f (f (f x)))))"),
+    SEVEN("\\f.\\x.f (f (f (f (f (f (f x))))))"),
+    EIGHT("\\f.\\x.f (f (f (f (f (f (f (f x)))))))"),
+    NINE("\\f.\\x.f (f (f (f (f (f (f (f (f x))))))))"),
+    TEN("\\f.\\x.f (f (f (f (f (f (f (f (f (f x)))))))))"),
+    SUCC("\\n.\\f.\\x.f (n f x)"),
+    PLUS("\\m.\\n.m SUCC n"),
+    MULT("\\m.\\n.\\f.m (n f)"),
+    POW("\\b.\\e.e b"),
+    PRED("\\n.\\f.\\x.n (\\g.\\h.h (g f)) (\\u.x) (\\u.u)"),
+    SUB("\\m.\\n.n PRED m"),
+
+    /* 逻辑 & 逻辑运算符 */
+    TRUE("\\x.\\y.x"),
+    FALSE("\\x.\\y.y"),
+    AND("\\p.\\q.p q p"),
+    OR("\\p.\\q.p p q"),
+    NOT("\\p.\\a.\\b.p b a"),
+    IF("\\p.\\a.\\b.p a b"),
+
+    /* 判断 & 比较运算符 */
+    IS_ZERO("\\n.n (\\x.FALSE) TRUE"), // == 0 ?
+    LEQ("\\m.\\n.IS_ZERO (SUB m n)"), // >= 0 ?
+    EQ("\\m.\\n.AND (LEQ m n) (LEQ n m)"), // == 0 ?
+
+    /* 自定义逻辑连接词 */
+    MY_NOT("\\x.IF x FALSE TRUE"),
+    MY_AND("\\x.\\y.IF x y FALSE"),
+    MY_OR("\\x.\\y.IF x TRUE y"),
+    MAX("\\m.\\n.IF (LEQ m n) n m"),
+    MIN("\\m.\\n.IF (LEQ m n) m n"),
+
+    /* 递归 */
+    FACT1("\\f.\\n.IF (IS_ZERO n) ONE (MULT n (f f (PRED n)))"),
+    FACT("FACT1 FACT1"), // 阶层
+    W("\\x.x x"),
+    FACTD("W (\\f.\\n.IF (IS_ZERO n) ONE (MULT n (f f (PRED n))))"),
+    ADD("W (\\f.\\n.\\m.IF (IS_ZERO m) n (f f (SUCC n) (PRED m)))"),
+
+    /* 不动点 */
+    Y("\\g.(\\x.g (x x)) \\x.g (x x)"),
+    FACT2("\\f.\\n.IF (IS_ZERO n) ONE (MULT n (f (PRED n)))"),
+    FACTY("Y FACT2"),
+    ;
+
+    private String pattern;
+
+    BasicPattern(String pattern) {
+        this.pattern = pattern;
+    }
+
+    public String getPattern() {
+        return pattern;
+    }
+}

+ 187 - 0
lambda-service/src/main/java/com/example/lambda/service/interpreter/Interpreter.java

@@ -0,0 +1,187 @@
+package com.example.lambda.service.interpreter;
+
+import com.example.lambda.service.interpreter.error.ErrorType;
+import com.example.lambda.service.interpreter.error.InterpreterError;
+import com.example.lambda.service.interpreter.error.InterpreterException;
+import com.example.lambda.service.interpreter.type.*;
+import com.example.lambda.service.interpreter.result.InterpreterResult;
+import org.springframework.stereotype.Component;
+
+import static com.example.lambda.service.interpreter.type.AstUtils.*;
+
+@Component
+public class Interpreter {
+
+    private Parser parser = new Parser();
+
+    private Ast ast;
+
+    private boolean doClear;
+
+    public Ast getAst() {
+        return ast;
+    }
+
+    /**
+     * 表达式化简
+     *
+     * @return
+     */
+    public Ast eval() {
+        doClear = false;
+        ast = _eval(ast);
+        if (doClear) AstUtils.clearIndex(ast);
+        return ast;
+    }
+
+    public Ast eval(String pattern) {
+        parse(pattern);
+        return eval();
+    }
+
+    public Ast eval(Ast ast) {
+        this.ast = ast;
+        return eval();
+    }
+
+    private Ast _eval(Ast ast) {
+        while (true) {
+//            System.out.println(ast);
+//            AstUtils.show(ast);
+            if (isApp(ast)) {
+                /* app = ( ?, ? ) */
+                Application app = (Application) ast;
+                if (isApp(app.getLeft())) {
+                    /* app = ( Application, ? ) */
+                    if (!isId(app.getLeft())) app.setLeft(_eval(app.getLeft()));
+                    if (isApp(app.getLeft())) {
+                        /* app = ( Application, ? ) */
+                        // left 解析完依旧为 Application,则不可规约
+                        // 再解析 right 后就返回
+                        if (!isId(app.getRight())) app.setRight(_eval(app.getRight()));
+                        return app;
+                    }
+                } else if (isAbs(app.getLeft())) {
+                    /* app = ( Abstract, ? ) */
+                    Abstract left = (Abstract) app.getLeft();
+                    if (!isId(app.getRight())) app.setRight(_eval(app.getRight()));
+                    // 进行 beta 规约
+                    // \A.B C => B(C)
+                    ast = substitue(left.getBody(), app.getRight());
+                } else {
+                    /* app = ( Identifier, ? ) */
+                    if (!isId(app.getRight())) app.setRight(_eval(app.getRight()));
+                    return app;
+                }
+            } else if (isAbs(ast)) {
+                /* ast = Abstract */
+                Abstract abs = (Abstract) ast;
+                if (!isId(abs.getBody())) abs.setBody(_eval(abs.getBody()));
+                return abs;
+            } else if (isId(ast)) {
+                return ast;
+            } else {
+                throw new InterpreterException("未知的 Ast 类型", ast.getIndex(), ErrorType.UNKNOWN_TOKEN);
+            }
+        }
+    }
+
+    /**
+     * beta 规约(替换)
+     *
+     * @param node
+     * @param val
+     * @return
+     */
+    private Ast substitue(Ast node, Ast val) {
+        doClear = true;
+        val = shift(1, val, 0);
+        node = subst(node, val, 0);
+        return shift(-1, node, 0);
+    }
+
+    private Ast subst(Ast node, Ast val, int depth) {
+        if (isApp(node)) {
+            Application app = (Application) node;
+            return new Application(
+                    subst(app.getLeft(), val, depth),
+                    subst(app.getRight(), val, depth)
+            );
+        } else if (isAbs(node)) {
+            Abstract abs = (Abstract) node;
+            return new Abstract(
+                    abs.getIndex(),
+                    abs.getParam(),
+                    subst(abs.getBody(), val, depth + 1)
+            );
+        } else if (isId(node)) {
+            if (depth == ((Identifier) node).getId()) {
+                return shift(depth, val, 0);
+            } else {
+                return node;
+            }
+        }
+        return null;
+    }
+
+    private Ast shift(int by, Ast node, int from) {
+        if (isApp(node)) {
+            Application app = (Application) node;
+            return new Application(
+                    shift(by, app.getLeft(), from),
+                    shift(by, app.getRight(), from)
+            );
+        } else if (isAbs(node)) {
+            Abstract abs = (Abstract) node;
+            return new Abstract(
+                    abs.getIndex(),
+                    abs.getParam(),
+                    shift(by, abs.getBody(), from + 1)
+            );
+        } else if (isId(node)) {
+            Identifier identifier = (Identifier) node;
+            int id = identifier.getId();
+            return new Identifier(
+                    identifier.getIndex(),
+                    id + (id >= from ? by : 0),
+                    identifier.getName()
+            );
+        }
+        return null;
+    }
+
+    /**
+     * 表达式解析
+     *
+     * @param pattern
+     * @return
+     */
+    public Ast parse(String pattern) {
+        ast = parser.parse(pattern);
+        return ast;
+    }
+
+    /**
+     * 解释器对外统一接口
+     *
+     * @param pattern
+     * @return
+     */
+    public InterpreterResult solve(String pattern) {
+        try {
+            parse(pattern);
+            eval();
+            return InterpreterResult.success(ast);
+        } catch (InterpreterException e) {
+            InterpreterError error = new InterpreterError(pattern, e);
+            System.out.println(error);
+            e.printStackTrace();
+
+            return InterpreterResult.innerError(error);
+        } catch (StackOverflowError e) {
+            e.printStackTrace();
+            return InterpreterResult.runtimeError("表达式太复杂了爆栈了,赶紧告诉学长他写的太烂了");
+        }
+    }
+
+}

+ 0 - 5
lambda-service/src/main/java/com/example/lambda/service/interpreter/InterpreterState.java

@@ -1,5 +0,0 @@
-package com.example.lambda.service.interpreter;
-
-public enum InterpreterState {
-    SUCCESS, ERROR
-}

+ 0 - 16
lambda-service/src/main/java/com/example/lambda/service/interpreter/LambdaInterpreter.java

@@ -1,16 +0,0 @@
-package com.example.lambda.service.interpreter;
-
-import com.example.lambda.service.interpreter.type.Ast;
-import org.springframework.stereotype.Component;
-
-@Component
-public class LambdaInterpreter {
-
-    public Result solve(String pattern) {
-        Result result = new Result();
-        result.setState(InterpreterState.SUCCESS);
-        result.setAst(Ast.ZERO());
-        return result;
-    }
-
-}

+ 126 - 0
lambda-service/src/main/java/com/example/lambda/service/interpreter/Lexer.java

@@ -0,0 +1,126 @@
+package com.example.lambda.service.interpreter;
+
+import com.example.lambda.service.interpreter.error.ErrorType;
+import com.example.lambda.service.interpreter.error.InterpreterException;
+import com.example.lambda.service.interpreter.type.Token;
+import com.example.lambda.service.interpreter.type.TokenType;
+
+import java.util.regex.Pattern;
+
+public class Lexer {
+
+    private int recentIndex;
+    private int index;
+    private String pattern;
+    private TokenType token;
+    private String tokenVal;
+
+    /******************
+     * setter / getter
+     ******************/
+    public void setPattern(String pattern) {
+        this.pattern = pattern;
+        this.index = 0;
+        nextToken();
+    }
+
+    public Token getToken() {
+        return new Token(recentIndex, token, (token == TokenType.LCID ? tokenVal : token.getVal()));
+    }
+
+    public String getPattern() {
+        return pattern;
+    }
+
+    /**
+     * 取下一个字符
+     *
+     * @return
+     */
+    private char nextChar() {
+        if (index >= pattern.length()) return '\0';
+        return pattern.charAt(index++);
+    }
+
+    /**
+     * 取下一个 token
+     *
+     * @return
+     */
+    public TokenType nextToken() {
+        char c;
+        String whiteSpace = "\\s";
+        String lcidTitle = "[a-zA-Z]";
+        String lcidContent = "[_a-zA-Z0-9]";
+        do {
+            c = nextChar();
+        } while (Pattern.matches(whiteSpace, "" + c));
+        // 记录该 token 首字符下标
+        recentIndex = index - 1;
+        switch (c) {
+            case '\\':
+                token = TokenType.LAMBDA;
+                break;
+            case '.':
+                token = TokenType.DOT;
+                break;
+            case '(':
+                token = TokenType.LP;
+                break;
+            case ')':
+                token = TokenType.RP;
+                break;
+            case '\0':
+                token = TokenType.EOF;
+                break;
+            default:
+                if (Pattern.matches(lcidTitle, "" + c)) {
+                    StringBuilder s = new StringBuilder();
+                    do {
+                        s.append(c);
+                        c = nextChar();
+                    } while (Pattern.matches(lcidContent, "" + c));
+                    if (c != '\0') index--;
+                    token = TokenType.LCID;
+                    tokenVal = s.toString();
+                }
+        }
+//        System.out.println(getToken());
+        return token;
+    }
+
+    /**
+     * 断言 token 的类型,并取下一个
+     *
+     * @param t
+     */
+    public Token match(TokenType t) {
+        if (token != t)
+            throw new InterpreterException("期望类型=" + t + ", 实际类型=" + token, recentIndex, ErrorType.SYNTAX_ERROR);
+        Token token = getToken();
+        nextToken();
+        return token;
+    }
+
+    /**
+     * 测试 token 的类型
+     *
+     * @param t
+     * @return
+     */
+    public boolean test(TokenType t) {
+        return token == t;
+    }
+
+    /**
+     * 测试 token 类型,true 则直接取下一个
+     *
+     * @param t
+     * @return
+     */
+    public boolean skip(TokenType t) {
+        boolean b = token == t;
+        if (b) nextToken();
+        return b;
+    }
+}

+ 135 - 0
lambda-service/src/main/java/com/example/lambda/service/interpreter/Parser.java

@@ -0,0 +1,135 @@
+package com.example.lambda.service.interpreter;
+
+import com.example.lambda.service.interpreter.error.ErrorType;
+import com.example.lambda.service.interpreter.error.InterpreterException;
+import com.example.lambda.service.interpreter.type.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+public class Parser {
+
+    private Lexer lexer = new Lexer();
+
+    /**
+     * 获取表达式所有 token(令牌)
+     *
+     * @param pattern
+     * @return
+     */
+    public List<Token> tokens(String pattern) {
+        lexer.setPattern(pattern);
+        List<Token> res = new ArrayList<>();
+        while (!lexer.test(TokenType.EOF)) {
+            res.add(lexer.getToken());
+            lexer.nextToken();
+        }
+        res.add(lexer.getToken());
+        return res;
+    }
+
+    /**
+     * 表达式解析
+     *
+     * @param pattern
+     * @return
+     */
+    public Ast parse(String pattern) {
+        lexer.setPattern(pattern);
+        return term(new ArrayList<>());
+    }
+
+    /**
+     * 表达式
+     * term := \ LCID . term | application
+     *
+     * @param ctx
+     * @return
+     */
+    private Ast term(List<Identifier> ctx) {
+        if (lexer.test(TokenType.LAMBDA)) {
+            /* term := \ LCID . term */
+            int index = lexer.match(TokenType.LAMBDA).getIndex();
+            Token lcid = lexer.match(TokenType.LCID);
+            Identifier param = new Identifier(lcid.getIndex(), -1, lcid.getVal());
+            lexer.match(TokenType.DOT);
+
+            ctx.add(0, param);
+            Ast body = term(ctx);
+            if (body == null) {
+                Token wrongToken = lexer.getToken();
+                throw new InterpreterException("期望类型=term, 实际类型=" + wrongToken.getType(), wrongToken.getIndex(), ErrorType.SYNTAX_ERROR);
+            }
+            ctx.remove(0);
+            return new Abstract(index, param, body);
+        } else {
+            /* term := application */
+            return application(ctx);
+        }
+    }
+
+    /**
+     * 应用
+     * application := atom | application atom
+     *
+     * @param ctx
+     * @return
+     */
+    private Ast application(List<Identifier> ctx) {
+        /* application := atom | application atom */
+        Ast left = atom(ctx);
+        while (true) {
+            Ast right = atom(ctx);
+            if (right == null) return left;
+            else left = new Application(left, right);
+        }
+    }
+
+    /**
+     * 原子
+     * atom := ( term ) | LCID | EOF
+     *
+     * @param ctx
+     * @return
+     */
+    private Ast atom(List<Identifier> ctx) {
+        if (lexer.skip(TokenType.LP)) {
+            /* atom := ( term ) */
+            Ast ast = term(ctx);
+            lexer.match(TokenType.RP);
+            return ast;
+        } else if (lexer.test(TokenType.LAMBDA)) {
+            /* atom := term */
+            return term(ctx);
+        } else if (lexer.test(TokenType.LCID)) {
+            /* term := LCID */
+            Token token = lexer.match(TokenType.LCID);
+            String constLcid = "[_A-Z0-9]+";
+            if (Pattern.matches(constLcid, token.getVal())) {
+                /* 全大写 := 预定义字面量 */
+//                return new Const(token.getIndex(), token.getVal());
+                try {
+                    return AstUtils.getConstAst(token.getVal());
+                } catch (IllegalArgumentException e) {
+                    throw new InterpreterException("未知的常量字面量 - " + token.getVal(), token.getIndex(), ErrorType.UNKNOWN_CONST);
+                }
+            } else {
+                /* 自由变量 */
+                int id = -1;
+                for (Identifier param : ctx) {
+                    if (token.getVal().equals(param.getName())) {
+                        id = ctx.indexOf(param);
+                        break;
+                    }
+                }
+                if (id < 0)
+                    throw new InterpreterException("未绑定自由变量", token.getIndex(), ErrorType.WILD_IDENTIFIER);
+                return new Identifier(token.getIndex(), id, token.getVal());
+            }
+        } else {
+            return null;
+        }
+    }
+
+}

+ 0 - 36
lambda-service/src/main/java/com/example/lambda/service/interpreter/Result.java

@@ -1,36 +0,0 @@
-package com.example.lambda.service.interpreter;
-
-import com.example.lambda.service.interpreter.type.Ast;
-
-public class Result {
-
-    private InterpreterState state;
-
-    private String msg;
-
-    private Ast ast;
-
-    public InterpreterState getState() {
-        return state;
-    }
-
-    public void setState(InterpreterState state) {
-        this.state = state;
-    }
-
-    public String getMsg() {
-        return msg;
-    }
-
-    public void setMsg(String msg) {
-        this.msg = msg;
-    }
-
-    public Ast getAst() {
-        return ast;
-    }
-
-    public void setAst(Ast ast) {
-        this.ast = ast;
-    }
-}

+ 8 - 0
lambda-service/src/main/java/com/example/lambda/service/interpreter/error/ErrorType.java

@@ -0,0 +1,8 @@
+package com.example.lambda.service.interpreter.error;
+
+public enum ErrorType {
+    SYNTAX_ERROR,
+    WILD_IDENTIFIER,
+    UNKNOWN_CONST,
+    UNKNOWN_TOKEN // AstUtils.show
+}

+ 61 - 0
lambda-service/src/main/java/com/example/lambda/service/interpreter/error/InterpreterError.java

@@ -0,0 +1,61 @@
+package com.example.lambda.service.interpreter.error;
+
+public class InterpreterError {
+
+    private String pattern;
+    private Integer index;
+    private ErrorType type;
+    private String msg;
+
+    public InterpreterError() {
+    }
+
+    public InterpreterError(String pattern, InterpreterException e) {
+        this.pattern = pattern;
+        this.index = e.getIndex();
+        this.type = e.getType();
+        this.msg = e.getMessage();
+    }
+
+    public String getPattern() {
+        return pattern;
+    }
+
+    public void setPattern(String pattern) {
+        this.pattern = pattern;
+    }
+
+    public Integer getIndex() {
+        return index;
+    }
+
+    public void setIndex(Integer index) {
+        this.index = index;
+    }
+
+    public ErrorType getType() {
+        return type;
+    }
+
+    public void setType(ErrorType type) {
+        this.type = type;
+    }
+
+    public String getMsg() {
+        return msg;
+    }
+
+    public void setMsg(String msg) {
+        this.msg = msg;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder s = new StringBuilder();
+        s.append(type + ": " + msg + '\n');
+        s.append("pattern: " + pattern + '\n');
+        for (int i = 0; i < 9 + index; i++) s.append(" ");
+        s.append("^");
+        return s.toString();
+    }
+}

+ 22 - 0
lambda-service/src/main/java/com/example/lambda/service/interpreter/error/InterpreterException.java

@@ -0,0 +1,22 @@
+package com.example.lambda.service.interpreter.error;
+
+public class InterpreterException extends RuntimeException {
+
+    private int index;
+    private ErrorType type;
+
+    public InterpreterException(String message, int index, ErrorType type) {
+        super(message);
+        this.index = index;
+        this.type = type;
+    }
+
+    public int getIndex() {
+        return index;
+    }
+
+    public ErrorType getType() {
+        return type;
+    }
+
+}

+ 65 - 0
lambda-service/src/main/java/com/example/lambda/service/interpreter/result/InterpreterResult.java

@@ -0,0 +1,65 @@
+package com.example.lambda.service.interpreter.result;
+
+import com.example.lambda.service.interpreter.error.InterpreterError;
+import com.example.lambda.service.interpreter.type.Ast;
+
+public class InterpreterResult {
+
+    private InterpreterState state;
+
+    private Object data;
+
+    public InterpreterResult() {
+    }
+
+    public InterpreterResult(InterpreterState state, Object data) {
+        this.state = state;
+        this.data = data;
+    }
+
+    /**
+     * SUCCESS 绑定返回 Ast
+     *
+     * @param ast
+     * @return
+     */
+    public static InterpreterResult success(Ast ast) {
+        return new InterpreterResult(InterpreterState.SUCCESS, ast);
+    }
+
+    /**
+     * INTERPRETER_ERROR 绑定返回 InterpreterError
+     *
+     * @param error
+     * @return
+     */
+    public static InterpreterResult innerError(InterpreterError error) {
+        return new InterpreterResult(InterpreterState.INTERPRETER_ERROR, error);
+    }
+
+    /**
+     * RUNTIME_ERROR 绑定返回 String
+     *
+     * @param msg
+     * @return
+     */
+    public static InterpreterResult runtimeError(String msg) {
+        return new InterpreterResult(InterpreterState.RUNTIME_ERROR, msg);
+    }
+
+    public InterpreterState getState() {
+        return state;
+    }
+
+    public void setState(InterpreterState state) {
+        this.state = state;
+    }
+
+    public Object getData() {
+        return data;
+    }
+
+    public void setData(Object data) {
+        this.data = data;
+    }
+}

+ 7 - 0
lambda-service/src/main/java/com/example/lambda/service/interpreter/result/InterpreterState.java

@@ -0,0 +1,7 @@
+package com.example.lambda.service.interpreter.result;
+
+public enum InterpreterState {
+    SUCCESS,
+    INTERPRETER_ERROR,
+    RUNTIME_ERROR
+}

+ 29 - 11
lambda-service/src/main/java/com/example/lambda/service/interpreter/type/Abstract.java

@@ -1,29 +1,47 @@
 package com.example.lambda.service.interpreter.type;
 
-public class Abstract implements Ast {
+public class Abstract extends Ast {
 
-    private Atom param;
+    private Integer index;
 
-    private Ast sub;
+    private Identifier param;
 
-    public Abstract(Atom param, Ast sub) {
+    private Ast body;
+
+    public Abstract(Integer index, Identifier param, Ast body) {
+        this.index = index;
         this.param = param;
-        this.sub = sub;
+        this.body = body;
+    }
+
+    @Override
+    public Integer getIndex() {
+        return index;
     }
 
-    public Atom getParam() {
+    @Override
+    public void setIndex(Integer index) {
+        this.index = index;
+    }
+
+    public Identifier getParam() {
         return param;
     }
 
-    public void setParam(Atom param) {
+    public void setParam(Identifier param) {
         this.param = param;
     }
 
-    public Ast getSub() {
-        return sub;
+    public Ast getBody() {
+        return body;
+    }
+
+    public void setBody(Ast body) {
+        this.body = body;
     }
 
-    public void setSub(Ast sub) {
-        this.sub = sub;
+    @Override
+    public String toString() {
+        return "(\\" + param + "." + body + ")";
     }
 }

+ 16 - 1
lambda-service/src/main/java/com/example/lambda/service/interpreter/type/Application.java

@@ -1,6 +1,6 @@
 package com.example.lambda.service.interpreter.type;
 
-public class Application implements Ast {
+public class Application extends Ast {
 
     private Ast left;
 
@@ -11,6 +11,16 @@ public class Application implements Ast {
         this.right = right;
     }
 
+    @Override
+    public Integer getIndex() {
+        return left.getIndex();
+    }
+
+    @Override
+    public void setIndex(Integer index) {
+        left.setIndex(index);
+    }
+
     public Ast getLeft() {
         return left;
     }
@@ -26,4 +36,9 @@ public class Application implements Ast {
     public void setRight(Ast right) {
         this.right = right;
     }
+
+    @Override
+    public String toString() {
+        return "(" + left + " " + right + ")";
+    }
 }

+ 4 - 7
lambda-service/src/main/java/com/example/lambda/service/interpreter/type/Ast.java

@@ -1,10 +1,7 @@
 package com.example.lambda.service.interpreter.type;
 
-public interface Ast {
-    static Ast ZERO() {
-        Atom f = new Atom(1, "f");
-        Atom x = new Atom(2, "x");
-        Ast zero = new Abstract(f, new Abstract(x, x));
-        return zero;
-    }
+public abstract class Ast {
+    public abstract Integer getIndex();
+
+    public abstract void setIndex(Integer index);
 }

+ 257 - 0
lambda-service/src/main/java/com/example/lambda/service/interpreter/type/AstUtils.java

@@ -0,0 +1,257 @@
+package com.example.lambda.service.interpreter.type;
+
+import com.example.lambda.service.interpreter.BasicPattern;
+import com.example.lambda.service.interpreter.Parser;
+import com.example.lambda.service.interpreter.error.ErrorType;
+import com.example.lambda.service.interpreter.error.InterpreterException;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class AstUtils {
+
+    /**
+     * 打印 Ast 树
+     *
+     * @param ast
+     */
+    public static void show(Ast ast) {
+        if (ast != null) show(ast, "");
+        else System.out.println("AstUtils.show: error occur, parse failure");
+    }
+
+    private static void show(Ast ast, String prefix) {
+        System.out.print(prefix);
+        String nextPre = prefix + "| ";
+        int index = ast.getIndex();
+        if (ast instanceof Abstract) {
+            if (index < 0) {
+                System.out.println("Abstract:");
+            } else {
+                System.out.println("Abstract: index=" + index);
+            }
+            show(((Abstract) ast).getParam(), nextPre);
+            show(((Abstract) ast).getBody(), nextPre);
+        } else if (ast instanceof Application) {
+            System.out.println("Application:");
+            show(((Application) ast).getLeft(), nextPre);
+            show(((Application) ast).getRight(), nextPre);
+        } else if (ast instanceof Identifier) {
+            if (index < 0) {
+                System.out.print("Identifier");
+            } else {
+                System.out.print("Identifier: index=" + index);
+            }
+            Identifier identifier = (Identifier) ast;
+            System.out.println("(" +
+                    identifier.getId() + ":" +
+                    identifier.getName() + ")");
+        } else {
+            throw new InterpreterException("未知的 Ast 类型", index, ErrorType.UNKNOWN_TOKEN);
+        }
+    }
+
+    public static void showSimple(Ast ast) {
+        if (ast != null) showSimple(ast, "");
+        else System.out.println("AstUtils.show: error occur, parse failure");
+    }
+
+    private static void showSimple(Ast ast, String prefix) {
+        System.out.print(prefix);
+        String nextPre = prefix + "| ";
+        int index = ast.getIndex();
+        if (ast instanceof Abstract) {
+            System.out.println("\\" + ((Abstract) ast).getParam().getName());
+            showSimple(((Abstract) ast).getBody(), nextPre);
+        } else if (ast instanceof Application) {
+            System.out.println("App:");
+            showSimple(((Application) ast).getLeft(), nextPre);
+            showSimple(((Application) ast).getRight(), nextPre);
+        } else if (ast instanceof Identifier) {
+            Identifier identifier = (Identifier) ast;
+            if (identifier.getId() < 0) {
+                System.out.println(identifier.getName());
+            } else {
+                System.out.println(identifier.getName() + ":" + identifier.getId());
+            }
+        } else {
+            throw new InterpreterException("未知的 Ast 类型", index, ErrorType.UNKNOWN_TOKEN);
+        }
+    }
+
+    /**
+     * 根据字面量获取对应 Ast
+     *
+     * @param name
+     * @return
+     */
+    public static Ast getConstAst(String name) {
+        name = name.toUpperCase();
+        return new Parser().parse(BasicPattern.valueOf(name).getPattern());
+    }
+
+    /**
+     * 转为匿名语法树
+     *
+     * @param ast
+     * @return
+     */
+    public static Ast castAnonymous(Ast ast) {
+        anonymousId = 0;
+        ctx = new ArrayList<>();
+        try {
+            return _castAnonymous(ast);
+        } catch (IndexOutOfBoundsException e) {
+            // 属于某个 Abstract 的 body(存在更外层的 id)
+            return null;
+        }
+    }
+
+    private static int anonymousId;
+    private static List<Identifier> ctx;
+
+    private static Ast _castAnonymous(Ast ast) {
+        if (ast instanceof Abstract) {
+            Abstract sample = (Abstract) ast;
+            Identifier param = (Identifier) _castAnonymous(sample.getParam());
+            ctx.add(0, param);
+            Ast body = _castAnonymous(sample.getBody());
+            ctx.remove(0);
+            return new Abstract(sample.getIndex(), param, body);
+        } else if (ast instanceof Application) {
+            Application sample = (Application) ast;
+            Ast left = _castAnonymous(sample.getLeft());
+            Ast right = _castAnonymous(sample.getRight());
+            return new Application(left, right);
+        } else if (ast instanceof Identifier) {
+            Identifier sample = (Identifier) ast;
+            String name = sample.getId() < 0 ? "#" + (++anonymousId) : ctx.get(sample.getId()).getName();
+            return new Identifier(sample.getIndex(), sample.getId(), name);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * 比较两个 Ast 是否相同
+     *
+     * @param a
+     * @param b
+     * @return
+     */
+    public static boolean compare(Ast a, Ast b) {
+        String sa = castAnonymous(a).toString();
+        String sb = castAnonymous(b).toString();
+        return sa.equals(sb);
+    }
+
+    /**
+     * 判断 Ast 的实际类型
+     *
+     * @param ast
+     * @return
+     */
+    public static boolean isApp(Ast ast) {
+        return ast instanceof Application;
+    }
+
+    public static boolean isAbs(Ast ast) {
+        return ast instanceof Abstract;
+    }
+
+    public static boolean isId(Ast ast) {
+        return ast instanceof Identifier;
+    }
+
+    /**
+     * 规约后 index 无效
+     *
+     * @param ast
+     */
+    public static void clearIndex(Ast ast) {
+        if (isApp(ast)) {
+            Application app = (Application) ast;
+            clearIndex(app.getLeft());
+            clearIndex(app.getRight());
+        } else if (isAbs(ast)) {
+            Abstract abs = (Abstract) ast;
+            abs.setIndex(-1);
+            clearIndex(abs.getParam());
+            clearIndex(abs.getBody());
+        } else if (isId(ast)) {
+            ast.setIndex(-1);
+        }
+    }
+
+    /**
+     * 将表达式化简回字面量
+     *
+     * @param ast
+     * @return
+     */
+    public static Ast abbreviate(Ast ast) {
+        if (constMapper == null) {
+            constMapper = new HashMap<>();
+            for (BasicPattern basicPattern : BasicPattern.values()) {
+                Ast constAst = new Parser().parse(basicPattern.getPattern());
+                constAst = castAnonymous(constAst);
+                constMapper.put(constAst.toString(), basicPattern.name());
+            }
+        }
+        return _abbreviate(castAnonymous(ast));
+    }
+
+    private static Map<String, String> constMapper;
+
+    private static Ast _abbreviate(Ast ast) {
+        if (isApp(ast)) {
+            Application app = (Application) ast;
+            return new Application(
+                    _abbreviate(app.getLeft()),
+                    _abbreviate(app.getRight())
+            );
+        } else if (isAbs(ast)) {
+            Ast anonymous = castAnonymous(ast);
+            String pattern;
+            if (anonymous != null && constMapper.containsKey(pattern = anonymous.toString())) {
+//                return new Const(-1, constMapper.get(pattern));
+                return new Identifier(-1, -1, constMapper.get(pattern));
+            }
+            Abstract abs = (Abstract) ast;
+            return new Abstract(
+                    abs.getIndex(),
+                    abs.getParam(),
+                    _abbreviate(abs.getBody())
+            );
+        }
+        return ast;
+    }
+
+    /**
+     * 自然数 Ast 转 int,不是自然数则返回 -1
+     *
+     * @param ast
+     * @return
+     */
+    public static int toInt(Ast ast) {
+        if (isAbs(ast)) {
+            ast = ((Abstract) ast).getBody(); // \f.?
+            if (isAbs(ast)) {
+                ast = ((Abstract) ast).getBody(); // \f.\x.?
+                int num = 0;
+                while (!isId(ast)) {
+                    if (!isApp(ast)) return -1;
+                    Ast left = ((Application) ast).getLeft();
+                    if (!isId(left) || ((Identifier) left).getId() != 1) return -1;
+                    ast = ((Application) ast).getRight();
+                    num += 1;
+                }
+                if (((Identifier) ast).getId() == 0) return num;
+            }
+        }
+        return -1;
+    }
+
+}

+ 20 - 5
lambda-service/src/main/java/com/example/lambda/service/interpreter/type/Atom.java → lambda-service/src/main/java/com/example/lambda/service/interpreter/type/Identifier.java

@@ -1,19 +1,29 @@
 package com.example.lambda.service.interpreter.type;
 
-public class Atom implements Ast {
+public class Identifier extends Ast {
+
+    private Integer index;
 
     private Integer id;
 
     private String name;
 
-    public Atom() {
-    }
-
-    public Atom(Integer id, String name) {
+    public Identifier(Integer index, Integer id, String name) {
+        this.index = index;
         this.id = id;
         this.name = name;
     }
 
+    @Override
+    public Integer getIndex() {
+        return index;
+    }
+
+    @Override
+    public void setIndex(Integer index) {
+        this.index = index;
+    }
+
     public Integer getId() {
         return id;
     }
@@ -29,4 +39,9 @@ public class Atom implements Ast {
     public void setName(String name) {
         this.name = name;
     }
+
+    @Override
+    public String toString() {
+        return name;
+    }
 }

+ 35 - 0
lambda-service/src/main/java/com/example/lambda/service/interpreter/type/Token.java

@@ -0,0 +1,35 @@
+package com.example.lambda.service.interpreter.type;
+
+public class Token {
+
+    private final int index;
+    private final TokenType type;
+    private final String val;
+
+    public Token(int index, TokenType type, String val) {
+        this.index = index;
+        this.type = type;
+        this.val = val;
+    }
+
+    public int getIndex() {
+        return index;
+    }
+
+    public TokenType getType() {
+        return type;
+    }
+
+    public String getVal() {
+        return val;
+    }
+
+    @Override
+    public String toString() {
+        return "Token{" +
+                "index=" + index +
+                ", type=" + type +
+                ", val='" + val + '\'' +
+                '}';
+    }
+}

+ 20 - 0
lambda-service/src/main/java/com/example/lambda/service/interpreter/type/TokenType.java

@@ -0,0 +1,20 @@
+package com.example.lambda.service.interpreter.type;
+
+public enum TokenType {
+    EOF("$"),
+    LAMBDA("\\"),
+    LP("("),
+    RP(")"),
+    LCID(""),
+    DOT(".");
+
+    private String val;
+
+    TokenType(String val) {
+        this.val = val;
+    }
+
+    public String getVal() {
+        return val;
+    }
+}

+ 27 - 31
lambda-service/src/main/java/com/example/lambda/service/web/LambdaService.java

@@ -1,13 +1,13 @@
 package com.example.lambda.service.web;
 
+import com.example.lambda.service.interpreter.result.InterpreterResult;
+import com.example.lambda.service.interpreter.result.InterpreterState;
 import com.example.lambda.service.interpreter.type.Abstract;
 import com.example.lambda.service.interpreter.type.Application;
 import com.example.lambda.service.interpreter.type.Ast;
-import com.example.lambda.service.interpreter.InterpreterState;
-import com.example.lambda.service.interpreter.LambdaInterpreter;
-import com.example.lambda.service.interpreter.Result;
-import com.example.lambda.service.interpreter.type.Atom;
-import com.example.lambda.service.utils.SVGRenderer;
+import com.example.lambda.service.interpreter.Interpreter;
+import com.example.lambda.service.interpreter.type.Identifier;
+import com.example.lambda.service.web.utils.SVGRenderer;
 import com.example.lambda.service.web.entity.*;
 import com.example.lambda.service.web.type.*;
 import com.example.lambda.service.web.typedata.IRAtom;
@@ -15,6 +15,7 @@ import com.example.lambda.service.web.utils.ResponseUtils;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Service;
 
@@ -26,7 +27,7 @@ public class LambdaService {
     public static final String PATTERN_SOURCE_TYPE_UNKNOWN = "无法解析参数类型";
 
     @Autowired
-    private LambdaInterpreter interpreter;
+    private Interpreter interpreter;
 
     @Autowired
     private SVGRenderer svgRenderer;
@@ -79,48 +80,48 @@ public class LambdaService {
     public ResponseEntity<String> pattern(PatternParam param) {
         if (param.getType() != SourceType.PATTERN) {
             // 参数类型不存在
-            return ResponseUtils.error(PATTERN_SOURCE_TYPE_UNKNOWN);
+            return ResponseUtils.create(PATTERN_SOURCE_TYPE_UNKNOWN, HttpStatus.OK);
         }
         String pattern = param.getData();
         try {
-            Result res = interpreter.solve(pattern);
-//            Result res = interpreter.solveErrorStub(pattern);
-            if (res.getState() == InterpreterState.ERROR) {
+            InterpreterResult res = interpreter.solve(pattern);
+            if (res.getState() != InterpreterState.SUCCESS) {
                 // 解析失败
-                return ResponseUtils.error(res.getMsg());
+                return ResponseUtils.create(res.getData(), HttpStatus.INTERNAL_SERVER_ERROR);
             }
-            System.out.println(res.getAst());
+            System.out.println(res.getData());
+            Ast ast = (Ast) res.getData();
             switch (param.getTarget()) {
                 case ATOM:
-                    IRAtom irAtom = new IRAtom("\\1.\\2.2", extractAtoms(res.getAst()));
-                    return ResponseUtils.build(irAtom);
+                    IRAtom irAtom = new IRAtom("\\1.\\2.2", extractAtoms(ast));
+                    return ResponseUtils.create(irAtom, HttpStatus.OK);
                 case AST:
                 default:
-                    return ResponseUtils.build(res.getAst());
+                    return ResponseUtils.create(ast, HttpStatus.OK);
             }
         } catch (Exception e) {
             e.printStackTrace();
-            return ResponseUtils.error("interpreter error");
+            return ResponseUtils.create("interpreter error", HttpStatus.INTERNAL_SERVER_ERROR);
         }
     }
 
-    private List<Atom> extractAtoms(Ast ast) {
-        Set<Atom> atoms = new HashSet<>();
+    private List<Identifier> extractAtoms(Ast ast) {
+        Set<Identifier> identifiers = new HashSet<>();
         Queue<Ast> Q = new LinkedList<>();
         Q.add(ast);
         while (Q.size() > 0) {
             Ast a = Q.poll();
-            if (a instanceof Atom) {
-                atoms.add((Atom) a);
+            if (a instanceof Identifier) {
+                identifiers.add((Identifier) a);
             } else if (a instanceof Application) {
                 Q.offer(((Application) a).getLeft());
                 Q.offer(((Application) a).getRight());
             } else if (a instanceof Abstract) {
-                atoms.add(((Abstract) a).getParam());
-                Q.add(((Abstract) a).getSub());
+                identifiers.add(((Abstract) a).getParam());
+                Q.add(((Abstract) a).getBody());
             }
         }
-        return new ArrayList<>(atoms);
+        return new ArrayList<>(identifiers);
     }
 
     /**
@@ -139,19 +140,14 @@ public class LambdaService {
             default:
                 break;
         }
-        try {
-            return ResponseUtils.build(el);
-        } catch (JsonProcessingException e) {
-            e.printStackTrace();
-            return ResponseUtils.error("json serialize error");
-        }
+        return ResponseUtils.create(el, HttpStatus.OK);
     }
 
     private String renderAtom(IRAtom data) {
         // build atom mapper
         Map<Integer, String> atoms = new HashMap<>();
-        for (Atom atom : data.getAtoms()) {
-            atoms.put(atom.getId(), atom.getName());
+        for (Identifier identifier : data.getAtoms()) {
+            atoms.put(identifier.getId(), identifier.getName());
         }
 //        System.out.println(atoms);
         // reformat pattern

+ 8 - 8
lambda-service/src/main/java/com/example/lambda/service/web/typedata/IRAtom.java

@@ -1,19 +1,19 @@
 package com.example.lambda.service.web.typedata;
 
-import com.example.lambda.service.interpreter.type.Atom;
+import com.example.lambda.service.interpreter.type.Identifier;
 
 import java.util.List;
 
 public class IRAtom {
     private String pattern;
-    private List<Atom> atoms;
+    private List<Identifier> identifiers;
 
     public IRAtom() {
     }
 
-    public IRAtom(String pattern, List<Atom> atoms) {
+    public IRAtom(String pattern, List<Identifier> identifiers) {
         this.pattern = pattern;
-        this.atoms = atoms;
+        this.identifiers = identifiers;
     }
 
     public String getPattern() {
@@ -24,11 +24,11 @@ public class IRAtom {
         this.pattern = pattern;
     }
 
-    public List<Atom> getAtoms() {
-        return atoms;
+    public List<Identifier> getAtoms() {
+        return identifiers;
     }
 
-    public void setAtoms(List<Atom> atoms) {
-        this.atoms = atoms;
+    public void setAtoms(List<Identifier> identifiers) {
+        this.identifiers = identifiers;
     }
 }

+ 8 - 6
lambda-service/src/main/java/com/example/lambda/service/web/utils/ResponseUtils.java

@@ -20,17 +20,19 @@ public class ResponseUtils {
         headersJson.setContentType(MediaType.APPLICATION_JSON);
     }
 
-    public static ResponseEntity<String> build(Object data) throws JsonProcessingException {
+    public static ResponseEntity<String> create(Object data, HttpStatus status) {
         String res;
         if (!(data instanceof String)) {
-            res = objectMapper.writeValueAsString(data);
+            try {
+                res = objectMapper.writeValueAsString(data);
+            } catch (JsonProcessingException e) {
+                res = "序列化异常";
+                status = HttpStatus.INTERNAL_SERVER_ERROR;
+            }
         } else {
             res = (String) data;
         }
-        return new ResponseEntity<>(res, headersJson, HttpStatus.OK);
+        return new ResponseEntity<>(res, headersJson, status);
     }
 
-    public static ResponseEntity<String> error(String msg) {
-        return new ResponseEntity<>(msg, headersJson, HttpStatus.INTERNAL_SERVER_ERROR);
-    }
 }

+ 1 - 1
lambda-service/src/main/java/com/example/lambda/service/utils/SVGRenderer.java → lambda-service/src/main/java/com/example/lambda/service/web/utils/SVGRenderer.java

@@ -1,4 +1,4 @@
-package com.example.lambda.service.utils;
+package com.example.lambda.service.web.utils;
 
 import org.springframework.stereotype.Component;
 

+ 22 - 0
lambda-service/src/test/java/com/example/lambda/service/interpreter/BasicPatternTest.java

@@ -0,0 +1,22 @@
+package com.example.lambda.service.interpreter;
+
+import com.example.lambda.service.interpreter.type.Ast;
+import com.example.lambda.service.interpreter.type.AstUtils;
+import org.junit.Test;
+
+public class BasicPatternTest {
+
+    private Parser parser = new Parser();
+
+    @Test
+    public void test_all() {
+        for (BasicPattern basicPattern : BasicPattern.values()) {
+            System.out.println("--- test: " + basicPattern.name() + " ---");
+            System.out.println("origin: " + basicPattern.getPattern());
+            Ast ast = parser.parse(basicPattern.getPattern());
+            System.out.println("full brackets: " + ast);
+            AstUtils.show(ast);
+            System.out.println();
+        }
+    }
+}

+ 102 - 0
lambda-service/src/test/java/com/example/lambda/service/interpreter/InterpreterTest.java

@@ -0,0 +1,102 @@
+package com.example.lambda.service.interpreter;
+
+import com.example.lambda.service.interpreter.type.Ast;
+import com.example.lambda.service.interpreter.type.AstUtils;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class InterpreterTest {
+
+    private Interpreter interpreter = new Interpreter();
+
+    private void testTemplate(String pattern) {
+        System.out.println("origin: " + pattern);
+        Ast ast = interpreter.parse(pattern);
+        System.out.println("ast: " + ast);
+        Ast res = interpreter.eval();
+        System.out.println("res: " + res);
+//        AstUtils.show(res);
+        Ast abbreviate = AstUtils.abbreviate(res);
+        System.out.println("abbreviate: " + abbreviate);
+        int num = AstUtils.toInt(res);
+        if (num >= 0) {
+            System.out.println("Is number: " + num);
+        }
+        System.out.println();
+    }
+
+    @Test
+    public void test_succ_n() {
+        testTemplate("SUCC ZERO");
+        testTemplate("SUCC (SUCC ZERO)");
+        testTemplate("SUCC (SUCC (SUCC ZERO))");
+        testTemplate("SUCC (SUCC (SUCC (SUCC ZERO)))");
+    }
+
+    @Test
+    public void test_mathematical() {
+        testTemplate("PLUS ONE TWO");
+        testTemplate("MULT TWO FOUR");
+        testTemplate("POW TWO FOUR");
+        testTemplate("PRED FOUR");
+        testTemplate("PRED (PRED FOUR)");
+        testTemplate("PRED (PRED (PRED FOUR))");
+        testTemplate("SUB FOUR TWO");
+        testTemplate("SUB TWO FOUR"); // ***
+    }
+
+    @Test
+    public void test_logical() {
+        testTemplate("TRUE");
+        testTemplate("AND");
+        testTemplate("AND TRUE TRUE");
+        testTemplate("AND TRUE FALSE");
+        testTemplate("AND FALSE FALSE");
+        testTemplate("OR TRUE TRUE");
+        testTemplate("OR TRUE FALSE");
+        testTemplate("OR FALSE FALSE");
+        testTemplate("NOT TRUE");
+        testTemplate("NOT FALSE");
+        testTemplate("IF FALSE ONE TWO");
+        testTemplate("IF TRUE ONE TWO");
+    }
+
+    @Test
+    public void test_compare() {
+        testTemplate("IS_ZERO THREE");
+        testTemplate("IS_ZERO ZERO");
+        testTemplate("IS_ZERO FALSE");
+        testTemplate("LEQ FIVE TWO");
+        testTemplate("LEQ TWO FIVE");
+        testTemplate("LEQ FIVE FIVE");
+        testTemplate("EQ FIVE TWO");
+        testTemplate("EQ TWO FIVE");
+        testTemplate("EQ FIVE FIVE");
+    }
+
+    @Test
+    public void test_my_logical() {
+        testTemplate("MY_NOT TRUE");
+        testTemplate("MY_NOT FALSE");
+        testTemplate("MY_AND TRUE TRUE");
+        testTemplate("MY_AND TRUE FALSE");
+        testTemplate("MY_AND FALSE FALSE");
+        testTemplate("MY_OR TRUE TRUE");
+        testTemplate("MY_OR TRUE FALSE");
+        testTemplate("MY_OR FALSE FALSE");
+        testTemplate("MAX ONE TWO");
+        testTemplate("MAX ONE THREE");
+        testTemplate("MAX THREE TWO");
+        testTemplate("MIN ONE TWO");
+        testTemplate("MIN ONE THREE");
+        testTemplate("MIN THREE TWO");
+    }
+
+//    @Test
+    public void test_recursive() {
+//        testTemplate("FACT THREE");
+        testTemplate("FACTY TWO");
+
+    }
+}

+ 71 - 0
lambda-service/src/test/java/com/example/lambda/service/interpreter/ParserTest.java

@@ -0,0 +1,71 @@
+package com.example.lambda.service.interpreter;
+
+import com.example.lambda.service.interpreter.error.InterpreterError;
+import com.example.lambda.service.interpreter.error.InterpreterException;
+import com.example.lambda.service.interpreter.type.Ast;
+import com.example.lambda.service.interpreter.type.AstUtils;
+import com.example.lambda.service.interpreter.type.Token;
+import org.junit.Test;
+
+public class ParserTest {
+
+    private Parser parser = new Parser();
+
+    private void testTemplate(String pattern) {
+        System.out.println("origin: " + pattern);
+        Ast ast = parser.parse(pattern);
+        System.out.println("full bracket: " + ast);
+        AstUtils.show(ast);
+        System.out.println();
+    }
+
+    private void exceptionTemplate(String pattern) {
+        try {
+            parser.parse(pattern);
+        } catch(InterpreterException e) {
+            InterpreterError error = new InterpreterError(pattern, e);
+            System.out.println(error);
+        }
+        System.out.println();
+    }
+
+    @Test
+    public void test_tokens() {
+        String zeroStr = BasicPattern.ZERO.getPattern();
+        System.out.println("origin: " + zeroStr);
+        for (Token token : parser.tokens(zeroStr)) {
+            System.out.println(token);
+        }
+    }
+
+    @Test
+    public void test_parse() {
+        String zeroStr = BasicPattern.ZERO.getPattern();
+        testTemplate(zeroStr);
+    }
+
+    @Test
+    public void test_parse_const() {
+        testTemplate("ZERO");
+    }
+
+    @Test
+    public void test_parse_application_abstract() {
+        testTemplate("\\f.\\x.x \\n.f n");
+    }
+
+    @Test
+    public void exception_unexpected_token() {
+        exceptionTemplate("\\f.\\x..x x");
+    }
+
+    @Test
+    public void exception_wild_identifier() {
+        exceptionTemplate("\\f.\\x.xx");
+    }
+
+    @Test
+    public void exception_unknown_const() {
+        exceptionTemplate("ZERO2");
+    }
+}

+ 103 - 0
lambda-service/src/test/java/com/example/lambda/service/interpreter/type/AstUtilsTest.java

@@ -0,0 +1,103 @@
+package com.example.lambda.service.interpreter.type;
+
+import com.example.lambda.service.interpreter.BasicPattern;
+import com.example.lambda.service.interpreter.Interpreter;
+import com.example.lambda.service.interpreter.Parser;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class AstUtilsTest {
+
+    private Parser parser = new Parser();
+
+    @Test
+    public void test_show() {
+        System.out.println("origin(PRED): " + BasicPattern.PRED.getPattern());
+        Ast ast = parser.parse("PRED");
+        System.out.println("full bracket: " + ast);
+        AstUtils.show(ast);
+    }
+
+    @Test
+    public void test_getConstAst() {
+        Ast zero = AstUtils.getConstAst("zero");
+        System.out.println("zero: " + zero);
+        Ast one = AstUtils.getConstAst("one");
+        System.out.println("one: " + one);
+        Ast succ = AstUtils.getConstAst("succ");
+        System.out.println("succ: " + succ);
+        Ast pred = AstUtils.getConstAst("pred");
+        System.out.println("pred: " + pred);
+    }
+
+    @Test
+    public void test_castAnonymous() {
+        System.out.println("origin(PRED): " + BasicPattern.PRED.getPattern());
+        Ast ast = parser.parse("PRED");
+        Ast anonymousAst = AstUtils.castAnonymous(ast);
+        System.out.println("full bracket: " + ast);
+        System.out.println("anonymous: " + anonymousAst);
+        AstUtils.show(ast);
+        AstUtils.show(anonymousAst);
+    }
+
+    @Test
+    public void test_compare() {
+        String pa = "\\f.\\x.x";
+        String pb = "\\a.\\b.b";
+        Ast a = parser.parse(pa);
+        Ast b = parser.parse(pb);
+        System.out.println(a);
+        System.out.println(b);
+        boolean res = AstUtils.compare(a, b);
+        System.out.println(res);
+        assertEquals(true, res);
+    }
+
+    @Test
+    public void test_clearIndex() {
+        System.out.println("origin(PRED): " + BasicPattern.PRED.getPattern());
+        Ast ast = parser.parse("PRED");
+        AstUtils.show(ast);
+        AstUtils.clearIndex(ast);
+        AstUtils.show(ast);
+    }
+
+    @Test
+    public void test_abbreviate() {
+        test_abbreviate_template("ZERO");
+        test_abbreviate_template("SUCC ONE");
+        test_abbreviate_template("PLUS ONE (SUCC ONE)");
+        test_abbreviate_template("PRED ONE");
+        test_abbreviate_template("SUB");
+        test_abbreviate_template("FACT1");
+        test_abbreviate_template("\\m.\\n.n PRED m n");
+        test_abbreviate_template("\\m.\\n.n PRED m");
+    }
+
+    private void test_abbreviate_template(String name) {
+        Ast ast = parser.parse(name);
+        System.out.println(ast);
+        ast = AstUtils.abbreviate(ast);
+        System.out.println(ast);
+    }
+
+    @Test
+    public void test_toInt() {
+        test_toInt_template("\\f.\\x.x");
+        test_toInt_template("\\f.\\x.f x");
+        test_toInt_template("\\f.\\x.f(f x)");
+        test_toInt_template("\\f.\\x.f(f(f x))");
+        test_toInt_template("\\f.\\x.f(f(f(f x)))");
+        test_toInt_template("\\f.\\x.f(f(f(f(f x))))");
+        test_toInt_template("\\f.\\x.f(f(f(f(f(f x)))))");
+    }
+
+    private void test_toInt_template(String pattern) {
+        System.out.println("origin: " + pattern);
+        Ast ast = parser.parse(pattern);
+        int num = AstUtils.toInt(ast);
+        System.out.println("ast: " + ast + " = " + num);
+    }
+}