!11 release: 合并 v0.3.0 版本至 main 分支

Merge pull request !11 from Luke/release/v0.3.0
This commit is contained in:
Luke 2025-06-14 01:32:23 +00:00 committed by Gitee
commit e134a7a7ec
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
27 changed files with 570 additions and 120 deletions

17
.run/Demo6.run.xml Normal file
View File

@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Demo6" type="Application" factoryName="Application" folderName="Demo" activateToolWindowBeforeRun="false">
<option name="ALTERNATIVE_JRE_PATH" value="graalvm-ce-23" />
<option name="MAIN_CLASS_NAME" value="org.jcnc.snow.compiler.cli.SnowCompiler" />
<module name="Snow" />
<option name="PROGRAM_PARAMETERS" value="-d playground/Demo6" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="org.jcnc.snow.compiler.parser.preprocessor.lexer.impl.api.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

17
.run/Demo7.run.xml Normal file
View File

@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Demo7" type="Application" factoryName="Application" folderName="Demo" activateToolWindowBeforeRun="false">
<option name="ALTERNATIVE_JRE_PATH" value="graalvm-ce-23" />
<option name="MAIN_CLASS_NAME" value="org.jcnc.snow.compiler.cli.SnowCompiler" />
<module name="Snow" />
<option name="PROGRAM_PARAMETERS" value="-d playground/Demo7" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="org.jcnc.snow.compiler.parser.preprocessor.lexer.impl.api.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View File

@ -11,8 +11,8 @@
<a href="https://gitee.com/jcnc-org/snow/blob/main/LICENSE">
<img src="https://img.shields.io/badge/%20license-Apache--2.0%20-blue" alt="">
</a>
<a href="https://gitee.com/jcnc-org/snow/tree/v0.2.0/">
<img src="https://img.shields.io/badge/version-v0.2.0-blue" alt="">
<a href="https://gitee.com/jcnc-org/snow/tree/v0.3.0/">
<img src="https://img.shields.io/badge/version-v0.3.0-blue" alt="">
</a>
</p>

View File

@ -0,0 +1,10 @@
module: Main
function: main
parameter:
return_type: int
body:
declare b1 :int = -1
return b1
end body
end function
end module

View File

@ -0,0 +1,10 @@
module: Main
function: main
parameter:
return_type: boolean
body:
declare b1 :boolean = true
return !b1
end body
end function
end module

View File

@ -2,24 +2,47 @@ package org.jcnc.snow.compiler.backend.generator;
import org.jcnc.snow.compiler.backend.builder.VMProgramBuilder;
import org.jcnc.snow.compiler.backend.core.InstructionGenerator;
import org.jcnc.snow.compiler.backend.util.IROpCodeMapper;
import org.jcnc.snow.compiler.backend.util.OpHelper;
import org.jcnc.snow.compiler.ir.core.IRValue;
import org.jcnc.snow.compiler.ir.instruction.BinaryOperationInstruction;
import org.jcnc.snow.compiler.ir.value.IRConstant;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 二元运算指令生成器
* 支持二元运算指令的自动类型提升
* <p>类型提升优先级为D > F > L > I > S > B</p>
* 二元运算指令生成器
* <p>
* 负责将中间表示的二元运算指令算术位运算及比较运算生成对应的虚拟机指令序列
* 并自动进行类型提升
* 同时实现 "+0 → MOV" Peephole 优化避免多余的 PUSH/ADD 序列
* </p>
* <p>类型提升优先级D &gt; F &gt; L &gt; I &gt; S &gt; B</p>
*/
public class BinaryOpGenerator implements InstructionGenerator<BinaryOperationInstruction> {
/* ---------- 类型优先级工具 ---------- */
/* -------------------- 常量与工具 -------------------- */
/**
* 返回类型前缀的优先级数值数值越大类型越宽
* D: 6, F: 5, L: 4, I: 3, S: 2, B: 1
* 用于生成唯一标签的计数器
*/
private static final AtomicInteger COUNTER = new AtomicInteger(0);
/**
* 生成一个新的唯一标签
*
* @param fn 当前函数名用于标签前缀
* @param tag 标签用途标识
* @return 形如 fn$tag$序号 的唯一标签
*/
private static String fresh(String fn, String tag) {
return fn + "$" + tag + "$" + COUNTER.getAndIncrement();
}
/**
* 类型优先级D &gt; F &gt; L &gt; I &gt; S &gt; B
*/
private static int rank(char p) {
return switch (p) {
@ -34,30 +57,42 @@ public class BinaryOpGenerator implements InstructionGenerator<BinaryOperationIn
}
/**
* 返回a和b中优先级更高的类型前缀即类型提升结果
* 返回优先级更高的类型字符
*/
private static char promote(char a, char b) {
return rank(a) >= rank(b) ? a : b;
}
/**
* 类型前缀转字符串方便拼接
* 单字符转字符串
*/
private static String str(char p) {
return String.valueOf(p);
}
/* ---------- 类型转换指令工具 ---------- */
/**
* 判断常量值是否等于 0
* 仅支持 Java 原生数值类型
*/
private static boolean isZero(Object v) {
if (v == null) return false;
return switch (v) {
case Integer i -> i == 0;
case Long l -> l == 0L;
case Short s -> s == (short) 0;
case Byte b -> b == (byte) 0;
case Float f -> f == 0.0f;
case Double d -> d == 0.0;
default -> false;
};
}
/**
* 根据源类型和目标类型前缀返回相应的类型转换指令助记符
*
* @param from 源类型前缀
* @param to 目标类型前缀
* @return 转换指令字符串 "I2L" "F2D"若无需转换则返回null
* 获取从类型 {@code from} {@code to} 的转换指令名
* 相同类型或无显式转换需求返回 {@code null}
*/
private static String convert(char from, char to) {
if (from == to) return null; // 类型一致无需转换
if (from == to) return null;
return switch ("" + from + to) {
case "IL" -> "I2L";
case "ID" -> "I2D";
@ -73,61 +108,104 @@ public class BinaryOpGenerator implements InstructionGenerator<BinaryOperationIn
case "DF" -> "D2F";
case "SI" -> "S2I";
case "BI" -> "B2I";
default -> null; // 其它组合暂未用到
default -> null;
};
}
/**
* 返回本生成器支持的指令类型 BinaryOperationInstruction
*/
/* -------------------- 接口实现 -------------------- */
@Override
public Class<BinaryOperationInstruction> supportedClass() {
return BinaryOperationInstruction.class;
}
/**
* 生成二元运算的虚拟机指令实现自动类型提升和必要的类型转换
*
* @param ins 当前二元运算IR指令
* @param out 虚拟机程序构建器
* @param slotMap IR虚拟寄存器到实际槽位编号的映射
* @param currentFn 当前函数名
*/
@Override
public void generate(BinaryOperationInstruction ins,
VMProgramBuilder out,
Map<IRVirtualRegister, Integer> slotMap,
String currentFn) {
/* ------- 1. 获取左右操作数的槽位编号和类型 ------- */
int lSlot = slotMap.get((IRVirtualRegister) ins.operands().get(0)); // 左操作数槽位
int rSlot = slotMap.get((IRVirtualRegister) ins.operands().get(1)); // 右操作数槽位
int dSlot = slotMap.get(ins.dest()); // 目标槽位
char lType = out.getSlotType(lSlot); // 左操作数类型前缀
char rType = out.getSlotType(rSlot); // 右操作数类型前缀
/* ---------- 0. +0 → MOV Peephole 优化 ---------- */
String irName = ins.op().name();
if (irName.startsWith("ADD_")) {
IRValue lhs = ins.operands().getFirst();
IRValue rhs = ins.operands().get(1);
// 类型提升确定本次二元运算的目标类型优先级较高的那一个
char tType = promote(lType, rType);
String tPref = str(tType); // 用于拼接指令字符串
boolean lhsZero = lhs instanceof IRConstant && isZero(((IRConstant) lhs).value());
boolean rhsZero = rhs instanceof IRConstant && isZero(((IRConstant) rhs).value());
/* ------- 2. 加载左操作数,并自动进行类型转换(如有必要) ------- */
out.emit(OpHelper.opcode(str(lType) + "_LOAD") + " " + lSlot); // LOAD指令
String cvt = convert(lType, tType); // 如需类型提升
if (cvt != null) out.emit(OpHelper.opcode(cvt)); // 插入类型转换指令
// 仅当一侧为常量 0 时可替换为 MOV
if (lhsZero ^ rhsZero) {
IRVirtualRegister srcVr = null;
if ((lhsZero ? rhs : lhs) instanceof IRVirtualRegister) {
srcVr = (IRVirtualRegister) (lhsZero ? rhs : lhs);
}
int srcSlot = slotMap.get(srcVr);
int destSlot = slotMap.get(ins.dest());
/* ------- 3. 加载右操作数,并自动进行类型转换(如有必要) ------- */
out.emit(OpHelper.opcode(str(rType) + "_LOAD") + " " + rSlot); // LOAD指令
cvt = convert(rType, tType); // 如需类型提升
if (cvt != null) out.emit(OpHelper.opcode(cvt)); // 插入类型转换指令
// 源与目标槽位不同才需要发 MOV
if (srcSlot != destSlot) {
out.emit(OpHelper.opcode("MOV") + " " + srcSlot + " " + destSlot);
}
// 复制槽位类型信息
out.setSlotType(destSlot, out.getSlotType(srcSlot));
return; // 优化路径结束
}
}
/* ------- 4. 生成具体的二元运算指令 ------- */
// 获取IR指令中的操作名如ADDSUBMUL等去掉结尾的"_"后缀
String opName = ins.op().name().split("_")[0];
// 例如生成 "I_ADD", "D_MUL" 等虚拟机指令
out.emit(OpHelper.opcode(tPref + "_" + opName));
/* ---------- 1. 槽位与类型 ---------- */
int lSlot = slotMap.get((IRVirtualRegister) ins.operands().get(0));
int rSlot = slotMap.get((IRVirtualRegister) ins.operands().get(1));
int dSlot = slotMap.get(ins.dest());
/* ------- 5. 结果存入目标槽位,并更新槽位类型 ------- */
out.emit(OpHelper.opcode(tPref + "_STORE") + " " + dSlot);
out.setSlotType(dSlot, tType); // 记录运算结果的类型前缀便于后续指令正确处理
char lType = out.getSlotType(lSlot); // 未登记默认 'I'
char rType = out.getSlotType(rSlot);
char tType = promote(lType, rType); // 类型提升结果
String tPre = str(tType);
/* ---------- 2. 加载并做类型转换 ---------- */
out.emit(OpHelper.opcode(str(lType) + "_LOAD") + " " + lSlot);
String cvt = convert(lType, tType);
if (cvt != null) out.emit(OpHelper.opcode(cvt));
out.emit(OpHelper.opcode(str(rType) + "_LOAD") + " " + rSlot);
cvt = convert(rType, tType);
if (cvt != null) out.emit(OpHelper.opcode(cvt));
/* ---------- 3. 区分算术 / 比较 ---------- */
boolean isCmp = irName.startsWith("CMP_");
/* === 3-A. 普通算术 / 位运算 === */
if (!isCmp) {
String opCore = irName.split("_")[0]; // ADD / SUB / MUL
out.emit(OpHelper.opcode(tPre + "_" + opCore));
out.emit(OpHelper.opcode(tPre + "_STORE") + " " + dSlot);
out.setSlotType(dSlot, tType);
return;
}
/* === 3-B. CMP_* —— 生成布尔结果 === */
String branchOp = OpHelper.opcode(IROpCodeMapper.toVMOp(ins.op())); // IC_E / IC_NE
String lblTrue = fresh(currentFn, "true");
String lblEnd = fresh(currentFn, "end");
// 条件跳转成立 lblTrue
out.emitBranch(branchOp, lblTrue);
// 不成立 0
out.emit(OpHelper.opcode("I_PUSH") + " 0");
out.emitBranch(OpHelper.opcode("JUMP"), lblEnd);
// 成立分支 1
out.emit(lblTrue + ":");
out.emit(OpHelper.opcode("I_PUSH") + " 1");
// 结束标签
out.emit(lblEnd + ":");
// 写入目标槽位
out.emit(OpHelper.opcode("I_STORE") + " " + dSlot);
out.setSlotType(dSlot, 'I'); // 布尔 int
}
}
}

View File

@ -41,13 +41,29 @@ public class UnaryOpGenerator implements InstructionGenerator<UnaryOperationInst
VMProgramBuilder out,
Map<IRVirtualRegister, Integer> slotMap,
String currentFn) {
// 获取操作数所在槽号
int slotId = slotMap.get((IRVirtualRegister) ins.operands().getFirst());
// 加载操作数到虚拟机栈顶
out.emit(OpHelper.opcode("I_LOAD") + " " + slotId);
// 生成对应的一元运算操作码如取负等
out.emit(OpHelper.opcode(IROpCodeMapper.toVMOp(ins.op())));
// 将结果存储到目标寄存器槽
out.emit(OpHelper.opcode("I_STORE") + " " + slotMap.get(ins.dest()));
/* -------- 1. 源槽位与类型 -------- */
int srcSlot = slotMap.get((IRVirtualRegister) ins.operands().getFirst());
char prefix = out.getSlotType(srcSlot); // 未登记则返回默认 'I'
String loadOp = prefix + "_LOAD";
String storeOp = prefix + "_STORE";
/* -------- 2. 指令序列 -------- */
// 2-A. 加载操作数
out.emit(OpHelper.opcode(loadOp) +
" " + srcSlot);
// 2-B. 执行具体一元运算NEGNOT
out.emit(OpHelper.opcode(
IROpCodeMapper.toVMOp(ins.op())));
// 2-C. 存结果到目标槽
int destSlot = slotMap.get(ins.dest());
out.emit(OpHelper.opcode(storeOp) +
" " + destSlot);
/* -------- 3. 更新目标槽类型 -------- */
out.setSlotType(destSlot, prefix);
}
}

View File

@ -3,6 +3,7 @@ package org.jcnc.snow.compiler.ir.builder;
import org.jcnc.snow.compiler.ir.core.IROpCode;
import org.jcnc.snow.compiler.ir.instruction.CallInstruction;
import org.jcnc.snow.compiler.ir.instruction.LoadConstInstruction;
import org.jcnc.snow.compiler.ir.instruction.UnaryOperationInstruction;
import org.jcnc.snow.compiler.ir.value.IRConstant;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
import org.jcnc.snow.compiler.ir.utils.ExpressionUtils;
@ -32,8 +33,15 @@ public record ExpressionBuilder(IRContext ctx) {
* <p>会根据节点的实际类型分别处理
* <ul>
* <li>数字字面量新建常量寄存器</li>
* <li>布尔字面量生成值为 0 1 的常量寄存器</li>
* <li>标识符查找当前作用域中的寄存器</li>
* <li>二元表达式递归处理子表达式并进行相应运算</li>
* <li>一元运算符
* <ul>
* <li><code>-x</code>取负生成 <code>NEG_I32</code> 指令</li>
* <li>code>!x</code>逻辑非转换为 <code>x == 0</code> 比较指令</li>
* </ul>
* </li>
* <li>函数调用生成对应的Call指令</li>
* <li>其它类型不支持抛出异常</li>
* </ul>
@ -42,6 +50,7 @@ public record ExpressionBuilder(IRContext ctx) {
* @return 该表达式的计算结果寄存器
* @throws IllegalStateException 如果遇到未定义的标识符或不支持的表达式类型
*/
public IRVirtualRegister build(ExpressionNode expr) {
return switch (expr) {
// 数字字面量
@ -59,11 +68,33 @@ public record ExpressionBuilder(IRContext ctx) {
case BinaryExpressionNode bin -> buildBinary(bin);
// 函数调用
case CallExpressionNode call -> buildCall(call);
case UnaryExpressionNode u -> buildUnary(u);
default -> throw new IllegalStateException(
"不支持的表达式类型: " + expr.getClass().getSimpleName());
};
}
/** 处理一元表达式 */
private IRVirtualRegister buildUnary(UnaryExpressionNode un) {
String op = un.operator();
IRVirtualRegister val = build(un.operand());
// -x NEG_*根据类型自动选择位宽
if (op.equals("-")) {
IRVirtualRegister dest = ctx.newRegister();
IROpCode code = ExpressionUtils.negOp(un.operand());
ctx.addInstruction(new UnaryOperationInstruction(code, dest, val));
return dest;
}
// !x (x == 0)
if (op.equals("!")) {
IRVirtualRegister zero = InstructionFactory.loadConst(ctx, 0);
return InstructionFactory.binOp(ctx, IROpCode.CMP_EQ, val, zero);
}
throw new IllegalStateException("未知一元运算符: " + op);
}
/**
* 直接将表达式计算结果写入指定的目标寄存器dest
@ -154,7 +185,7 @@ public record ExpressionBuilder(IRContext ctx) {
.toList();
// 获取完整调用目标名称支持成员/模块调用和普通调用
String fullName = switch (call.callee()) {
case MemberExpressionNode member when member.object() instanceof IdentifierNode mod ->
case MemberExpressionNode member when member.object() instanceof IdentifierNode _ ->
((IdentifierNode)member.object()).name() + "." + member.member();
case IdentifierNode id -> id.name();
default -> throw new IllegalStateException("不支持的调用目标: " + call.callee().getClass().getSimpleName());

View File

@ -19,7 +19,7 @@ import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
*/
public class IRContext {
/* ➡ 新增:生成唯一标签用 */
/* 生成唯一标签用 */
private int labelCounter = 0;
/**
* 当前正在构建的 IRFunction 对象所有指令将添加至此

View File

@ -78,7 +78,7 @@ public class InstructionFactory {
}
/**
* 简易 Move 指令src dest若寄存器相同也安全
* Move 指令src dest若寄存器相同也安全
* <p>
* 实现方式dest = src + 0即加上常量 0
* </p>
@ -88,7 +88,11 @@ public class InstructionFactory {
* @param dest 目标寄存器
*/
public static void move(IRContext ctx, IRVirtualRegister src, IRVirtualRegister dest) {
/* 采用 “dest = src + 0” 的最简实现 */
// 自赋值无需任何操作避免生成多余的常量 0 寄存器
if (src == dest) {
return;
}
// 回退实现dest = src + 0
IRVirtualRegister zero = loadConst(ctx, 0);
ctx.addInstruction(new BinaryOperationInstruction(IROpCode.ADD_I32, dest, src, zero));
}

View File

@ -8,6 +8,8 @@ import org.jcnc.snow.compiler.parser.ast.*;
import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
import org.jcnc.snow.compiler.parser.ast.base.StatementNode;
import java.util.Locale;
/**
* StatementBuilder AST 语句节点 ({@link StatementNode}) 转换为 IR 指令序列的构建器
* <p>
@ -177,4 +179,16 @@ public class StatementBuilder {
InstructionFactory.cmpJump(ctx, IROpCode.CMP_EQ, condReg, zero, falseLabel);
}
}
private static char typeSuffixFromType(String type) {
if (type == null) return '\0';
return switch (type.toLowerCase(Locale.ROOT)) {
case "byte" -> 'b';
case "short" -> 's';
case "long" -> 'l';
case "float" -> 'f';
case "double" -> 'd';
default -> '\0'; // 其余默认按 32-bit 整型处理
};
}
}

View File

@ -78,6 +78,30 @@ public class ExpressionUtils {
};
}
/**
* 根据表达式节点推断一元取负-运算应使用的操作码
*
* <p>优先级与 {@link #resolveOpCode} 使用的类型提升规则保持一致</p>
* <ul>
* <li>字面量或标识符带显式后缀时直接以后缀决定位宽</li>
* <li>未显式指定时默认使用 32 位整型 {@link IROpCode#NEG_I32}</li>
* </ul>
*
* @param operand 一元取负运算的操作数
* @return 匹配的 {@link IROpCode}
*/
public static IROpCode negOp(ExpressionNode operand) {
char t = typeChar(operand);
return switch (t) {
case 'b' -> IROpCode.NEG_B8;
case 's' -> IROpCode.NEG_S16;
case 'l' -> IROpCode.NEG_L64;
case 'f' -> IROpCode.NEG_F32;
case 'd' -> IROpCode.NEG_D64;
default -> IROpCode.NEG_I32;
};
}
/* =================== 类型推断与操作符匹配 =================== */
/**

View File

@ -5,26 +5,29 @@ import org.jcnc.snow.compiler.lexer.token.Token;
import org.jcnc.snow.compiler.lexer.token.TokenType;
/**
* 运算符扫描器识别逻辑与比较运算符包括单字符和双字符组合
* <p>
* 支持的运算符包括
* 运算符扫描器OperatorTokenScanner
*
* <p>负责在词法分析阶段识别由 <b>= ! &lt; &gt; | &amp; %</b> 等字符
* 起始的单字符或双字符运算符并生成相应 {@link Token}</p>
*
* <ul>
* <li>赋值与比较===!=</li>
* <li>关系运算符&gt;&gt;=&lt;&lt;=</li>
* <li>逻辑运算符&&||</li>
* <li>赋值 / 比较{@code =}, {@code ==}, {@code !=}</li>
* <li>关系运算{@code >}, {@code >=}, {@code <}, {@code <=}</li>
* <li>逻辑运算{@code &&}, {@code ||}</li>
* <li>取模运算{@code %}</li>
* <li>逻辑非{@code !}</li>
* </ul>
* <p>
* 不符合上述组合的字符会返回 {@code UNKNOWN} 类型的 Token
*
* <p>如果无法匹配到合法组合将返回 {@link TokenType#UNKNOWN}</p>
*/
public class OperatorTokenScanner extends AbstractTokenScanner {
/**
* 判断是否可以处理当前位置的字符
* <p>运算符扫描器关注的起始字符包括=!&lt;&gt;|&amp;</p>
* 判断当前字符是否可能是运算符的起始字符
*
* @param c 当前字符
* @param ctx 当前词法上下文
* @return 如果是潜在的运算符起始字符则返回 true
* @param ctx 词法上下文
* @return 若是关注的起始字符则返回 {@code true}
*/
@Override
public boolean canHandle(char c, LexerContext ctx) {
@ -32,14 +35,12 @@ public class OperatorTokenScanner extends AbstractTokenScanner {
}
/**
* 扫描并识别运算符 Token
* <p>支持组合运算符判断 ==!=&gt;=
* 若无法匹配组合形式则退回单字符形式</p>
* 按最长匹配优先原则扫描并生成运算符 token
*
* @param ctx 词法上下文
* @param ctx 词法上下文
* @param line 当前行号
* @param col 当前列号
* @return 对应的运算符 Token无法识别的运算符返回 {@code UNKNOWN}
* @param col 当前列号
* @return 已识别的 {@link Token}
*/
@Override
protected Token scanToken(LexerContext ctx, int line, int col) {
@ -51,64 +52,71 @@ public class OperatorTokenScanner extends AbstractTokenScanner {
case '=':
if (ctx.match('=')) {
lexeme = "==";
type = TokenType.DOUBLE_EQUALS;
type = TokenType.DOUBLE_EQUALS;
} else {
lexeme = "=";
type = TokenType.EQUALS;
type = TokenType.EQUALS;
}
break;
case '!':
if (ctx.match('=')) {
lexeme = "!=";
type = TokenType.NOT_EQUALS;
type = TokenType.NOT_EQUALS;
} else {
lexeme = "!";
type = TokenType.UNKNOWN;
type = TokenType.NOT;
}
break;
case '>':
if (ctx.match('=')) {
lexeme = ">=";
type = TokenType.GREATER_EQUAL;
type = TokenType.GREATER_EQUAL;
} else {
lexeme = ">";
type = TokenType.GREATER_THAN;
type = TokenType.GREATER_THAN;
}
break;
case '<':
if (ctx.match('=')) {
lexeme = "<=";
type = TokenType.LESS_EQUAL;
type = TokenType.LESS_EQUAL;
} else {
lexeme = "<";
type = TokenType.LESS_THAN;
type = TokenType.LESS_THAN;
}
break;
case '%':
lexeme = "%";
type = TokenType.MODULO;
type = TokenType.MODULO;
break;
case '&':
if (ctx.match('&')) {
lexeme = "&&";
type = TokenType.AND;
type = TokenType.AND;
} else {
lexeme = "&";
type = TokenType.UNKNOWN;
type = TokenType.UNKNOWN;
}
break;
case '|':
if (ctx.match('|')) {
lexeme = "||";
type = TokenType.OR;
type = TokenType.OR;
} else {
lexeme = "|";
type = TokenType.UNKNOWN;
type = TokenType.UNKNOWN;
}
break;
default:
lexeme = String.valueOf(c);
type = TokenType.UNKNOWN;
type = TokenType.UNKNOWN;
}
return new Token(type, lexeme, line, col);

View File

@ -19,9 +19,6 @@ import java.util.Set;
* <li>对不合法的词素自动标记为 UNKNOWN 类型</li>
* </ul>
* </p>
*
* @author 你的名字
* @version 1.0
*/
public class TokenFactory {

View File

@ -10,6 +10,7 @@ package org.jcnc.snow.compiler.lexer.token;
*/
public enum TokenType {
/* ---------- 基础 ---------- */
/** 普通标识符,如变量名、函数名等 */
IDENTIFIER,
@ -19,14 +20,17 @@ public enum TokenType {
/** 内置类型名称(如 int、string、bool 等) */
TYPE,
/* ---------- 字面量 ---------- */
/** 布尔字面量 true / false */
BOOL_LITERAL,
/** 字符串字面量(如 "hello" */
STRING_LITERAL,
/** 数字字面量(整数或浮点数) */
NUMBER_LITERAL,
/* ---------- 分隔符 ---------- */
/** 冒号 ':' */
COLON,
@ -36,6 +40,7 @@ public enum TokenType {
/** 点号 '.' */
DOT,
/* ---------- 运算符 ---------- */
/** 赋值符号 '=' */
EQUALS,
@ -53,6 +58,9 @@ public enum TokenType {
/** 减号 '-' */
MINUS,
/** 取反 '!' */
NOT,
/** 左括号 '(' */
LPAREN,

View File

@ -5,15 +5,13 @@ import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
/**
* 表示布尔字面量boolean literal的抽象语法树AST节点
* <p>
* 本类实现了 {@link ExpressionNode} 接口用于在编译器前端构建语法分析过程中
* 该纪录类实现 {@link ExpressionNode} 接口用于在编译器前端构建语法分析过程中
* 表达布尔类型的字面量常量 "true" "false"
* </p>
*
* @param value 字面量的布尔值
*/
public class BoolLiteralNode implements ExpressionNode {
/**
* 字面量的布尔值
*/
private final boolean value;
public record BoolLiteralNode(boolean value) implements ExpressionNode {
/**
* 使用布尔字面量字符串构造一个 {@code BoolLiteralNode} 实例
@ -25,7 +23,7 @@ public class BoolLiteralNode implements ExpressionNode {
* @param lexeme 布尔字面量的字符串表示
*/
public BoolLiteralNode(String lexeme) {
this.value = Boolean.parseBoolean(lexeme);
this(Boolean.parseBoolean(lexeme));
}
/**

View File

@ -0,0 +1,31 @@
package org.jcnc.snow.compiler.parser.ast;
import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
/**
* {@code UnaryExpressionNode} 前缀一元运算 AST 节点
*
* <p>代表两种受支持的一元前缀表达式
* <ul>
* <li><b>取负</b>{@code -x}</li>
* <li><b>逻辑非</b>{@code !x}</li>
* </ul>
*
* {@link #equals(Object)}{@link #hashCode()} 等方法</p>
*
* @param operator 一元运算符 "-" "!"
* @param operand 运算对象 / 右操作数
*/
public record UnaryExpressionNode(String operator,
ExpressionNode operand) implements ExpressionNode {
/**
* 生成调试友好的字符串表示例如 {@code "-x"} {@code "!flag"}
*
* @return 一元表达式的串表示
*/
@Override
public String toString() {
return operator + operand;
}
}

View File

@ -45,6 +45,10 @@ public class PrattExpressionParser implements ExpressionParser {
prefixes.put(TokenType.STRING_LITERAL.name(), new StringLiteralParselet());
prefixes.put(TokenType.BOOL_LITERAL.name(), new BoolLiteralParselet());
// 注册一元前缀运算
prefixes.put(TokenType.MINUS.name(), new UnaryOperatorParselet());
prefixes.put(TokenType.NOT.name(), new UnaryOperatorParselet());
// 注册中缀解析器
infixes.put("+", new BinaryOperatorParselet(Precedence.SUM, true));
infixes.put("-", new BinaryOperatorParselet(Precedence.SUM, true));

View File

@ -24,6 +24,9 @@ public enum Precedence {
*/
PRODUCT,
/** 一元前缀(-x !x */
UNARY,
/**
* 函数调用成员访问等最强绑定例如 foo()obj.prop
*/

View File

@ -0,0 +1,55 @@
package org.jcnc.snow.compiler.parser.expression;
import org.jcnc.snow.compiler.lexer.token.Token;
import org.jcnc.snow.compiler.parser.ast.UnaryExpressionNode;
import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
import org.jcnc.snow.compiler.parser.context.ParserContext;
import org.jcnc.snow.compiler.parser.expression.base.PrefixParselet;
/**
* {@code UnaryOperatorParselet} 前缀一元运算符的 Pratt 解析器
*
* <p>当前 parselet 负责解析两种前缀运算
* <ul>
* <li><b>取负</b>{@code -x}</li>
* <li><b>逻辑非</b>{@code !x}</li>
* </ul>
*
* 解析过程:
*
* <ol>
* <li> parselet 在外层解析器已消费运算符 {@code token} 后被调用</li>
* <li> {@link Precedence#UNARY} 作为 <em>绑定强度</em> 递归解析右侧子表达式
* 保证任何更高优先级的表达式括号字面量等优先归属右侧</li>
* <li>最终生成 {@link UnaryExpressionNode} AST 节点记录运算符与操作数</li>
* </ol>
*
* <p>此类仅负责<strong>语法结构</strong>的构建
* <ul>
* <li>类型正确性在 {@code UnaryExpressionAnalyzer} 中校验</li>
* <li>IR 生成在 {@code ExpressionBuilder.buildUnary} 中完成</li>
* </ul>
*/
public class UnaryOperatorParselet implements PrefixParselet {
/**
* 解析前缀一元表达式
*
* @param ctx 当前解析上下文
* @param token 已被消费的运算符 Token字面值应为 {@code "-" "!"}
* @return 构建出的 {@link UnaryExpressionNode}
*/
@Override
public ExpressionNode parse(ParserContext ctx, Token token) {
/* ------------------------------------------------------------
* 1. UNARY 优先级递归解析操作数避免错误结合顺序
* ------------------------------------------------------------ */
ExpressionNode operand =
new PrattExpressionParser().parseExpression(ctx, Precedence.UNARY);
/* ------------------------------------------------------------
* 2. 封装成 AST 节点并返回
* ------------------------------------------------------------ */
return new UnaryExpressionNode(token.getLexeme(), operand);
}
}

View File

@ -140,7 +140,6 @@ public class FunctionParser implements TopLevelParser {
private void parseFunctionFooter(TokenStream ts) {
ts.expect("end");
ts.expect("function");
ts.expectType(TokenType.NEWLINE);
}
/**

View File

@ -13,7 +13,7 @@ import java.util.*;
* 并可借助 {@code JSONParser.toJson(Object)} 方法将其序列化为 JSON 字符串用于调试
* 可视化或跨语言数据传输
* <p>
* 支持的节点类型包括
* 支持的节点类型包括新增对 {@code BoolLiteralNode}{@code UnaryExpressionNode} 的完整支持
* <ul>
* <li>{@link ModuleNode}</li>
* <li>{@link FunctionNode}</li>
@ -23,7 +23,9 @@ import java.util.*;
* <li>{@link LoopNode}</li>
* <li>{@link ReturnNode}</li>
* <li>{@link ExpressionStatementNode}</li>
* <li>各类 {@link ExpressionNode} 子类型 {@code BinaryExpressionNode}, {@code IdentifierNode} </li>
* <li>{@link BoolLiteralNode}</li>
* <li>{@link UnaryExpressionNode}</li>
* <li>以及各类 {@link ExpressionNode} 子类型 {@code BinaryExpressionNode}, {@code IdentifierNode} </li>
* </ul>
*/
public class ASTJsonSerializer {
@ -174,19 +176,32 @@ public class ASTJsonSerializer {
*/
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 UnaryExpressionNode(String operator, ExpressionNode operand) -> exprMap("UnaryExpression",
"operator", operator,
"operand", exprToMap(operand)
);
// 布尔字面量
case BoolLiteralNode(boolean value) -> exprMap("BoolLiteral", "value", value);
// 标识符
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<ExpressionNode> arguments, int line, int column, String file) -> {
// 调用表达式
case CallExpressionNode(ExpressionNode callee, List<ExpressionNode> arguments, _, _, _) -> {
List<Object> 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
@ -195,4 +210,4 @@ public class ASTJsonSerializer {
default -> Map.of("type", expr.getClass().getSimpleName());
};
}
}
}

View File

@ -67,9 +67,9 @@ public class BinaryExpressionAnalyzer implements ExpressionAnalyzer<BinaryExpres
Type wide = Type.widen(left, right);
if (wide == null) wide = BuiltinType.INT; // 容错降级为 int
// 若为比较运算符统一返回 int 类型作为布尔值表示
// 若为比较运算符统一返回 boolean 类型作为布尔值表示
if ("< <= > >= == !=".contains(op)) {
return BuiltinType.INT;
return BuiltinType.BOOLEAN;
}
return wide;

View File

@ -0,0 +1,107 @@
package org.jcnc.snow.compiler.semantic.analyzers.expression;
import org.jcnc.snow.compiler.parser.ast.FunctionNode;
import org.jcnc.snow.compiler.parser.ast.UnaryExpressionNode;
import org.jcnc.snow.compiler.semantic.analyzers.base.ExpressionAnalyzer;
import org.jcnc.snow.compiler.semantic.core.Context;
import org.jcnc.snow.compiler.semantic.core.ModuleInfo;
import org.jcnc.snow.compiler.semantic.error.SemanticError;
import org.jcnc.snow.compiler.semantic.symbol.SymbolTable;
import org.jcnc.snow.compiler.semantic.type.BuiltinType;
import org.jcnc.snow.compiler.semantic.type.Type;
/**
* {@code UnaryExpressionAnalyzer} 一元表达式的语义分析器
*
* <p>目前实现两种一元运算
* <ul>
* <li>{@code -x} 取负仅允许作用于数值类型int / float </li>
* <li>{@code !x} 逻辑非仅允许作用于 {@code boolean} 类型</li>
* </ul>
*
* <p>分析流程
* <ol>
* <li>递归分析操作数表达式获取其类型 {@code operandType}</li>
* <li>根据运算符检查类型合法性
* <ul>
* <li>若类型不符记录 {@link SemanticError} 并返回一个占位类型
* 取负返回 {@link BuiltinType#INT}逻辑非返回
* {@link BuiltinType#BOOLEAN}</li>
* <li>若合法则返回运算后的结果类型
* 取负为 {@code operandType}逻辑非为 {@link BuiltinType#BOOLEAN}</li>
* </ul>
* </li>
* </ol>
*
* <p>若遇到未支持的运算符将生成错误并返回 {@code int} 作为占位类型</p>
*
*/
public class UnaryExpressionAnalyzer implements ExpressionAnalyzer<UnaryExpressionNode> {
/**
* 对一元表达式进行语义分析
*
* @param ctx 全局编译上下文持有错误列表注册表等
* @param mi 当前模块信息
* @param fn 所在函数节点可为 {@code null} 表示顶层
* @param locals 当前作用域符号表
* @param expr 要分析的一元表达式节点
* @return 表达式的结果类型若有错误返回占位类型并在 {@code ctx.getErrors()}
* 中记录 {@link SemanticError}
*/
@Override
public Type analyze(Context ctx,
ModuleInfo mi,
FunctionNode fn,
SymbolTable locals,
UnaryExpressionNode expr) {
/* ------------------------------------------------------------------
* 1. 先递归分析操作数确定其类型
* ------------------------------------------------------------------ */
Type operandType = ctx.getRegistry()
.getExpressionAnalyzer(expr.operand())
.analyze(ctx, mi, fn, locals, expr.operand());
/* ------------------------------------------------------------------
* 2. 根据运算符校验类型并给出结果类型
* ------------------------------------------------------------------ */
switch (expr.operator()) {
/* -------------- 取负运算 -------------- */
case "-" -> {
if (!operandType.isNumeric()) {
ctx.getErrors().add(new SemanticError(
expr,
"'-' 只能应用于数值类型,当前为 " + operandType
));
// 返回占位类型避免后续阶段 NPE
return BuiltinType.INT;
}
// 合法结果类型与操作数相同
return operandType;
}
/* -------------- 逻辑非运算 -------------- */
case "!" -> {
if (operandType != BuiltinType.BOOLEAN) {
ctx.getErrors().add(new SemanticError(
expr,
"'!' 只能应用于 boolean 类型,当前为 " + operandType
));
return BuiltinType.BOOLEAN;
}
// 合法结果类型恒为 boolean
return BuiltinType.BOOLEAN;
}
/* -------------- 未知运算符 -------------- */
default -> {
ctx.getErrors().add(new SemanticError(
expr,
"未知一元运算符: " + expr.operator()
));
return BuiltinType.INT;
}
}
}
}

View File

@ -55,7 +55,10 @@ public final class AnalyzerRegistrar {
registry.registerExpressionAnalyzer(CallExpressionNode.class, new CallExpressionAnalyzer());
registry.registerExpressionAnalyzer(BinaryExpressionNode.class, new BinaryExpressionAnalyzer());
// 对尚未实现的表达式类型使用兜底处理器 MemberExpression
// ---------- 注册一元表达式分析器 ----------
registry.registerExpressionAnalyzer(UnaryExpressionNode.class,new UnaryExpressionAnalyzer());
// 对尚未实现的表达式类型使用兜底处理器
registry.registerExpressionAnalyzer(MemberExpressionNode.class,
new UnsupportedExpressionAnalyzer<>());
}

View File

@ -48,8 +48,8 @@ public class LXorCommand implements Command {
}
// Pop the top two operands from the stack
final int b = (int) operandStack.pop();
final int a = (int) operandStack.pop();
final long b = (long) operandStack.pop();
final long a = (long) operandStack.pop();
// Perform the long64 bitwise XOR operation and push the result back onto the stack
operandStack.push(a ^ b);

View File

@ -20,7 +20,8 @@ import java.util.List;
* <li>{@link CommandExecutionHandler} dispatches opcodes</li>
* </ul>
*
* <h2>Root-frame contract</h2>
* Root-frame contract:
* <p>
* A <strong>root stack frame</strong> is pushed <em>once</em> via
* {@link #ensureRootFrame()} before the first instruction executes
* and is never popped. When a {@code RET} executed in the root frame