diff --git a/src/main/java/org/jcnc/snow/compiler/Main.java b/src/main/java/org/jcnc/snow/compiler/Main.java index 6c4cffe..5298ae3 100644 --- a/src/main/java/org/jcnc/snow/compiler/Main.java +++ b/src/main/java/org/jcnc/snow/compiler/Main.java @@ -34,14 +34,9 @@ public class Main { // 3. 可读地打印 AST ASTPrinter.print(ast); - // 1) 输出紧凑 JSON - String compact = ASTJsonSerializer.toJsonString(ast); -// System.out.println("=== Compact JSON ==="); -// System.out.println(compact); - - // 2) 输出美化后的 JSON + // 4. AST JSON System.out.println("=== Pretty JSON ==="); - System.out.println(JsonFormatter.prettyPrint(compact)); + System.out.println(JsonFormatter.prettyPrint(ASTJsonSerializer.toJsonString(ast))); } } \ No newline at end of file diff --git a/src/main/java/org/jcnc/snow/compiler/parser/util/ASTJsonSerializer.java b/src/main/java/org/jcnc/snow/compiler/parser/util/ASTJsonSerializer.java index 2d643a2..1b3c023 100644 --- a/src/main/java/org/jcnc/snow/compiler/parser/util/ASTJsonSerializer.java +++ b/src/main/java/org/jcnc/snow/compiler/parser/util/ASTJsonSerializer.java @@ -4,183 +4,170 @@ import org.jcnc.snow.compiler.parser.ast.*; import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode; import org.jcnc.snow.compiler.parser.ast.base.Node; -import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; +import java.util.*; +/** + * ASTJsonSerializer 工具类 + *

+ * 将编译器生成的 AST(抽象语法树)节点列表转换为通用的 Map/List 结构 + * 并借助 JSONParser.toJson(Object) 方法序列化为 JSON 字符串。 + *

+ * 支持的节点类型包括:ModuleNode、FunctionNode、DeclarationNode、 + * AssignmentNode、IfNode、LoopNode、ReturnNode、ExpressionStatementNode + * 以及各种 ExpressionNode(如 BinaryExpressionNode、IdentifierNode 等)。 + */ public class ASTJsonSerializer { - public static String toJsonString(List ast) { - return ast.stream() - .map(ASTJsonSerializer::nodeToJson) - .collect(Collectors.joining(",", "[", "]")); + /** + * 快速创建一个 LinkedHashMap,并写入 type 字段 + */ + private static Map newNodeMap(String type) { + Map m = new LinkedHashMap<>(); + m.put("type", type); + return m; } - private static String nodeToJson(Node n) { - return switch (n) { - case ModuleNode m -> moduleToJson(m); - case FunctionNode f -> functionToJson(f); - case DeclarationNode d -> declarationToJson(d); - case AssignmentNode a -> assignmentToJson(a); - case IfNode i -> ifToJson(i); - case LoopNode l -> loopToJson(l); - case ReturnNode r -> returnToJson(r); - case ExpressionStatementNode e -> exprStmtToJson(e); - default -> simpleTypeJson(n.getClass().getSimpleName()); - }; - } - - private static String moduleToJson(ModuleNode m) { - return """ - {"type":"Module","name":%1$s,"imports":%2$s,"functions":%3$s} - """.formatted( - quote(m.name()), - listToJsonArray(m.imports(), - imp -> String.format("{\"type\":\"Import\",\"module\":%s}", quote(imp.moduleName())) - ), - listToJsonArray(m.functions(), ASTJsonSerializer::functionToJson) - ); - } - - private static String functionToJson(FunctionNode f) { - return """ - {"type":"Function","name":%1$s,"parameters":%2$s,"returnType":%3$s,"body":%4$s} - """.formatted( - quote(f.name()), - listToJsonArray(f.parameters(), - p -> String.format("{\"name\":%s,\"type\":%s}", quote(p.name()), quote(p.type())) - ), - quote(f.returnType()), - listToJsonArray(f.body(), ASTJsonSerializer::nodeToJson) - ); - } - - private static String declarationToJson(DeclarationNode d) { - var base = new StringBuilder(); - base.append(""" - {"type":"Declaration","name":%1$s,"varType":%2$s - """.formatted(quote(d.getName()), quote(d.getType()))); - d.getInitializer().ifPresent(init -> - base.append(",\"initializer\":").append(exprToJson(init)) - ); - base.append("}"); - return base.toString(); - } - - private static String assignmentToJson(AssignmentNode a) { - return """ - {"type":"Assignment","variable":%1$s,"value":%2$s} - """.formatted(quote(a.variable()), exprToJson(a.value())); - } - - private static String ifToJson(IfNode i) { - var thenJson = listToJsonArray(i.thenBranch(), ASTJsonSerializer::nodeToJson); - var elseJson = i.elseBranch().isEmpty() ? "" - : ",\"else\":" + listToJsonArray(i.elseBranch(), ASTJsonSerializer::nodeToJson); - return """ - {"type":"If","condition":%1$s,"then":%2$s%3$s} - """.formatted(exprToJson(i.condition()), thenJson, elseJson); - } - - private static String loopToJson(LoopNode l) { - return """ - {"type":"Loop","initializer":%1$s,"condition":%2$s,"update":%3$s,"body":%4$s} - """.formatted( - l.initializer() != null ? nodeToJson(l.initializer()) : "null", - l.condition() != null ? exprToJson(l.condition()) : "null", - l.update() != null ? nodeToJson(l.update()) : "null", - listToJsonArray(l.body(), ASTJsonSerializer::nodeToJson) - ); - } - - private static String returnToJson(ReturnNode r) { - var sb = new StringBuilder("{\"type\":\"Return\""); - r.getExpression().ifPresent(expr -> - sb.append(",\"value\":").append(exprToJson(expr)) - ); - sb.append("}"); - return sb.toString(); - } - - private static String exprStmtToJson(ExpressionStatementNode e) { - return """ - {"type":"ExpressionStatement","expression":%s} - """.formatted(exprToJson(e.expression())); - } - - private static String exprToJson(ExpressionNode expr) { - return switch (expr) { - case BinaryExpressionNode b -> binaryToJson(b); - case IdentifierNode id -> idToJson(id); - case NumberLiteralNode n -> numToJson(n); - case StringLiteralNode s -> strToJson(s); - case CallExpressionNode c -> callToJson(c); - case MemberExpressionNode m -> memberToJson(m); - default -> simpleTypeJson(expr.getClass().getSimpleName()); - }; - } - - private static String binaryToJson(BinaryExpressionNode b) { - return """ - {"type":"BinaryExpression","left":%1$s,"operator":%2$s,"right":%3$s} - """.formatted(exprToJson(b.left()), quote(b.operator()), exprToJson(b.right())); - } - - private static String idToJson(IdentifierNode id) { - return """ - {"type":"Identifier","name":%s} - """.formatted(quote(id.name())); - } - - private static String numToJson(NumberLiteralNode n) { - return """ - {"type":"NumberLiteral","value":%s} - """.formatted(quote(n.value())); - } - - private static String strToJson(StringLiteralNode s) { - return """ - {"type":"StringLiteral","value":%s} - """.formatted(quote(s.value())); - } - - private static String callToJson(CallExpressionNode c) { - return """ - {"type":"CallExpression","callee":%1$s,"arguments":%2$s} - """.formatted(exprToJson(c.callee()), listToJsonArray(c.arguments(), ASTJsonSerializer::exprToJson)); - } - - private static String memberToJson(MemberExpressionNode m) { - return """ - {"type":"MemberExpression","object":%1$s,"member":%2$s} - """.formatted(exprToJson(m.object()), quote(m.member())); - } - - // ======== 通用辅助 ======== - - private static String simpleTypeJson(String typeName) { - return "{\"type\":" + quote(typeName) + "}"; - } - - private static String listToJsonArray(List list, Function mapper) { - return list.stream() - .map(mapper) - .collect(Collectors.joining(",", "[", "]")); - } - - private static String quote(String s) { - var sb = new StringBuilder("\""); - for (char c : s.toCharArray()) { - switch (c) { - case '\\' -> sb.append("\\\\"); - case '\"' -> sb.append("\\\""); - case '\n' -> sb.append("\\n"); - case '\r' -> sb.append("\\r"); - case '\t' -> sb.append("\\t"); - default -> sb.append(c); - } + /** + * 用于构建表达式节点的 Map + */ + private static Map exprMap(String type, Object... kv) { + Map m = new LinkedHashMap<>(); + m.put("type", type); + for (int i = 0; i < kv.length; i += 2) { + m.put((String) kv[i], kv[i + 1]); } - sb.append('"'); - return sb.toString(); + return m; } -} + + /** + * 将 AST 根节点列表序列化为 JSON 字符串。 + * + * @param ast 表示抽象语法树根节点的 List + * @return 对应的 JSON 格式字符串 + */ + public static String toJsonString(List ast) { + List list = new ArrayList<>(ast.size()); + for (Node n : ast) { + list.add(nodeToMap(n)); + } + return JSONParser.toJson(list); + } + + /** + * 递归地将 AST 节点转换为 Map 或 List 等通用结构, + * 便于后续统一序列化。 + */ + private static Object nodeToMap(Node n) { + return switch (n) { + case ModuleNode(String name, List imports, List functions) -> { + Map map = newNodeMap("Module"); + map.put("name", name); + List imps = new ArrayList<>(imports.size()); + for (ImportNode imp : imports) { + imps.add(Map.of( + "type", "Import", + "module", imp.moduleName() + )); + } + map.put("imports", imps); + List funcs = new ArrayList<>(functions.size()); + for (FunctionNode f : functions) { + funcs.add(nodeToMap(f)); + } + map.put("functions", funcs); + yield map; + } + case FunctionNode f -> { + Map map = newNodeMap("Function"); + map.put("name", f.name()); + List params = new ArrayList<>(f.parameters().size()); + for (var p : f.parameters()) { + params.add(Map.of( + "name", p.name(), + "type", p.type() + )); + } + map.put("parameters", params); + map.put("returnType", f.returnType()); + List body = new ArrayList<>(f.body().size()); + for (Node stmt : f.body()) { + body.add(nodeToMap(stmt)); + } + map.put("body", body); + yield map; + } + case DeclarationNode d -> { + Map map = newNodeMap("Declaration"); + map.put("name", d.getName()); + map.put("varType", d.getType()); + map.put("initializer", + d.getInitializer().map(ASTJsonSerializer::exprToMap).orElse(null) + ); + yield map; + } + case AssignmentNode a -> exprMap("Assignment", + "variable", a.variable(), + "value", exprToMap(a.value()) + ); + case IfNode i -> { + Map map = newNodeMap("If"); + map.put("condition", exprToMap(i.condition())); + List thenList = new ArrayList<>(i.thenBranch().size()); + for (Node stmt : i.thenBranch()) thenList.add(nodeToMap(stmt)); + map.put("then", thenList); + if (!i.elseBranch().isEmpty()) { + List elseList = new ArrayList<>(i.elseBranch().size()); + for (Node stmt : i.elseBranch()) elseList.add(nodeToMap(stmt)); + map.put("else", elseList); + } + yield map; + } + case LoopNode l -> { + Map map = newNodeMap("Loop"); + map.put("initializer", l.initializer() != null ? nodeToMap(l.initializer()) : null); + map.put("condition", l.condition() != null ? exprToMap(l.condition()) : null); + map.put("update", l.update() != null ? nodeToMap(l.update()) : null); + List body = new ArrayList<>(l.body().size()); + for (Node stmt : l.body()) body.add(nodeToMap(stmt)); + map.put("body", body); + yield map; + } + case ReturnNode r -> { + Map map = newNodeMap("Return"); + r.getExpression().ifPresent(expr -> map.put("value", exprToMap(expr))); + yield map; + } + case ExpressionStatementNode e -> exprMap("ExpressionStatement", + "expression", exprToMap(e.expression()) + ); + case ExpressionNode expressionNode -> exprToMap(expressionNode); + default -> Map.of("type", n.getClass().getSimpleName()); + }; + } + + /** + * 将表达式节点转换为 Map 表示。 + */ + private static Object exprToMap(ExpressionNode expr) { + return switch (expr) { + case BinaryExpressionNode(ExpressionNode left, String operator, ExpressionNode right) -> exprMap("BinaryExpression", + "left", exprToMap(left), + "operator", operator, + "right", exprToMap(right) + ); + case IdentifierNode(String name) -> exprMap("Identifier", "name", name); + case NumberLiteralNode(String value) -> exprMap("NumberLiteral", "value", value); + case StringLiteralNode(String value) -> exprMap("StringLiteral", "value", value); + case CallExpressionNode(ExpressionNode callee, List arguments) -> { + List args = new ArrayList<>(arguments.size()); + for (ExpressionNode arg : arguments) args.add(exprToMap(arg)); + yield exprMap("CallExpression", "callee", exprToMap(callee), "arguments", args); + } + case MemberExpressionNode(ExpressionNode object, String member) -> exprMap("MemberExpression", + "object", exprToMap(object), + "member", member + ); + default -> Map.of("type", expr.getClass().getSimpleName()); + }; + } +} \ No newline at end of file diff --git a/src/main/java/org/jcnc/snow/compiler/parser/util/JSONParser.java b/src/main/java/org/jcnc/snow/compiler/parser/util/JSONParser.java new file mode 100644 index 0000000..d5204a6 --- /dev/null +++ b/src/main/java/org/jcnc/snow/compiler/parser/util/JSONParser.java @@ -0,0 +1,371 @@ +package org.jcnc.snow.compiler.parser.util; + +import java.util.*; +import java.util.Map.Entry; + +/** + * JSON 工具类,提供线程安全、可重用的解析与序列化功能 + *

+ * - 解析:将合法的 JSON 文本转换为 Java 原生对象(Map、List、String、Number、Boolean 或 null) + * - 序列化:将 Java 原生对象转换为符合 JSON 标准的字符串 + *

+ * 设计要点: + * 1. 使用静态方法作为唯一入口,避免状态共享导致的线程安全问题 + * 2. 解析器内部使用 char[] 缓冲区,提高访问性能 + * 3. 维护行列号信息,抛出异常时能精确定位错误位置 + * 4. 序列化器基于 StringBuilder,预分配容量,减少中间字符串创建 + */ +public class JSONParser { + // 私有构造,禁止外部实例化 + private JSONParser() {} + + /** + * 将 JSON 文本解析为对应的 Java 对象 + * @param input JSON 格式字符串 + * @return 对应的 Java 原生对象: + * - JSON 对象 -> Map + * - JSON 数组 -> List + * - JSON 字符串 -> String + * - JSON 数值 -> Long 或 Double + * - JSON 布尔 -> Boolean + * - JSON null -> null + * @throws RuntimeException 如果遇到语法错误或多余字符,异常消息中包含行列信息 + */ + public static Object parse(String input) { + return new Parser(input).parseInternal(); + } + + /** + * 将 Java 原生对象序列化为 JSON 字符串 + * @param obj 支持的类型:Map、Collection、String、Number、Boolean 或 null + * @return 符合 JSON 规范的字符串 + */ + public static String toJson(Object obj) { + return Writer.write(obj); + } + + // ======= 内部解析器 ======= + /** + * 负责将 char[] 缓冲区中的 JSON 文本解析为 Java 对象 + */ + private static class Parser { + /** 输入缓冲区 */ + private final char[] buf; + /** 当前解析到的位置索引 */ + private int pos; + /** 当前字符所在行号,从 1 开始 */ + private int line; + /** 当前字符所在列号,从 1 开始 */ + private int col; + + /** + * 构造解析器,初始化缓冲区和行列信息 + * @param input 待解析的 JSON 文本 + */ + Parser(String input) { + this.buf = input.toCharArray(); + this.pos = 0; + this.line = 1; + this.col = 1; + } + + /** + * 入口方法,跳过空白后调用 parseValue,再校验尾部无多余字符 + */ + Object parseInternal() { + skipWhitespace(); + Object value = parseValue(); + skipWhitespace(); + if (pos < buf.length) { + error("存在无法识别的多余字符"); + } + return value; + } + + /** + * 根据下一个字符决定解析哪种 JSON 值 + */ + private Object parseValue() { + skipWhitespace(); + if (match("null")) return null; + if (match("true")) return Boolean.TRUE; + if (match("false")) return Boolean.FALSE; + char c = currentChar(); + if (c == '"') return parseString(); + if (c == '{') return parseObject(); + if (c == '[') return parseArray(); + if (c == '-' || isDigit(c)) return parseNumber(); + error("遇到意外字符 '" + c + "'"); + return null; // 永不到达 + } + + /** + * 解析 JSON 对象,返回 Map + */ + private Map parseObject() { + expect('{'); // 跳过 '{' + skipWhitespace(); + Map map = new LinkedHashMap<>(); + // 空对象 {} + if (currentChar() == '}') { + advance(); // 跳过 '}' + return map; + } + // 多成员对象解析 + while (true) { + skipWhitespace(); + String key = parseString(); // 解析键 + skipWhitespace(); expect(':'); skipWhitespace(); + Object val = parseValue(); // 解析值 + map.put(key, val); + skipWhitespace(); + if (currentChar() == '}') { + advance(); // 跳过 '}' + break; + } + expect(','); skipWhitespace(); + } + return map; + } + + /** + * 解析 JSON 数组,返回 List + */ + private List parseArray() { + expect('['); + skipWhitespace(); + List list = new ArrayList<>(); + // 空数组 [] + if (currentChar() == ']') { + advance(); // 跳过 ']' + return list; + } + // 多元素数组解析 + while (true) { + skipWhitespace(); + list.add(parseValue()); + skipWhitespace(); + if (currentChar() == ']') { + advance(); + break; + } + expect(','); skipWhitespace(); + } + return list; + } + + /** + * 解析 JSON 字符串文字,处理转义字符 + */ + private String parseString() { + expect('"'); // 跳过开头 '"' + StringBuilder sb = new StringBuilder(); + while (true) { + char c = currentChar(); + if (c == '"') { + advance(); // 跳过结束 '"' + break; + } + if (c == '\\') { + advance(); // 跳过 '\' + c = currentChar(); + switch (c) { + case '"': sb.append('"'); break; + case '\\': sb.append('\\'); break; + case '/': sb.append('/'); break; + case 'b': sb.append('\b'); break; + case 'f': sb.append('\f'); break; + case 'n': sb.append('\n'); break; + case 'r': sb.append('\r'); break; + case 't': sb.append('\t'); break; + case 'u': // 解析 Unicode 转义 + String hex = new String(buf, pos+1, 4); + sb.append((char) Integer.parseInt(hex, 16)); + pos += 4; col += 4; + break; + default: + error("无效转义字符 '\\" + c + "'"); + } + advance(); + } else { + sb.append(c); + advance(); + } + } + return sb.toString(); + } + + /** + * 解析 JSON 数值,支持整数、浮点及科学计数法 + */ + private Number parseNumber() { + int start = pos; + if (currentChar() == '-') advance(); + while (isDigit(currentChar())) advance(); + if (currentChar() == '.') { + advance(); + while (isDigit(currentChar())) advance(); + } + if (currentChar() == 'e' || currentChar() == 'E') { + advance(); + if (currentChar() == '+' || currentChar() == '-') advance(); + while (isDigit(currentChar())) advance(); + } + String num = new String(buf, start, pos - start); + // 判断返回 Long 还是 Double + if (num.indexOf('.') >= 0 || num.indexOf('e') >= 0 || num.indexOf('E') >= 0) { + return Double.parseDouble(num); + } + try { + return Long.parseLong(num); + } catch (NumberFormatException e) { + return Double.parseDouble(num); + } + } + + /** + * 跳过所有空白字符,支持空格、制表符、回车、换行 + */ + private void skipWhitespace() { + while (pos < buf.length) { + char c = buf[pos]; + if (c == ' ' || c == '\t' || c == '\r' || c == '\n') { + advance(); + } else { + break; + } + } + } + + /** + * 获取当前位置字符,超出范围返回 '\0' + */ + private char currentChar() { + return pos < buf.length ? buf[pos] : '\0'; + } + + /** + * 推进到下一个字符,并更新行列信息 + */ + private void advance() { + if (pos < buf.length) { + if (buf[pos] == '\n') { + line++; col = 1; + } else { + col++; + } + pos++; + } + } + + /** + * 验证当前位置字符等于预期字符,否则抛出错误 + */ + private void expect(char c) { + if (currentChar() != c) { + error("期望 '" + c + "',但遇到 '" + currentChar() + "'"); + } + advance(); + } + + /** + * 尝试匹配给定字符串,匹配成功则移动位置并返回 true + */ + private boolean match(String s) { + int len = s.length(); + if (pos + len > buf.length) return false; + for (int i = 0; i < len; i++) { + if (buf[pos + i] != s.charAt(i)) return false; + } + for (int i = 0; i < len; i++) advance(); + return true; + } + + /** + * 判断字符是否为数字 + */ + private boolean isDigit(char c) { + return c >= '0' && c <= '9'; + } + + /** + * 抛出带行列定位的解析错误 + */ + private void error(String msg) { + throw new RuntimeException("Error at line " + line + ", column " + col + ": " + msg); + } + } + + // ======= 内部序列化器 ======= + /** + * 负责高效地将 Java 对象写为 JSON 文本 + */ + private static class Writer { + /** 默认 StringBuilder 初始容量,避免频繁扩容 */ + private static final int DEFAULT_CAPACITY = 1024; + + /** + * 入口方法,根据 obj 类型分派写入逻辑 + */ + static String write(Object obj) { + StringBuilder sb = new StringBuilder(DEFAULT_CAPACITY); + writeValue(obj, sb); + return sb.toString(); + } + + /** + * 根据对象类型选择合适的写入方式 + */ + @SuppressWarnings("unchecked") + private static void writeValue(Object obj, StringBuilder sb) { + if (obj == null) { + sb.append("null"); + } else if (obj instanceof String) { + quote((String) obj, sb); + } else if (obj instanceof Number || obj instanceof Boolean) { + sb.append(obj.toString()); + } else if (obj instanceof Map) { + sb.append('{'); + boolean first = true; + for (Entry e : ((Map) obj).entrySet()) { + if (!first) sb.append(','); + first = false; + quote(e.getKey().toString(), sb); + sb.append(':'); + writeValue(e.getValue(), sb); + } + sb.append('}'); + } else if (obj instanceof Collection) { + sb.append('['); + boolean first = true; + for (Object item : (Collection) obj) { + if (!first) sb.append(','); + first = false; + writeValue(item, sb); + } + sb.append(']'); + } else { + // 其他类型,使用 toString 并加引号 + quote(obj.toString(), sb); + } + } + + /** + * 为字符串添加双引号并转义必须的字符 + */ + private static void quote(String s, StringBuilder sb) { + sb.append('"'); + for (char c : s.toCharArray()) { + switch (c) { + case '\\': sb.append("\\\\"); break; + case '"': sb.append("\\\""); break; + case '\n': sb.append("\\n"); break; + case '\r': sb.append("\\r"); break; + case '\t': sb.append("\\t"); break; + default: sb.append(c); + } + } + sb.append('"'); + } + } +} diff --git a/src/main/java/org/jcnc/snow/compiler/parser/util/JsonFormatter.java b/src/main/java/org/jcnc/snow/compiler/parser/util/JsonFormatter.java index 6c1b501..498f8ae 100644 --- a/src/main/java/org/jcnc/snow/compiler/parser/util/JsonFormatter.java +++ b/src/main/java/org/jcnc/snow/compiler/parser/util/JsonFormatter.java @@ -37,28 +37,24 @@ public class JsonFormatter { sb.append(c); } else if (!inQuotes) { switch (c) { - case '{', '[': + case '{', '[' -> { sb.append(c).append('\n'); indent++; appendIndent(sb, indent); - break; - case '}', ']': + } + case '}', ']' -> { sb.append('\n'); indent--; appendIndent(sb, indent); sb.append(c); - break; - case ',': - sb.append(c).append('\n'); - appendIndent(sb, indent); - break; - case ':': - sb.append(c).append(' '); - break; - default: + } + case ',' -> sb.append(c).append('\n').append(" ".repeat(indent)); + case ':' -> sb.append(c).append(' '); + default -> { if (!Character.isWhitespace(c)) { sb.append(c); } + } } } else { // 字符串内部原样输出 @@ -78,4 +74,4 @@ public class JsonFormatter { private static void appendIndent(StringBuilder sb, int indent) { sb.append(" ".repeat(Math.max(0, indent))); } -} +} \ No newline at end of file diff --git a/test b/test index d2eb11e..2f24c35 100644 --- a/test +++ b/test @@ -1,15 +1,11 @@ module: CommonTasks function: add_numbers - body: return num1 + num2 end body - parameter: - declare num1: int declare num2: int - return_type: int end function @@ -41,7 +37,6 @@ end module module: MainModule import: CommonTasks, MathUtils, StringUtils, BuiltinUtils - function: test parameter: declare first_str: string @@ -52,7 +47,6 @@ module: MainModule end body end function - function: main parameter: declare args: string @@ -89,9 +83,9 @@ module: MainModule BuiltinUtils.print(" first inner") else if j % 2 == 0 then - BuiltinUtils.print(" j even") + BuiltinUtils.print("j even") else - BuiltinUtils.print(" j odd") + BuiltinUtils.print("j odd") end if end if end body