diff --git a/cn-universal-framework/cn-universal-core/src/main/java/cn/universal/core/engine/compile/MagicScriptCompiler.java b/cn-universal-framework/cn-universal-core/src/main/java/cn/universal/core/engine/compile/MagicScriptCompiler.java index d564c96..7b7ea1b 100644 --- a/cn-universal-framework/cn-universal-core/src/main/java/cn/universal/core/engine/compile/MagicScriptCompiler.java +++ b/cn-universal-framework/cn-universal-core/src/main/java/cn/universal/core/engine/compile/MagicScriptCompiler.java @@ -59,7 +59,7 @@ public class MagicScriptCompiler implements Opcodes { private final Stack> vars = new Stack<>(); - private final Stack labelStack = new Stack<>(); + private final Stack labelStack = new Stack<>(); private final Stack> finallyStack = new Stack<>(); @@ -123,7 +123,7 @@ public class MagicScriptCompiler implements Opcodes { methodVisitors.push(visitor); vars.push(new ArrayList<>()); finallyStack.push(null); - labelStack.push(new Label[2]); + labelStack.push(new FlowControlLabel(null, null)); return this; } @@ -138,7 +138,13 @@ public class MagicScriptCompiler implements Opcodes { /** 标识continue和break位置 */ public MagicScriptCompiler markLabel(Label start, Label end) { - labelStack.push(new Label[] {start, end}); + labelStack.push(new FlowControlLabel(start, end)); + return this; + } + + /** 标识仅支持break的位置,例如 switch */ + public MagicScriptCompiler markBreakLabel(Label end) { + labelStack.push(new FlowControlLabel(null, end)); return this; } @@ -150,12 +156,24 @@ public class MagicScriptCompiler implements Opcodes { /** 跳转到continue位置 */ public MagicScriptCompiler start() { - return jump(GOTO, labelStack.peek()[0]); + for (int i = labelStack.size() - 1; i >= 0; i--) { + Label label = labelStack.get(i).getContinueLabel(); + if (label != null) { + return jump(GOTO, label); + } + } + throw new MagicScriptCompileException("continue 只能在循环中使用"); } /** 跳转到break位置 */ public MagicScriptCompiler end() { - return jump(GOTO, labelStack.peek()[1]); + for (int i = labelStack.size() - 1; i >= 0; i--) { + Label label = labelStack.get(i).getBreakLabel(); + if (label != null) { + return jump(GOTO, label); + } + } + throw new MagicScriptCompileException("break 只能在循环或 switch 中使用"); } /** 访问AST节点 */ @@ -315,6 +333,12 @@ public class MagicScriptCompiler implements Opcodes { return this; } + /** 加载本地临时变量 */ + public MagicScriptCompiler loadLocal(int index) { + self().visitVarInsn(ALOAD, index); + return this; + } + /** 加载变量 */ public MagicScriptCompiler load(int index) { return load2() @@ -753,6 +777,26 @@ public class MagicScriptCompiler implements Opcodes { return methodVisitors.peek(); } + private static class FlowControlLabel { + + private final Label continueLabel; + + private final Label breakLabel; + + private FlowControlLabel(Label continueLabel, Label breakLabel) { + this.continueLabel = continueLabel; + this.breakLabel = breakLabel; + } + + public Label getContinueLabel() { + return continueLabel; + } + + public Label getBreakLabel() { + return breakLabel; + } + } + private static String getJvmType(Class target) { return target == null ? null : target.getName().replace(".", "/"); } diff --git a/cn-universal-framework/cn-universal-core/src/main/java/cn/universal/core/engine/parsing/Parser.java b/cn-universal-framework/cn-universal-core/src/main/java/cn/universal/core/engine/parsing/Parser.java index 851a7f4..cf46bf6 100644 --- a/cn-universal-framework/cn-universal-core/src/main/java/cn/universal/core/engine/parsing/Parser.java +++ b/cn-universal-framework/cn-universal-core/src/main/java/cn/universal/core/engine/parsing/Parser.java @@ -101,6 +101,7 @@ import cn.universal.core.engine.parsing.ast.literal.StringLiteral; import cn.universal.core.engine.parsing.ast.statement.Assert; import cn.universal.core.engine.parsing.ast.statement.AsyncCall; import cn.universal.core.engine.parsing.ast.statement.Break; +import cn.universal.core.engine.parsing.ast.statement.ClassicForStatement; import cn.universal.core.engine.parsing.ast.statement.ClassConverter; import cn.universal.core.engine.parsing.ast.statement.Continue; import cn.universal.core.engine.parsing.ast.statement.Exit; @@ -115,6 +116,7 @@ import cn.universal.core.engine.parsing.ast.statement.MethodCall; import cn.universal.core.engine.parsing.ast.statement.NewStatement; import cn.universal.core.engine.parsing.ast.statement.Return; import cn.universal.core.engine.parsing.ast.statement.Spread; +import cn.universal.core.engine.parsing.ast.statement.SwitchStatement; import cn.universal.core.engine.parsing.ast.statement.Throw; import cn.universal.core.engine.parsing.ast.statement.TryStatement; import cn.universal.core.engine.parsing.ast.statement.VariableAccess; @@ -272,6 +274,8 @@ public class Parser { result = parseReturn(); } else if (stream.match("for", false)) { result = parseForStatement(); + } else if (isSwitchStatement()) { + result = parseSwitchStatement(); } else if (stream.match("while", false)) { result = parseWhileStatement(); } else if (stream.match("continue", false)) { @@ -675,9 +679,16 @@ public class Parser { return new WhileStatement(addSpan(openingWhile, closingEnd), condition, trueBlock); } - private ForStatement parseForStatement() { + private Node parseForStatement() { Span openingFor = stream.expect("for").getSpan(); stream.expect("("); + if (isClassicForStatement()) { + return parseClassicForStatement(openingFor); + } + return parseIteratorForStatement(openingFor); + } + + private ForStatement parseIteratorForStatement(Span openingFor) { push(); Span index = null; Span value = stream.expect(Identifier).getSpan(); @@ -709,6 +720,140 @@ public class Parser { body); } + private ClassicForStatement parseClassicForStatement(Span openingFor) { + push(); + Node initializer = null; + Expression condition = null; + Expression update = null; + if (!stream.match(";", false)) { + initializer = parseForInitializer(); + } + stream.expect(";"); + if (!stream.match(";", false)) { + condition = parseExpression(); + } + stream.expect(";"); + if (!stream.match(")", false)) { + update = parseExpression(); + } + stream.expect(")"); + List body = parseFunctionBody(); + pop(); + return new ClassicForStatement( + addSpan(openingFor, stream.getPrev().getSpan()), initializer, condition, update, body); + } + + private Node parseForInitializer() { + if (matchVarDefine()) { + return parseVarDefine(); + } + int index = stream.makeIndex(); + if (matchTypeDefine()) { + stream.resetIndex(index); + return parseVarDefine(); + } + stream.resetIndex(index); + return parseExpression(); + } + + private boolean isClassicForStatement() { + int index = stream.makeIndex(); + int depth = 0; + try { + while (stream.hasMore()) { + Token token = stream.consume(); + switch (token.getType()) { + case LeftParantheses: + case LeftBracket: + case LeftCurly: + depth++; + break; + case RightParantheses: + if (depth == 0) { + return false; + } + depth--; + break; + case RightBracket: + case RightCurly: + if (depth > 0) { + depth--; + } + break; + default: + if (";".equals(token.getText()) && depth == 0) { + return true; + } + break; + } + } + return false; + } finally { + stream.resetIndex(index); + } + } + + private boolean isSwitchStatement() { + if (!stream.match("switch", false)) { + return false; + } + int index = stream.makeIndex(); + try { + stream.consume(); + return stream.match(LeftParantheses, false); + } finally { + stream.resetIndex(index); + } + } + + private SwitchStatement parseSwitchStatement() { + Span openingSwitch = stream.expect("switch").getSpan(); + requiredNew = false; + Expression target = parseExpression(); + requiredNew = true; + stream.expect("{"); + List switchCases = new ArrayList<>(); + boolean hasDefault = false; + while (stream.hasMore() && !stream.match("}", false)) { + boolean defaultCase = false; + Expression condition = null; + Span caseSpan; + if (stream.match("case", true)) { + caseSpan = stream.getPrev().getSpan(); + condition = parseExpression(); + } else if (stream.match("default", true)) { + caseSpan = stream.getPrev().getSpan(); + defaultCase = true; + if (hasDefault) { + MagicScriptError.error("switch 中只允许一个 default", caseSpan); + } + hasDefault = true; + } else { + MagicScriptError.error("switch 中只允许 case 或 default", stream.consume().getSpan()); + return null; + } + stream.expect(Colon); + List statements = new ArrayList<>(); + while (stream.hasMore() + && !stream.match("}", false) + && !stream.match("case", false) + && !stream.match("default", false)) { + Node node = parseStatement(true); + if (node != null) { + validateNode(node); + statements.add(node); + } + } + Span clauseEnd = + statements.isEmpty() ? stream.getPrev().getSpan() : statements.get(statements.size() - 1).getSpan(); + switchCases.add( + new SwitchStatement.SwitchCase( + addSpan(caseSpan, clauseEnd), condition, statements, defaultCase)); + } + Span closingEnd = expectCloseing(); + return new SwitchStatement(addSpan(openingSwitch, closingEnd), target, switchCases); + } + private Span expectCloseing() { if (!stream.hasMore()) { MagicScriptError.error("Did not find closing }.", stream.prev().getSpan()); diff --git a/cn-universal-framework/cn-universal-core/src/main/java/cn/universal/core/engine/parsing/ast/statement/ClassicForStatement.java b/cn-universal-framework/cn-universal-core/src/main/java/cn/universal/core/engine/parsing/ast/statement/ClassicForStatement.java new file mode 100644 index 0000000..a3f7e3a --- /dev/null +++ b/cn-universal-framework/cn-universal-core/src/main/java/cn/universal/core/engine/parsing/ast/statement/ClassicForStatement.java @@ -0,0 +1,63 @@ +package cn.universal.core.engine.parsing.ast.statement; + +import cn.universal.core.engine.asm.Label; +import cn.universal.core.engine.compile.MagicScriptCompiler; +import cn.universal.core.engine.parsing.Span; +import cn.universal.core.engine.parsing.ast.Expression; +import cn.universal.core.engine.parsing.ast.Node; +import cn.universal.core.engine.runtime.handle.OperatorHandle; +import java.util.List; + +public class ClassicForStatement extends Node { + + private final Node initializer; + private final Expression condition; + private final Expression update; + private final List body; + + public ClassicForStatement( + Span span, Node initializer, Expression condition, Expression update, List body) { + super(span); + this.initializer = initializer; + this.condition = condition; + this.update = update; + this.body = body; + } + + @Override + public void visitMethod(MagicScriptCompiler compiler) { + if (initializer != null) { + initializer.visitMethod(compiler); + } + if (condition != null) { + condition.visitMethod(compiler); + } + if (update != null) { + update.visitMethod(compiler); + } + body.forEach(it -> it.visitMethod(compiler)); + } + + @Override + public void compile(MagicScriptCompiler compiler) { + Label start = new Label(); + Label updateLabel = new Label(); + Label end = new Label(); + Label continueLabel = update == null ? start : updateLabel; + if (initializer != null) { + compiler.compile(initializer, true); + } + compiler.markLabel(continueLabel, end).label(start); + if (condition != null) { + compiler + .visit(condition) + .invoke(INVOKESTATIC, OperatorHandle.class, "isTrue", boolean.class, Object.class) + .jump(IFEQ, end); + } + compiler.compile(body); + if (update != null) { + compiler.label(updateLabel).compile(update, true); + } + compiler.jump(GOTO, start).label(end).exitLabel(); + } +} diff --git a/cn-universal-framework/cn-universal-core/src/main/java/cn/universal/core/engine/parsing/ast/statement/SwitchStatement.java b/cn-universal-framework/cn-universal-core/src/main/java/cn/universal/core/engine/parsing/ast/statement/SwitchStatement.java new file mode 100644 index 0000000..7c74b94 --- /dev/null +++ b/cn-universal-framework/cn-universal-core/src/main/java/cn/universal/core/engine/parsing/ast/statement/SwitchStatement.java @@ -0,0 +1,91 @@ +package cn.universal.core.engine.parsing.ast.statement; + +import cn.universal.core.engine.asm.Label; +import cn.universal.core.engine.compile.MagicScriptCompiler; +import cn.universal.core.engine.parsing.Span; +import cn.universal.core.engine.parsing.ast.Expression; +import cn.universal.core.engine.parsing.ast.Node; +import cn.universal.core.engine.runtime.handle.OperatorHandle; +import java.util.ArrayList; +import java.util.List; + +public class SwitchStatement extends Node { + + private final Expression target; + private final List switchCases; + + public SwitchStatement(Span span, Expression target, List switchCases) { + super(span); + this.target = target; + this.switchCases = switchCases; + } + + @Override + public void visitMethod(MagicScriptCompiler compiler) { + target.visitMethod(compiler); + switchCases.forEach(it -> it.visitMethod(compiler)); + } + + @Override + public void compile(MagicScriptCompiler compiler) { + int targetSlot = compiler.getTempIndex(); + Label end = new Label(); + List