feat: 支持数组元素赋值操作
- 新增 __setindex_x 系列内置函数,用于数组元素赋值 - 实现了对 byte、short、int、long、float、double、boolean 和引用类型数组的支持 - 修改了 ExpressionBuilder 和 StatementBuilder以支持数组赋值语法 - 更新了 VirtualMachineEngine 和 SyscallCommand 以支持新的 ARR_SET系统调用
This commit is contained in:
parent
477591303a
commit
d3a85a24bf
@ -77,7 +77,7 @@ public class CallGenerator implements InstructionGenerator<CallInstruction> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 各种一维数组类型(byte/short/int/long/float/double/boolean)
|
// 各种一维数组类型(byte/short/int/long/float/double/boolean)读取
|
||||||
switch (fn) {
|
switch (fn) {
|
||||||
case "__index_b" -> {
|
case "__index_b" -> {
|
||||||
generateIndexInstruction(ins, out, slotMap, 'B');
|
generateIndexInstruction(ins, out, slotMap, 'B');
|
||||||
@ -107,6 +107,35 @@ public class CallGenerator implements InstructionGenerator<CallInstruction> {
|
|||||||
generateIndexInstruction(ins, out, slotMap, 'R');
|
generateIndexInstruction(ins, out, slotMap, 'R');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "__setindex_b" -> {
|
||||||
|
generateSetIndexInstruction(ins, out, slotMap, 'B');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case "__setindex_s" -> {
|
||||||
|
generateSetIndexInstruction(ins, out, slotMap, 'S');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case "__setindex_i" -> {
|
||||||
|
generateSetIndexInstruction(ins, out, slotMap, 'I');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case "__setindex_l" -> {
|
||||||
|
generateSetIndexInstruction(ins, out, slotMap, 'L');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case "__setindex_f" -> {
|
||||||
|
generateSetIndexInstruction(ins, out, slotMap, 'F');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case "__setindex_d" -> {
|
||||||
|
generateSetIndexInstruction(ins, out, slotMap, 'D');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case "__setindex_r" -> {
|
||||||
|
generateSetIndexInstruction(ins, out, slotMap, 'R');
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 普通函数调用
|
// 普通函数调用
|
||||||
@ -115,6 +144,54 @@ public class CallGenerator implements InstructionGenerator<CallInstruction> {
|
|||||||
|
|
||||||
// ========== 私有辅助方法 ==========
|
// ========== 私有辅助方法 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成数组元素赋值指令(arr[idx] = value),无返回值。
|
||||||
|
* <p>
|
||||||
|
* 调用栈压栈顺序为:arr (引用类型, R),idx (整型, I),value (元素类型, T)。
|
||||||
|
* 执行 SYSCALL ARR_SET 指令以完成数组赋值操作。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param ins 当前的调用指令(包含参数信息)
|
||||||
|
* @param out VM 指令生成器(用于输出 VM 指令)
|
||||||
|
* @param slotMap 虚拟寄存器与槽位的映射表
|
||||||
|
* @param valType 待写入的 value 的类型标识('B': byte, 'S': short, 'I': int, 'L': long, 'F': float, 'D': double, 其余为引用类型'R')
|
||||||
|
* @throws IllegalStateException 如果参数数量不为3,则抛出异常
|
||||||
|
*/
|
||||||
|
private void generateSetIndexInstruction(CallInstruction ins,
|
||||||
|
VMProgramBuilder out,
|
||||||
|
Map<IRVirtualRegister, Integer> slotMap,
|
||||||
|
char valType) {
|
||||||
|
List<IRValue> args = ins.getArguments();
|
||||||
|
if (args.size() != 3) {
|
||||||
|
// 参数数量错误,抛出异常并输出实际参数列表
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"[CallGenerator] __setindex_* 需要三个参数(arr, idx, value),实际: " + args);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第一个参数为数组对象,压入引用类型寄存器('R'),如 arr
|
||||||
|
loadArgument(out, slotMap, args.get(0), 'R', ins.getFunctionName());
|
||||||
|
|
||||||
|
// 第二个参数为索引值,压入整型寄存器('I'),如 idx
|
||||||
|
loadArgument(out, slotMap, args.get(1), 'I', ins.getFunctionName());
|
||||||
|
|
||||||
|
// 第三个参数为待赋值元素,根据元素类型压入相应类型寄存器
|
||||||
|
// 支持类型:'B'(byte), 'S'(short), 'I'(int), 'L'(long), 'F'(float), 'D'(double)
|
||||||
|
// 其他情况(如引用类型),按'R'处理
|
||||||
|
switch (valType) {
|
||||||
|
case 'B' -> loadArgument(out, slotMap, args.get(2), 'B', ins.getFunctionName());
|
||||||
|
case 'S' -> loadArgument(out, slotMap, args.get(2), 'S', ins.getFunctionName());
|
||||||
|
case 'I' -> loadArgument(out, slotMap, args.get(2), 'I', ins.getFunctionName());
|
||||||
|
case 'L' -> loadArgument(out, slotMap, args.get(2), 'L', ins.getFunctionName());
|
||||||
|
case 'F' -> loadArgument(out, slotMap, args.get(2), 'F', ins.getFunctionName());
|
||||||
|
case 'D' -> loadArgument(out, slotMap, args.get(2), 'D', ins.getFunctionName());
|
||||||
|
default -> loadArgument(out, slotMap, args.get(2), 'R', ins.getFunctionName());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 输出 VM 指令:SYSCALL ARR_SET,完成数组元素写入操作
|
||||||
|
out.emit(VMOpCode.SYSCALL + " " + "ARR_SET");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析 syscall 子命令(第一个参数),支持字符串常量与已绑定字符串的虚拟寄存器。
|
* 解析 syscall 子命令(第一个参数),支持字符串常量与已绑定字符串的虚拟寄存器。
|
||||||
*
|
*
|
||||||
|
|||||||
@ -191,15 +191,15 @@ public record ExpressionBuilder(IRContext ctx) {
|
|||||||
int p = base.indexOf('[');
|
int p = base.indexOf('[');
|
||||||
if (p > 0) base = base.substring(0, p); // 基本类型
|
if (p > 0) base = base.substring(0, p); // 基本类型
|
||||||
switch (base) {
|
switch (base) {
|
||||||
case "byte" -> func = "__index_b";
|
case "byte" -> func = "__index_b";
|
||||||
case "short" -> func = "__index_s";
|
case "short" -> func = "__index_s";
|
||||||
case "int" -> func = "__index_i";
|
case "int" -> func = "__index_i";
|
||||||
case "long" -> func = "__index_l";
|
case "long" -> func = "__index_l";
|
||||||
case "float" -> func = "__index_f";
|
case "float" -> func = "__index_f";
|
||||||
case "double" -> func = "__index_d";
|
case "double" -> func = "__index_d";
|
||||||
case "boolean" -> func = "__index_i"; // 布尔型用 int 通道返回 1/0
|
case "boolean" -> func = "__index_i"; // 布尔型用 int 通道返回 1/0
|
||||||
case "string" -> func = "__index_r"; // 字符串/其它未识别类型均走引用
|
case "string" -> func = "__index_r"; // 字符串/其它未识别类型均走引用
|
||||||
default -> func = "__index_r";
|
default -> func = "__index_r";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -222,7 +222,7 @@ public record ExpressionBuilder(IRContext ctx) {
|
|||||||
* @param node 下标访问表达式节点
|
* @param node 下标访问表达式节点
|
||||||
* @return 存放引用结果的虚拟寄存器
|
* @return 存放引用结果的虚拟寄存器
|
||||||
*/
|
*/
|
||||||
private IRVirtualRegister buildIndexRef(IndexExpressionNode node) {
|
public IRVirtualRegister buildIndexRef(IndexExpressionNode node) {
|
||||||
// 1. 常量折叠:如果 array 和 index 都是编译期常量,直接取值
|
// 1. 常量折叠:如果 array 和 index 都是编译期常量,直接取值
|
||||||
Object arrConst = tryFoldConst(node.array());
|
Object arrConst = tryFoldConst(node.array());
|
||||||
Object idxConst = tryFoldConst(node.index());
|
Object idxConst = tryFoldConst(node.index());
|
||||||
|
|||||||
@ -103,6 +103,57 @@ public class StatementBuilder {
|
|||||||
ctx.clearVarType();
|
ctx.clearVarType();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==== 支持数组下标赋值 arr[idx] = value ====
|
||||||
|
if (stmt instanceof IndexAssignmentNode idxAssign) {
|
||||||
|
IndexExpressionNode target = idxAssign.target();
|
||||||
|
|
||||||
|
// 1. 目标数组寄存器:多维时用 buildIndexRef 拿引用
|
||||||
|
IRVirtualRegister arrReg = (target.array() instanceof IndexExpressionNode inner)
|
||||||
|
? expr.buildIndexRef(inner)
|
||||||
|
: expr.build(target.array());
|
||||||
|
|
||||||
|
// 2. 下标与右值
|
||||||
|
IRVirtualRegister idxReg = expr.build(target.index());
|
||||||
|
IRVirtualRegister valReg = expr.build(idxAssign.value());
|
||||||
|
|
||||||
|
// 3. 选择内置函数名 __setindex_x,根据元素类型分派
|
||||||
|
String func = "__setindex_r";
|
||||||
|
org.jcnc.snow.compiler.parser.ast.base.ExpressionNode base = target.array();
|
||||||
|
while (base instanceof IndexExpressionNode innerIdx) base = innerIdx.array();
|
||||||
|
if (base instanceof IdentifierNode id) {
|
||||||
|
String arrType = ctx.getScope().lookupType(id.name());
|
||||||
|
if (arrType != null) {
|
||||||
|
String elemType = arrType.endsWith("[]") ? arrType.substring(0, arrType.length() - 2) : arrType;
|
||||||
|
switch (elemType) {
|
||||||
|
case "byte" -> func = "__setindex_b";
|
||||||
|
case "short" -> func = "__setindex_s";
|
||||||
|
case "int" -> func = "__setindex_i";
|
||||||
|
case "long" -> func = "__setindex_l";
|
||||||
|
case "float" -> func = "__setindex_f";
|
||||||
|
case "double" -> func = "__setindex_d";
|
||||||
|
case "boolean" -> func = "__setindex_i";
|
||||||
|
case "string" -> func = "__setindex_r";
|
||||||
|
default -> func = "__setindex_r";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 生成 CALL 指令
|
||||||
|
java.util.List<org.jcnc.snow.compiler.ir.core.IRValue> argv =
|
||||||
|
java.util.List.of(arrReg, idxReg, valReg);
|
||||||
|
ctx.addInstruction(new org.jcnc.snow.compiler.ir.instruction.CallInstruction(null, func, argv));
|
||||||
|
|
||||||
|
// 5. 赋值后清理常量绑定
|
||||||
|
try {
|
||||||
|
if (base instanceof IdentifierNode id2) {
|
||||||
|
ctx.getScope().clearConstValue(id2.name());
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (stmt instanceof DeclarationNode decl) {
|
if (stmt instanceof DeclarationNode decl) {
|
||||||
// 变量声明语句(如 int a = 1;)
|
// 变量声明语句(如 int a = 1;)
|
||||||
if (decl.getInitializer().isPresent()) {
|
if (decl.getInitializer().isPresent()) {
|
||||||
|
|||||||
@ -14,292 +14,35 @@ import java.util.List;
|
|||||||
* and represents the "reference push" instruction ({@code R_PUSH}) in the virtual machine.
|
* and represents the "reference push" instruction ({@code R_PUSH}) in the virtual machine.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* This instruction pushes a reference value—typically a string literal or an array literal—onto the operand stack.
|
* This instruction pushes a reference-type value onto the operand stack.
|
||||||
* </p>
|
* The input is parsed from the textual instruction form, which can represent:
|
||||||
*
|
|
||||||
* <p><b>Instruction format:</b> {@code R_PUSH <literal>}</p>
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* Example usages:
|
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>{@code R_PUSH "hello"} → push the string {@code "hello"} onto stack</li>
|
* <li>String literals</li>
|
||||||
* <li>{@code R_PUSH [1,2,3]} → push an (unmodifiable) {@code List<Integer>} onto stack</li>
|
* <li>Array literals (e.g., {@code [1, 2, 3]}), including nested arrays</li>
|
||||||
* <li>{@code R_PUSH [[1,2,3],[4,5,6]]} → push a nested (unmodifiable) {@code List<List<Integer>>} onto stack</li>
|
|
||||||
* </ul>
|
* </ul>
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Supported element types in array literals include integers, floating-point numbers (parsed as {@code Double}),
|
* For array literals, a nested list structure is constructed. In this implementation,
|
||||||
* booleans ({@code true}/{@code false} → {@code 1}/{@code 0}), quoted strings (surrounded by double quotes),
|
* array literals are pushed as <b>mutable</b> {@link java.util.ArrayList} structures,
|
||||||
* and further nested arrays.
|
* so that subsequent system calls such as {@code ARR_SET} can modify elements in-place.
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
public final class RPushCommand implements Command {
|
public class RPushCommand implements Command {
|
||||||
|
|
||||||
// ======== Parsing helpers ========
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deeply wraps lists as unmodifiable; leaves scalars unchanged.
|
* Executes the R_PUSH command.
|
||||||
*
|
*
|
||||||
* @param v input object
|
* @param parts The parts of the instruction, where {@code parts[1..n]} are concatenated as the literal.
|
||||||
* @return deeply unmodifiable version of the object
|
* @param pc The current program counter.
|
||||||
*/
|
* @param stack The operand stack where the result will be pushed.
|
||||||
private static Object deepUnmodifiableObject(Object v) {
|
* @param local The local variable store (unused in this instruction).
|
||||||
if (v instanceof List<?> l) {
|
* @param callStack The call stack (unused in this instruction).
|
||||||
return deepUnmodifiable(l);
|
* @return The new program counter (typically {@code pc+1}).
|
||||||
}
|
* @throws IllegalStateException if no literal parameter is provided.
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursively wraps all nested lists as unmodifiable.
|
|
||||||
*
|
|
||||||
* @param l input list
|
|
||||||
* @return deeply unmodifiable list
|
|
||||||
*/
|
|
||||||
private static List<?> deepUnmodifiable(List<?> l) {
|
|
||||||
List<Object> out = new ArrayList<>(l.size());
|
|
||||||
for (Object v : l) out.add(deepUnmodifiableObject(v));
|
|
||||||
return Collections.unmodifiableList(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a value starting from the cursor.
|
|
||||||
* Skips whitespace, and delegates to the appropriate sub-parser depending on the character:
|
|
||||||
* array, quoted string, or atomic value.
|
|
||||||
*
|
|
||||||
* @param c cursor
|
|
||||||
* @return parsed value (Object)
|
|
||||||
*/
|
|
||||||
private static Object parseValue(Cursor c) {
|
|
||||||
skipWs(c);
|
|
||||||
if (c.end()) return "";
|
|
||||||
char ch = c.ch();
|
|
||||||
if (ch == '[') return parseArray(c);
|
|
||||||
if (ch == '\"') return parseQuoted(c);
|
|
||||||
return parseAtom(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses an array literal from the cursor, supporting nested structures.
|
|
||||||
* Assumes the current character is '['.
|
|
||||||
*
|
|
||||||
* @param c cursor
|
|
||||||
* @return List of parsed objects
|
|
||||||
*/
|
|
||||||
private static List<Object> parseArray(Cursor c) {
|
|
||||||
// assumes current char is '['
|
|
||||||
expect(c, '[');
|
|
||||||
skipWs(c);
|
|
||||||
List<Object> values = new ArrayList<>();
|
|
||||||
if (!peek(c, ']')) {
|
|
||||||
while (true) {
|
|
||||||
Object v = parseValue(c);
|
|
||||||
values.add(v);
|
|
||||||
skipWs(c);
|
|
||||||
if (peek(c, ',')) {
|
|
||||||
c.i++; // consume ','
|
|
||||||
skipWs(c);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
expect(c, ']');
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======== Recursive-descent parser for array literals ========
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a string literal wrapped in double quotes (supports common escape sequences).
|
|
||||||
* <p>
|
|
||||||
* Assumes the cursor currently points to the starting quote character ("), and consumes the opening quote.
|
|
||||||
* Parses the string content from the cursor, stopping at the closing quote (").
|
|
||||||
* Supported escape sequences:
|
|
||||||
* <ul>
|
|
||||||
* <li>\n newline</li>
|
|
||||||
* <li>\r carriage return</li>
|
|
||||||
* <li>\t tab</li>
|
|
||||||
* <li>\" double quote itself</li>
|
|
||||||
* <li>\\ backslash</li>
|
|
||||||
* <li>Other characters are output as-is</li>
|
|
||||||
* </ul>
|
|
||||||
* If the string is not closed properly (i.e., no closing quote is found before the end), returns the currently parsed content.
|
|
||||||
*
|
|
||||||
* @param c cursor object (must support ch() for current char, i for index, end() for boundary check)
|
|
||||||
* @return parsed string content
|
|
||||||
*/
|
|
||||||
private static String parseQuoted(Cursor c) {
|
|
||||||
// Assume current position is the opening quote; consume it
|
|
||||||
expect(c, '\"');
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
|
|
||||||
// Traverse until the end or an unclosed string
|
|
||||||
while (!c.end()) {
|
|
||||||
char ch = c.ch();
|
|
||||||
c.i++;
|
|
||||||
if (ch == '\\') { // handle escape sequences
|
|
||||||
if (c.end()) break; // nothing after escape char
|
|
||||||
char nxt = c.ch();
|
|
||||||
c.i++;
|
|
||||||
// Common escapes
|
|
||||||
switch (nxt) {
|
|
||||||
case 'n' -> sb.append('\n'); // newline
|
|
||||||
case 'r' -> sb.append('\r'); // carriage return
|
|
||||||
case 't' -> sb.append('\t'); // tab
|
|
||||||
case '\"' -> sb.append('\"'); // double quote
|
|
||||||
case '\\' -> sb.append('\\'); // backslash
|
|
||||||
default -> sb.append(nxt); // any other char as-is
|
|
||||||
}
|
|
||||||
} else if (ch == '\"') {
|
|
||||||
// Found closing quote; end of string
|
|
||||||
return sb.toString();
|
|
||||||
} else {
|
|
||||||
// Regular character
|
|
||||||
sb.append(ch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Unclosed string, return parsed content
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses an atomic constant ("atom"), supporting type-suffixed numbers and booleans.
|
|
||||||
* <p>
|
|
||||||
* Examples: 0.1f, 123L, 3.14d, 100, true, false<br>
|
|
||||||
* Parsing rules:
|
|
||||||
* <ul>
|
|
||||||
* <li>Supports float(f/F), long(l/L), double(d/D), short(s/S), byte(b/B) type suffixes</li>
|
|
||||||
* <li>Supports boolean true/false (case-insensitive, converted to 1/0)</li>
|
|
||||||
* <li>Decimals without suffix parsed as double; integers without suffix as int</li>
|
|
||||||
* <li>If parsing fails, returns the original string</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* @param c cursor, must support ch() for current char, i for index, end() for boundary check, s for the original string
|
|
||||||
* @return parsed Object
|
|
||||||
*/
|
|
||||||
private static Object parseAtom(Cursor c) {
|
|
||||||
int start = c.i;
|
|
||||||
// Read until a comma, ']' or whitespace
|
|
||||||
while (!c.end()) {
|
|
||||||
char ch = c.ch();
|
|
||||||
if (ch == ',' || ch == ']') break;
|
|
||||||
if (Character.isWhitespace(ch)) break;
|
|
||||||
c.i++;
|
|
||||||
}
|
|
||||||
// Extract current token
|
|
||||||
String token = c.s.substring(start, c.i).trim();
|
|
||||||
if (token.isEmpty()) return "";
|
|
||||||
// Boolean parsing (case-insensitive, convert to 1/0)
|
|
||||||
if ("true".equalsIgnoreCase(token)) return 1;
|
|
||||||
if ("false".equalsIgnoreCase(token)) return 0;
|
|
||||||
// Handle numeric type suffixes
|
|
||||||
try {
|
|
||||||
char last = token.charAt(token.length() - 1);
|
|
||||||
switch (last) {
|
|
||||||
case 'f':
|
|
||||||
case 'F':
|
|
||||||
// float suffix
|
|
||||||
return Float.parseFloat(token.substring(0, token.length() - 1));
|
|
||||||
case 'l':
|
|
||||||
case 'L':
|
|
||||||
// long suffix
|
|
||||||
return Long.parseLong(token.substring(0, token.length() - 1));
|
|
||||||
case 'd':
|
|
||||||
case 'D':
|
|
||||||
// double suffix
|
|
||||||
return Double.parseDouble(token.substring(0, token.length() - 1));
|
|
||||||
case 's':
|
|
||||||
case 'S':
|
|
||||||
// short suffix
|
|
||||||
return Short.parseShort(token.substring(0, token.length() - 1));
|
|
||||||
case 'b':
|
|
||||||
case 'B':
|
|
||||||
// byte suffix
|
|
||||||
return Byte.parseByte(token.substring(0, token.length() - 1));
|
|
||||||
default:
|
|
||||||
// No suffix, check for floating point or integer
|
|
||||||
if (token.contains(".") || token.contains("e") || token.contains("E")) {
|
|
||||||
return Double.parseDouble(token);
|
|
||||||
} else {
|
|
||||||
return Integer.parseInt(token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (NumberFormatException ex) {
|
|
||||||
// Parsing failed, fall back to original string (e.g. identifiers)
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Skips all whitespace characters at the current cursor position until a non-whitespace or end of text is reached.
|
|
||||||
* <p>
|
|
||||||
* The cursor index is automatically incremented, so it will point to the next non-whitespace character (or end of text).
|
|
||||||
*
|
|
||||||
* @param c cursor object (must support ch() for current char, i for index, end() for boundary check)
|
|
||||||
*/
|
|
||||||
private static void skipWs(Cursor c) {
|
|
||||||
// Increment cursor while not at end and is whitespace
|
|
||||||
while (!c.end() && Character.isWhitespace(c.ch())) {
|
|
||||||
c.i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the current cursor position matches the specified character.
|
|
||||||
*
|
|
||||||
* @param c cursor object
|
|
||||||
* @param ch expected character
|
|
||||||
* @return true if not at end and character matches ch, otherwise false
|
|
||||||
*/
|
|
||||||
private static boolean peek(Cursor c, char ch) {
|
|
||||||
return !c.end() && c.ch() == ch;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the current cursor position is the specified character; throws if not.
|
|
||||||
* If it matches, skips the character and any following whitespace.
|
|
||||||
*
|
|
||||||
* @param c cursor object
|
|
||||||
* @param ch expected character
|
|
||||||
* @throws IllegalArgumentException if current position is not the expected character
|
|
||||||
*/
|
|
||||||
private static void expect(Cursor c, char ch) {
|
|
||||||
if (c.end() || c.ch() != ch)
|
|
||||||
throw new IllegalArgumentException("R_PUSH array literal parse error: expected '" + ch + "' at position " + c.i);
|
|
||||||
c.i++; // Consume current character
|
|
||||||
skipWs(c); // Skip any subsequent whitespace
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes the R_PUSH instruction: pushes a constant or array constant onto the operand stack.
|
|
||||||
* <p>
|
|
||||||
* Processing steps:
|
|
||||||
* <ul>
|
|
||||||
* <li>1. Checks parameter count, throws if insufficient.</li>
|
|
||||||
* <li>2. Concatenates all arguments (except opcode) into a raw literal string.</li>
|
|
||||||
* <li>3. Checks if the literal is an array (starts with [ and ends with ]).</li>
|
|
||||||
* <li>4. If array, recursively parses and pushes as a read-only List onto the operand stack.</li>
|
|
||||||
* <li>5. Otherwise, pushes the literal string as-is.</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* @param parts instruction and parameter strings (parts[0] is the opcode, others are params)
|
|
||||||
* @param pc current instruction index
|
|
||||||
* @param stack operand stack
|
|
||||||
* @param lvs local variable store
|
|
||||||
* @param cs call stack
|
|
||||||
* @return next instruction index
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int execute(String[] parts, int pc,
|
public int execute(String[] parts, int pc, OperandStack stack, LocalVariableStore local, CallStack callStack) {
|
||||||
OperandStack stack,
|
|
||||||
LocalVariableStore lvs,
|
|
||||||
CallStack cs) {
|
|
||||||
|
|
||||||
// Check parameter count
|
|
||||||
if (parts.length < 2)
|
if (parts.length < 2)
|
||||||
throw new IllegalStateException("R_PUSH missing parameter");
|
throw new IllegalStateException("R_PUSH missing parameter");
|
||||||
|
|
||||||
@ -318,8 +61,8 @@ public final class RPushCommand implements Command {
|
|||||||
// Should not happen in theory; safety fallback
|
// Should not happen in theory; safety fallback
|
||||||
stack.push(parsed);
|
stack.push(parsed);
|
||||||
} else {
|
} else {
|
||||||
// Convert to read-only List before pushing to prevent modification
|
// Push a deep-mutable copy so ARR_SET can modify elements in-place
|
||||||
stack.push(deepUnmodifiable(list));
|
stack.push(deepMutable(list));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Regular string, push as-is
|
// Regular string, push as-is
|
||||||
@ -331,19 +74,29 @@ public final class RPushCommand implements Command {
|
|||||||
/**
|
/**
|
||||||
* A simple string cursor, supporting index increment and character reading, for use by the parser.
|
* A simple string cursor, supporting index increment and character reading, for use by the parser.
|
||||||
*/
|
*/
|
||||||
private static final class Cursor {
|
static class Cursor {
|
||||||
final String s; // Original string
|
final String s;
|
||||||
int i; // Current index
|
int i;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new {@code Cursor} for the given string.
|
||||||
|
*
|
||||||
|
* @param s The string to parse.
|
||||||
|
*/
|
||||||
Cursor(String s) {
|
Cursor(String s) {
|
||||||
this.s = s;
|
this.s = s;
|
||||||
this.i = 0;
|
this.i = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the cursor is at the end of the string.
|
* Advances the cursor by one character.
|
||||||
*
|
*/
|
||||||
* @return true if at end
|
void skip() {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@code true} if the cursor has reached the end of the string.
|
||||||
*/
|
*/
|
||||||
boolean end() {
|
boolean end() {
|
||||||
return i >= s.length();
|
return i >= s.length();
|
||||||
@ -353,10 +106,186 @@ public final class RPushCommand implements Command {
|
|||||||
* Gets the character at the current cursor position.
|
* Gets the character at the current cursor position.
|
||||||
*
|
*
|
||||||
* @return current character
|
* @return current character
|
||||||
|
* @throws StringIndexOutOfBoundsException if at end of string
|
||||||
*/
|
*/
|
||||||
char ch() {
|
char ch() {
|
||||||
return s.charAt(i);
|
return s.charAt(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a value from the input string at the current cursor position.
|
||||||
|
* This can be an array literal, a quoted string, or a simple atom (number, word).
|
||||||
|
*
|
||||||
|
* @param c The cursor for parsing.
|
||||||
|
* @return The parsed value (could be List, String, Number).
|
||||||
|
*/
|
||||||
|
Object parseValue(Cursor c) {
|
||||||
|
skipWs(c);
|
||||||
|
if (c.end()) return "";
|
||||||
|
char ch = c.ch();
|
||||||
|
if (ch == '[') return parseArray(c);
|
||||||
|
if (ch == '\"') return parseQuoted(c);
|
||||||
|
return parseAtom(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skips whitespace characters in the input string.
|
||||||
|
*
|
||||||
|
* @param c The cursor to advance.
|
||||||
|
*/
|
||||||
|
private static void skipWs(Cursor c) {
|
||||||
|
while (!c.end()) {
|
||||||
|
char ch = c.ch();
|
||||||
|
if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') c.skip();
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses an array literal from the input, including nested arrays.
|
||||||
|
*
|
||||||
|
* @param c The cursor (positioned at '[' at entry).
|
||||||
|
* @return A List representing the parsed array.
|
||||||
|
*/
|
||||||
|
private Object parseArray(Cursor c) {
|
||||||
|
// assumes current char is '['
|
||||||
|
c.skip(); // skip '['
|
||||||
|
List<Object> out = new ArrayList<>();
|
||||||
|
skipWs(c);
|
||||||
|
while (!c.end()) {
|
||||||
|
if (c.ch() == ']') {
|
||||||
|
c.skip(); // skip ']'
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Object v = parseValue(c);
|
||||||
|
out.add(v);
|
||||||
|
skipWs(c);
|
||||||
|
if (!c.end() && c.ch() == ',') {
|
||||||
|
c.skip();
|
||||||
|
skipWs(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a quoted string literal, handling escape characters.
|
||||||
|
*
|
||||||
|
* @param c The cursor (positioned at '"' at entry).
|
||||||
|
* @return The parsed string value.
|
||||||
|
*/
|
||||||
|
private static String parseQuoted(Cursor c) {
|
||||||
|
// assumes current char is '"'
|
||||||
|
c.skip(); // skip opening quote
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
while (!c.end()) {
|
||||||
|
char ch = c.ch();
|
||||||
|
c.skip();
|
||||||
|
if (ch == '\\') {
|
||||||
|
if (c.end()) break;
|
||||||
|
char esc = c.ch();
|
||||||
|
c.skip();
|
||||||
|
switch (esc) {
|
||||||
|
case 'n' -> sb.append('\n');
|
||||||
|
case 'r' -> sb.append('\r');
|
||||||
|
case 't' -> sb.append('\t');
|
||||||
|
case '\"' -> sb.append('\"');
|
||||||
|
case '\\' -> sb.append('\\');
|
||||||
|
default -> sb.append(esc);
|
||||||
|
}
|
||||||
|
} else if (ch == '\"') {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
sb.append(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses an atom (number, hexadecimal, binary, or plain string token).
|
||||||
|
*
|
||||||
|
* @param c The cursor.
|
||||||
|
* @return An Integer, Double, or String, depending on the content.
|
||||||
|
*/
|
||||||
|
private static Object parseAtom(Cursor c) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
while (!c.end()) {
|
||||||
|
char ch = c.ch();
|
||||||
|
if (ch == ',' || ch == ']' || ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') break;
|
||||||
|
sb.append(ch);
|
||||||
|
c.skip();
|
||||||
|
}
|
||||||
|
String token = sb.toString();
|
||||||
|
// try number
|
||||||
|
try {
|
||||||
|
if (token.startsWith("0x") || token.startsWith("0X")) {
|
||||||
|
return Integer.parseInt(token.substring(2), 16);
|
||||||
|
}
|
||||||
|
if (token.startsWith("0b") || token.startsWith("0B")) {
|
||||||
|
return Integer.parseInt(token.substring(2), 2);
|
||||||
|
}
|
||||||
|
if (token.contains(".")) {
|
||||||
|
return Double.parseDouble(token);
|
||||||
|
}
|
||||||
|
return Integer.parseInt(token);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
// fallback as string
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------- helpers for immutability/mutability ----------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively creates an unmodifiable copy of a list, with all nested lists also unmodifiable.
|
||||||
|
*
|
||||||
|
* @param l The list to make unmodifiable.
|
||||||
|
* @return An unmodifiable deep copy of the list.
|
||||||
|
*/
|
||||||
|
List<?> deepUnmodifiable(List<?> l) {
|
||||||
|
List<Object> out = new ArrayList<>(l.size());
|
||||||
|
for (Object v : l) out.add(deepUnmodifiableObject(v));
|
||||||
|
return Collections.unmodifiableList(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method for {@link #deepUnmodifiable(List)}. Recursively processes each element.
|
||||||
|
*
|
||||||
|
* @param v The object to process.
|
||||||
|
* @return Unmodifiable list if input is a list, otherwise the value itself.
|
||||||
|
*/
|
||||||
|
Object deepUnmodifiableObject(Object v) {
|
||||||
|
if (v instanceof List<?> l) {
|
||||||
|
return deepUnmodifiable(l);
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a deep mutable copy of a nested List structure, preserving element values.
|
||||||
|
* Nested lists are turned into {@link java.util.ArrayList} so they can be modified by ARR_SET.
|
||||||
|
*
|
||||||
|
* @param l The source list.
|
||||||
|
* @return Deep mutable copy of the list.
|
||||||
|
*/
|
||||||
|
private static java.util.List<?> deepMutable(java.util.List<?> l) {
|
||||||
|
java.util.List<Object> out = new java.util.ArrayList<>(l.size());
|
||||||
|
for (Object v : l) out.add(deepMutableObject(v));
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method for {@link #deepMutable(List)}. Recursively processes each element.
|
||||||
|
*
|
||||||
|
* @param v The object to process.
|
||||||
|
* @return Mutable list if input is a list, otherwise the value itself.
|
||||||
|
*/
|
||||||
|
private static Object deepMutableObject(Object v) {
|
||||||
|
if (v instanceof java.util.List<?> l) {
|
||||||
|
return deepMutable(l);
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -381,6 +381,40 @@ public class SyscallCommand implements Command {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "ARR_SET" -> {
|
||||||
|
/*
|
||||||
|
arr[idx] = value
|
||||||
|
支持 List 和所有原生数组类型(int[], double[], ...)
|
||||||
|
参数顺序:栈顶 value、idx、arr
|
||||||
|
不返回值(成功/失败由异常控制)
|
||||||
|
*/
|
||||||
|
Object value = stack.pop();
|
||||||
|
Object idxObj = stack.pop();
|
||||||
|
Object arrObj = stack.pop();
|
||||||
|
int idx;
|
||||||
|
if (idxObj instanceof Number n) idx = n.intValue();
|
||||||
|
else if (idxObj instanceof String s) idx = Integer.parseInt(s.trim());
|
||||||
|
else throw new IllegalArgumentException("ARR_SET: invalid index type " + idxObj);
|
||||||
|
|
||||||
|
if (arrObj instanceof java.util.List<?> list) {
|
||||||
|
// 必须是可变 List
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
java.util.List<Object> mlist = (java.util.List<Object>) list;
|
||||||
|
if (idx < 0 || idx >= mlist.size())
|
||||||
|
throw new IndexOutOfBoundsException("数组下标越界: " + idx + " (长度 " + mlist.size() + ")");
|
||||||
|
mlist.set(idx, value);
|
||||||
|
} else if (arrObj != null && arrObj.getClass().isArray()) {
|
||||||
|
int len = java.lang.reflect.Array.getLength(arrObj);
|
||||||
|
if (idx < 0 || idx >= len)
|
||||||
|
throw new IndexOutOfBoundsException("数组下标越界: " + idx + " (长度 " + len + ")");
|
||||||
|
java.lang.reflect.Array.set(arrObj, idx, value);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("ARR_SET: not an array/list: " + arrObj);
|
||||||
|
}
|
||||||
|
// 操作成功,push 0 作为 ok 信号;不需要返回时可省略
|
||||||
|
stack.push(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// 控制台输出
|
// 控制台输出
|
||||||
case "PRINT" -> {
|
case "PRINT" -> {
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
package org.jcnc.snow.vm.engine;
|
package org.jcnc.snow.vm.engine;
|
||||||
|
|
||||||
import org.jcnc.snow.common.Mode;
|
|
||||||
import org.jcnc.snow.vm.execution.CommandExecutionHandler;
|
import org.jcnc.snow.vm.execution.CommandExecutionHandler;
|
||||||
import org.jcnc.snow.vm.module.*;
|
import org.jcnc.snow.vm.module.*;
|
||||||
|
|
||||||
@ -51,8 +50,6 @@ public class VirtualMachineEngine {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds a VM engine with fresh runtime structures.
|
* Builds a VM engine with fresh runtime structures.
|
||||||
*
|
|
||||||
* @param vmMode execution mode (DEBUG / RUN)
|
|
||||||
*/
|
*/
|
||||||
public VirtualMachineEngine() {
|
public VirtualMachineEngine() {
|
||||||
this.operandStack = new OperandStack();
|
this.operandStack = new OperandStack();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user