feat: 支持以 'this' 开头的表达式并优化赋值语句解析

- 允许 'this'作为表达式起始,支持 'this.xxx' 形式的赋值
- 优化赋值语句解析逻辑,支持更复杂的左值表达式
- 新增对 'this' 成员赋值的特殊处理,降级为普通变量赋值
- 改进错误处理,对不支持的赋值左值类型抛出异常
This commit is contained in:
Luke 2025-08-29 17:35:04 +08:00
parent 5b259a01d8
commit 2dd5e64c3a

View File

@ -14,17 +14,17 @@ import org.jcnc.snow.compiler.parser.expression.PrattExpressionParser;
/**
* {@code ExpressionStatementParser} 用于解析通用表达式语句赋值或普通表达式
* <p>
* 支持以下两种语法结构:
* 支持以下两种语法结构:
* <pre>{@code
* x = 1 + 2 // 赋值语句
* doSomething() // 一般表达式语句
* this.name = n // this.name 赋值语法糖为对 name 的赋值
* }</pre>
* <ul>
* <li>以标识符开头且后接 {@code =} 解析为 {@link AssignmentNode}</li>
* <li>否则视为普通表达式解析为 {@link ExpressionStatementNode}</li>
* <li>所有表达式语句必须以换行符{@code NEWLINE}结尾</li>
* </ul>
* 若语句起始为关键字或空行将直接抛出异常防止非法语法进入表达式解析流程
*
* - 以标识符开头且后接 '=' 解析为 {@link AssignmentNode}
* - 否则先解析为一般表达式若后续遇到 '='则回退为<expr> = <expr>赋值语句
* - 所有表达式语句必须以换行符NEWLINE结尾
* <p>
*/
public class ExpressionStatementParser implements StatementParser {
@ -33,49 +33,81 @@ public class ExpressionStatementParser implements StatementParser {
*
* @param ctx 当前解析上下文提供词法流与环境信息
* @return {@link AssignmentNode} {@link ExpressionStatementNode} 语法节点
* @throws UnexpectedToken 若遇到非法起始关键字空行
* @throws UnexpectedToken 若遇到非法起始关键字 'end'
*/
@Override
public StatementNode parse(ParserContext ctx) {
TokenStream ts = ctx.getTokens();
if (ts.peek().getType() == TokenType.NEWLINE || ts.peek().getType() == TokenType.KEYWORD) {
// ----------- 起始 token 合法性检查放宽以支持 this 开头-----------
if (ts.peek().getType() == TokenType.NEWLINE) {
// 空行不应进入表达式解析直接抛出异常
throw new UnexpectedToken(
"无法解析以关键字开头的表达式: " + ts.peek().getLexeme(),
"无法解析以空行开头的表达式",
ts.peek().getLine(),
ts.peek().getCol()
);
}
if (ts.peek().getType() == TokenType.KEYWORD) {
String kw = ts.peek().getLexeme();
// 仅允许 this 作为表达式起始其它关键字 end/if/else 仍禁止
if (!"this".equals(kw)) {
throw new UnexpectedToken(
"无法解析以关键字开头的表达式: " + kw,
ts.peek().getLine(),
ts.peek().getCol()
);
}
}
int line = ts.peek().getLine();
int column = ts.peek().getCol();
String file = ctx.getSourceName();
// 简单形式: IDENTIFIER = expr
// ------------- 简单形式: IDENTIFIER = expr -------------
// 快速路径 "a = ..."直接识别为赋值语句无需完整表达式树回退
if (ts.peek().getType() == TokenType.IDENTIFIER && "=".equals(ts.peek(1).getLexeme())) {
String varName = ts.next().getLexeme();
ts.expect("=");
ExpressionNode value = new PrattExpressionParser().parse(ctx);
String varName = ts.next().getLexeme(); // 消费 IDENTIFIER
ts.expect("="); // 消费 '='
ExpressionNode value = new PrattExpressionParser().parse(ctx); // 解析右侧表达式
ts.expectType(TokenType.NEWLINE);
// 返回简单变量赋值节点
return new AssignmentNode(varName, value, new NodeContext(line, column, file));
}
// 尝试解析更通用的左值形式支持下标: <expr> = <expr>
// ------------- 通用形式: <expr> [= <expr>] -------------
// 先解析潜在左值表达式或普通表达式
ExpressionNode lhs = new PrattExpressionParser().parse(ctx);
// 若遇到等号则尝试回退为赋值语句兼容更复杂的左值表达式
if ("=".equals(ts.peek().getLexeme())) {
ts.next(); // consume '='
ExpressionNode rhs = new PrattExpressionParser().parse(ctx);
ts.next(); // 消费 '='
ExpressionNode rhs = new PrattExpressionParser().parse(ctx); // 解析右值表达式
ts.expectType(TokenType.NEWLINE);
// 根据左值类型构造具体赋值节点
// 根据左值 AST 类型生成不同赋值节点
if (lhs instanceof org.jcnc.snow.compiler.parser.ast.IdentifierNode id) {
// 变量名赋值a = rhs
return new AssignmentNode(id.name(), rhs, new NodeContext(line, column, file));
} else if (lhs instanceof org.jcnc.snow.compiler.parser.ast.IndexExpressionNode idx) {
// 下标赋值a[i] = rhs
return new org.jcnc.snow.compiler.parser.ast.IndexAssignmentNode(idx, rhs, new NodeContext(line, column, file));
} else if (lhs instanceof org.jcnc.snow.compiler.parser.ast.MemberExpressionNode mem
&& mem.object() instanceof org.jcnc.snow.compiler.parser.ast.IdentifierNode oid
&& "this".equals(oid.name())) {
// 支持this.field = rhs
// 语法糖降级为对当前作用域同名变量的赋值相当于 "field = rhs"
return new AssignmentNode(mem.member(), rhs, new NodeContext(line, column, file));
} else {
// 其它成员赋值 a.b = ...不支持报错
throw new UnexpectedToken("不支持的赋值左值类型: " + lhs.getClass().getSimpleName(), line, column);
}
}
// 普通表达式语句
// 不是赋值则当作普通表达式语句处理
ts.expectType(TokenType.NEWLINE);
return new ExpressionStatementNode(lhs, new NodeContext(line, column, file));
}