feat: support bare name assignments in interactive and function scopes

This commit is contained in:
2026-10-20 18:16:54 +08:00
parent 6281e58647
commit df1b77fc1a
17 changed files with 259 additions and 37 deletions
+18
View File
@@ -114,6 +114,11 @@ pub fn analyze(root: &AstRoot, options: &CompileOptions) -> Result<SymbolTable,
globals.insert(binding.name.text.clone());
root_scope.intern(&binding.name.text);
}
StmtKind::Assignment(assignment) => {
root_scope.expression(&assignment.value, &globals)?;
globals.insert(assignment.name.text.clone());
root_scope.intern(&assignment.name.text);
}
StmtKind::Declaration(_) => {}
StmtKind::Expression(expression) => {
root_scope.expression(expression, &globals)?
@@ -164,6 +169,15 @@ fn function_scope(function: &Function, globals: &BTreeSet<String>) -> Result<Sco
declare(&mut locals, &binding.name.text, binding.name.span)?;
builder.scope.layout.locals.push(binding.name.text.clone());
}
StmtKind::Assignment(assignment) => {
if locals.insert(assignment.name.text.clone()) {
builder
.scope
.layout
.locals
.push(assignment.name.text.clone());
}
}
StmtKind::Declaration(_) => {
return Err(error(
statement.span,
@@ -186,6 +200,10 @@ fn function_scope(function: &Function, globals: &BTreeSet<String>) -> Result<Sco
builder.expression(&binding.value, globals)?;
builder.initialized.insert(binding.name.text.clone());
}
StmtKind::Assignment(assignment) => {
builder.expression(&assignment.value, globals)?;
builder.initialized.insert(assignment.name.text.clone());
}
StmtKind::Return(statement) => {
if let Some(value) = &statement.value {
builder.expression(value, globals)?;
+20 -14
View File
@@ -179,22 +179,28 @@ impl<'a> Emitter<'a> {
self.emit(Opcode::StoreGlobal, self.name_index(&function.name.text))
}
fn store_binding(&mut self, name: &str, value: &Expr) -> Result<(), Diagnostic> {
self.expression(value)?;
if self.scope.kind == ScopeKind::Function {
let index = self
.scope
.layout
.locals
.iter()
.position(|local| local == name)
.expect("analysis allocates binding and assignment targets");
self.emit(Opcode::StoreFast, index)?;
} else {
self.emit(Opcode::StoreGlobal, self.name_index(name))?;
}
Ok(())
}
fn statement(&mut self, statement: &Stmt, symbols: &SymbolTable) -> Result<(), Diagnostic> {
match &statement.kind {
StmtKind::Binding(binding) => {
self.expression(&binding.value)?;
if self.scope.kind == ScopeKind::Function {
let index = self
.scope
.layout
.locals
.iter()
.position(|name| name == &binding.name.text)
.expect("analysis allocates each local");
self.emit(Opcode::StoreFast, index)?;
} else {
self.emit(Opcode::StoreGlobal, self.name_index(&binding.name.text))?;
}
StmtKind::Binding(binding) => self.store_binding(&binding.name.text, &binding.value)?,
StmtKind::Assignment(assignment) => {
self.store_binding(&assignment.name.text, &assignment.value)?
}
StmtKind::Return(statement) => {
if let Some(value) = &statement.value {
+2
View File
@@ -2,4 +2,6 @@
[pipeline.rs](pipeline.rs) 将真实 Endor 示例交给 reader/parser/名称分析/代码生成/VM,检查函数值、前向引用、局部遮蔽、精确数字常量、语义诊断、宿主全局与属性回调、对象结果保活、跨 VM 模板装载、反复调用、错误帧清理、递归限额、共享交互命名空间及超过 255 的操作数。
裸赋值检查覆盖跨交互输入创建/更新 globals、参数重赋值、局部槽复用及未初始化读取;执行结束检查空堆。
同时检查重复名称、局部提前读取、未声明名称、不支持的执行特性和不合法 AST 原文范围。CLI 的文件/stdin 与退出码检查见 [cli/tests](https://gitea.entroia.com/Zayden-Jung/endorphin-lyra-cli/src/branch/main/crates/cli/tests/README.md)。源码说明验证意图;仅由 ces-control 的 cargo test --workspace --locked 执行,本地不运行,结果边界见 [验证](../../../docs/operations/validation.md)。返回 [compiler](../README.md)。
+78
View File
@@ -368,3 +368,81 @@ fn compiling_ast_rejects_invalid_source_ranges() {
}
assert!(compile_ast(&root, &source, &CompileOptions::default()).is_err());
}
#[test]
fn bare_assignments_create_and_replace_real_interactive_globals() {
let mut vm = Vm::new().unwrap();
let globals = vm.new_dict().unwrap();
for text in ["value = \"first\"", "value = \"second\"", "value"] {
let options = CompileOptions {
globals: vm
.dict_items(globals)
.unwrap()
.iter()
.filter_map(|(key, _)| match key {
Value::Text(name) => Some(name.clone()),
_ => None,
})
.collect(),
};
let compiled = compile_source(
&EndorReader::from_text("<session>", text),
ParseMode::Interactive,
&options,
)
.unwrap();
let code = compiled.load(&mut vm).unwrap();
let result = vm.execute(code, globals).unwrap();
if text == "value" {
assert_eq!(result, Value::Text("second".into()));
}
vm.release_value(result).unwrap();
vm.release(code).unwrap();
}
assert_eq!(
vm.dict_get(globals, &Value::Text("value".into())).unwrap(),
Some(Value::Text("second".into()))
);
vm.dict_clear(globals).unwrap();
vm.release(globals).unwrap();
vm.collect_cycles();
assert_eq!(vm.object_count(), 0);
}
#[test]
fn assignments_update_parameters_and_allocate_one_local_slot_per_target() {
let compiled = compile(
"function main(value: String): String { copy = value copy = \"updated\" value = copy return value }",
ParseMode::Module,
);
let mut vm = Vm::new().unwrap();
let (code, globals) = module(&mut vm, &compiled);
let result = vm
.call(
function(&vm, globals, "main"),
&[Value::Text("original".into())],
)
.unwrap();
assert_eq!(result, Value::Text("updated".into()));
vm.release_value(result).unwrap();
cleanup(&mut vm, code, globals);
assert!(
compile_source(
&EndorReader::from_text("<uninitialized>", "function f(): T { x = x return x }"),
ParseMode::Module,
&CompileOptions::default()
)
.is_err()
);
assert!(
compile_source(
&EndorReader::from_text(
"<duplicate>",
"function f(): T { let x = 1 let x = 2 return x }"
),
ParseMode::Module,
&CompileOptions::default()
)
.is_err()
);
}
+2
View File
@@ -17,10 +17,12 @@ module Endor {
Block = (Stmt* statements, span span)
Stmt = (StmtKind kind, span span)
StmtKind = Binding(Binding binding)
| Assignment(Assignment assignment)
| Return(ReturnStatement statement)
| Expression(Expr expression)
| Declaration(Decl declaration)
Binding = (Name name, Expr value, span span)
Assignment = (Name name, Expr value, span span)
ReturnStatement = (Expr? value, span span)
InteractiveInput = (Stmt* statements, span span)
+38 -2
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "urn:endor:ast:sha256:c0da1a2640ad85f2c8e8159a10756e649d3aab179823a7cd890d9ebbb7f5d46e",
"$id": "urn:endor:ast:sha256:8c89fde6c029357a0809751c41cb46d59972b987bbe1b25c0cced4bfcdbe2a7b",
"$ref": "#/$defs/AstDocument",
"$defs": {
"Span": {
@@ -27,7 +27,7 @@
"type": "object",
"properties": {
"schema": {
"const": "urn:endor:ast:sha256:c0da1a2640ad85f2c8e8159a10756e649d3aab179823a7cd890d9ebbb7f5d46e"
"const": "urn:endor:ast:sha256:8c89fde6c029357a0809751c41cb46d59972b987bbe1b25c0cced4bfcdbe2a7b"
},
"root": {
"$ref": "#/$defs/AstRoot"
@@ -266,6 +266,22 @@
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": {
"const": "Assignment"
},
"data": {
"$ref": "#/$defs/Assignment"
}
},
"required": [
"kind",
"data"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
@@ -336,6 +352,26 @@
],
"additionalProperties": false
},
"Assignment": {
"type": "object",
"properties": {
"name": {
"$ref": "#/$defs/Name"
},
"value": {
"$ref": "#/$defs/Expr"
},
"span": {
"$ref": "#/$defs/Span"
}
},
"required": [
"name",
"value",
"span"
],
"additionalProperties": false
},
"ReturnStatement": {
"type": "object",
"properties": {
+13 -2
View File
@@ -1,6 +1,6 @@
// Generated by scripts/generate_ast.py from Endor.asdl. Do not edit.
export const AST_SCHEMA_ID = "urn:endor:ast:sha256:c0da1a2640ad85f2c8e8159a10756e649d3aab179823a7cd890d9ebbb7f5d46e" as const;
export const AST_SCHEMA_ID = "urn:endor:ast:sha256:8c89fde6c029357a0809751c41cb46d59972b987bbe1b25c0cced4bfcdbe2a7b" as const;
export interface Span { start: number; end: number; }
export type AstDocument = { schema: typeof AST_SCHEMA_ID; root: AstRoot; };
export type AstRoot = { kind: "Module"; data: Program; } | { kind: "Expression"; data: Expr; } | { kind: "Interactive"; data: InteractiveInput; };
@@ -12,8 +12,9 @@ export type Function = { name: Name; parameters: Array<Parameter>; return_type:
export type Parameter = { name: Name; type_name: Name; span: Span; };
export type Block = { statements: Array<Stmt>; span: Span; };
export type Stmt = { kind: StmtKind; span: Span; };
export type StmtKind = { kind: "Binding"; data: Binding; } | { kind: "Return"; data: ReturnStatement; } | { kind: "Expression"; data: Expr; } | { kind: "Declaration"; data: Decl; };
export type StmtKind = { kind: "Binding"; data: Binding; } | { kind: "Assignment"; data: Assignment; } | { kind: "Return"; data: ReturnStatement; } | { kind: "Expression"; data: Expr; } | { kind: "Declaration"; data: Decl; };
export type Binding = { name: Name; value: Expr; span: Span; };
export type Assignment = { name: Name; value: Expr; span: Span; };
export type ReturnStatement = { value?: Expr | null; span: Span; };
export type InteractiveInput = { statements: Array<Stmt>; span: Span; };
export type Expr = { kind: ExprKind; span: Span; };
@@ -34,6 +35,7 @@ visitBlock?(node: Block): boolean | void;
visitStmt?(node: Stmt): boolean | void;
visitStmtKind?(node: StmtKind): boolean | void;
visitBinding?(node: Binding): boolean | void;
visitAssignment?(node: Assignment): boolean | void;
visitReturnStatement?(node: ReturnStatement): boolean | void;
visitInteractiveInput?(node: InteractiveInput): boolean | void;
visitExpr?(node: Expr): boolean | void;
@@ -112,6 +114,9 @@ switch (node.kind) {
case "Binding":
walkBinding(node.data, visitor);
break;
case "Assignment":
walkAssignment(node.data, visitor);
break;
case "Return":
walkReturnStatement(node.data, visitor);
break;
@@ -129,6 +134,12 @@ walkName(node.name, visitor);
walkExpr(node.value, visitor);
walkSpan(node.span, visitor);
}
export function walkAssignment(node: Assignment, visitor: Visitor): void {
if (visitor.visitAssignment?.(node) === false) return;
walkName(node.name, visitor);
walkExpr(node.value, visitor);
walkSpan(node.span, visitor);
}
export function walkReturnStatement(node: ReturnStatement, visitor: Visitor): void {
if (visitor.visitReturnStatement?.(node) === false) return;
if (node.value != null) walkExpr(node.value, visitor);
+15 -1
View File
@@ -3,7 +3,7 @@
use super::Spanned;
use crate::Span;
pub const AST_SCHEMA_ID: &str =
"urn:endor:ast:sha256:c0da1a2640ad85f2c8e8159a10756e649d3aab179823a7cd890d9ebbb7f5d46e";
"urn:endor:ast:sha256:8c89fde6c029357a0809751c41cb46d59972b987bbe1b25c0cced4bfcdbe2a7b";
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct AstDocument {
@@ -123,6 +123,7 @@ impl Spanned for Stmt {
#[serde(tag = "kind", content = "data", deny_unknown_fields)]
pub enum StmtKind {
Binding(Binding),
Assignment(Assignment),
Return(ReturnStatement),
Expression(Expr),
Declaration(Decl),
@@ -131,6 +132,7 @@ impl Spanned for StmtKind {
fn span(&self) -> Span {
match self {
Self::Binding(node) => node.span(),
Self::Assignment(node) => node.span(),
Self::Return(node) => node.span(),
Self::Expression(node) => node.span(),
Self::Declaration(node) => node.span(),
@@ -151,6 +153,18 @@ impl Spanned for Binding {
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Assignment {
pub name: Name,
pub value: Expr,
pub span: Span,
}
impl Spanned for Assignment {
fn span(&self) -> Span {
self.span
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ReturnStatement {
pub value: Option<Expr>,
pub span: Span,
+21 -2
View File
@@ -1,8 +1,9 @@
// Generated by scripts/generate_ast.py from Endor.asdl. Do not edit.
use super::{
AstDocument, AstRoot, BinaryOp, Binding, Block, Decl, DeclKind, Expr, ExprKind, Function,
InteractiveInput, Name, Parameter, Program, ReturnStatement, Spanned, Stmt, StmtKind, UnaryOp,
Assignment, AstDocument, AstRoot, BinaryOp, Binding, Block, Decl, DeclKind, Expr, ExprKind,
Function, InteractiveInput, Name, Parameter, Program, ReturnStatement, Spanned, Stmt, StmtKind,
UnaryOp,
};
use crate::Span;
pub trait Visitor {
@@ -83,6 +84,12 @@ pub trait Visitor {
self.leave_node();
result
}
fn visit_assignment(&mut self, node: &Assignment) -> Result<(), Self::Error> {
self.enter_node(node.span())?;
let result = walk_assignment(self, node);
self.leave_node();
result
}
fn visit_return_statement(&mut self, node: &ReturnStatement) -> Result<(), Self::Error> {
self.enter_node(node.span())?;
let result = walk_return_statement(self, node);
@@ -201,6 +208,9 @@ pub fn walk_stmt_kind<V: Visitor + ?Sized>(
StmtKind::Binding(value) => {
visitor.visit_binding(value)?;
}
StmtKind::Assignment(value) => {
visitor.visit_assignment(value)?;
}
StmtKind::Return(value) => {
visitor.visit_return_statement(value)?;
}
@@ -219,6 +229,15 @@ pub fn walk_binding<V: Visitor + ?Sized>(visitor: &mut V, node: &Binding) -> Res
visitor.visit_span(&node.span)?;
Ok(())
}
pub fn walk_assignment<V: Visitor + ?Sized>(
visitor: &mut V,
node: &Assignment,
) -> Result<(), V::Error> {
visitor.visit_name(&node.name)?;
visitor.visit_expr(&node.value)?;
visitor.visit_span(&node.span)?;
Ok(())
}
pub fn walk_return_statement<V: Visitor + ?Sized>(
visitor: &mut V,
node: &ReturnStatement,
+13 -2
View File
@@ -62,6 +62,7 @@ peg::parser! {
Stmt { span: declaration.span, kind: StmtKind::Declaration(declaration) }
}
/ binding:binding() { binding_statement(binding) }
/ assignment:assignment() { assignment }
/ expression:expression() {
Stmt { span: expression.span, kind: StmtKind::Expression(expression) }
}
@@ -78,12 +79,22 @@ peg::parser! {
}
rule block() -> Block
= start:lbrace() bindings:binding()* returned:return_statement() end:rbrace() {
let mut statements: Vec<Stmt> = bindings.into_iter().map(binding_statement).collect();
= start:lbrace() locals:local_statement()* returned:return_statement() end:rbrace() {
let mut statements = locals;
statements.push(returned);
Block { statements, span: start.through(end) }
}
rule local_statement() -> Stmt
= binding:binding() { binding_statement(binding) }
/ assignment:assignment() { assignment }
rule assignment() -> Stmt
= name:name() equal() value:expression() {
let span = name.span.through(value.span);
Stmt { span, kind: StmtKind::Assignment(Assignment { name, value, span }) }
}
rule binding() -> Binding
= start:let_keyword() name:name() equal() value:expression() {
Binding { span: start.through(value.span), name, value }
+2 -2
View File
@@ -1,6 +1,6 @@
use crate::ast::{
AstRoot, BinaryOp, Binding, Block, Decl, DeclKind, Expr, ExprKind, Function, InteractiveInput,
Name, Parameter, Program, ReturnStatement, Stmt, StmtKind, UnaryOp,
Assignment, AstRoot, BinaryOp, Binding, Block, Decl, DeclKind, Expr, ExprKind, Function,
InteractiveInput, Name, Parameter, Program, ReturnStatement, Stmt, StmtKind, UnaryOp,
};
use crate::lexer::decode_string;
use crate::{Diagnostic, DiagnosticKind, Span, Token, TokenKind, lex};
+1 -1
View File
@@ -2,7 +2,7 @@
[syntax.rs](syntax.rs) 覆盖 UTF-8 token 范围、中文/日文名称、数字原文、负号、字符串转义与错误、算术优先级和结合性、成员/调用链、函数与绑定、上下文名称、完整输入、非法/未支持语法以及 CRLF 和 EOF 诊断位置。
[ast.rs](ast.rs) 覆盖根模式、声明/语句结构、生成 Visitor 与错误退出、Spanned 位置、带 schema 的 JSON 往返及错误输入;同时核对编辑器产物标识。
[ast.rs](ast.rs) 覆盖根模式、声明/语句结构、生成 Visitor 与错误退出、Spanned 位置、带 schema 的 JSON 往返及错误输入;同时核对编辑器产物标识,并验证裸名称赋值的独立节点、Unicode 范围、JSON 往返和不支持的赋值目标。
[lexical.rs](lexical.rs) 专门覆盖字符 PEG 迁移后的原文位置换算、首个错误及详细诊断、Unicode 非法转义、未闭合字符串、注释/除法/成员/数字/字符串边界和空输入/尾随注释。
+21
View File
@@ -179,3 +179,24 @@ fn json_interfaces_reject_unknown_fields_schema_changes_and_invalid_ranges() {
Err(AstError::InvalidSpan(_))
));
}
#[test]
fn bare_assignment_has_its_own_ast_and_unicode_source_ranges() {
let source = "标题 = ui.text(\"你好\")";
let root = parse_ast(source, ParseMode::Interactive).unwrap();
let AstRoot::Interactive(input) = &root else {
panic!("interactive input")
};
let StmtKind::Assignment(assignment) = &input.statements[0].kind else {
panic!("assignment")
};
assert_eq!(assignment.name.text, "标题");
assert_eq!(assignment.name.source_text(source), Some("标题"));
assert_eq!(assignment.source_text(source), Some(source));
let document = AstDocument::new(root).unwrap();
document.validate_source(source).unwrap();
let json = document.to_json().unwrap();
assert_eq!(AstDocument::from_json(&json).unwrap(), document);
assert!(parse_ast("value = 1 = 2", ParseMode::Interactive).is_err());
assert!(parse_ast("object.field = 1", ParseMode::Interactive).is_err());
}
+1 -1
View File
@@ -7,7 +7,7 @@
| EndorObject、句柄、默认操作 | [对象模型](runtime/objects.md) | objects/src |
| Number、String、Dict、Cell、Function | [基本对象](runtime/builtins.md) | objects/src |
| 源码读取与来源 | [Reader](runtime/reader.md) | frontend/src/reader.rs |
| 词法、语法和诊断 | [Frontend](runtime/frontend.md) | frontend/src/lexer.peg/grammar.peg |
| 词法、语法、裸赋值和诊断 | [Frontend](runtime/frontend.md) | frontend/src/lexer.peg/grammar.peg |
| ASDL、AST 与编辑器接口 | [AST](runtime/ast.md) | frontend/Endor.asdl |
| 名称分析、编译和装载 | [Compiler](runtime/compiler.md) | compiler/src |
| 字节码、函数与限额 | [字节码 VM](runtime/bytecode.md) | objects/src、core/src |
+3 -2
View File
@@ -33,13 +33,14 @@ Rust 用 struct/enum、Vec/Option 表达结构;生成器在按值依赖图
| Program | declarations: Vec<Decl> 与 span;当前声明种类为 Function |
| Decl/DeclKind | 声明的位置及其具体种类;Function 保存参数、返回类型名称和 Block |
| Block | statements: Vec<Stmt> 与 span,不再用固定的 bindings/result 字段表示 |
| Stmt/StmtKind | Binding、Return、Expression、Declaration;每条语句有自身范围 |
| Stmt/StmtKind | Binding、Assignment、Return、Expression、Declaration;每条语句有自身范围 |
| Assignment | 名称目标、右侧表达式及原文范围;独立于 let 的 Binding 节点 |
| ReturnStatement | 可选表达式与范围;当前源码中的 return 仍要求表达式 |
| Expr/ExprKind | 名称、数字原文、字符串、符号、一元/二元运算、成员和调用 |
参数与返回类型仍为 Name,只保留语法,不实施类型或名称检查。Program::functions() 提供函数迭代便捷接口。旧的 Program.functions、Block.bindings/result/return_span 字段已移除;源码构造和使用方改为声明/语句列表。ExprKind 的既有表达式结构保留。
AST 数据模型允许一般语句列表,不代表 parser 接受了所有组合。模块仍要求一个或多个函数;函数体仍接受 let* 后跟一个带值 return。Interactive 模式接受一次函数声明、let 绑定或表达式,必须消费完整输入;不创建交互会话,不处理续行提示,不执行语言代码。多条交互提交由宿主分别解析。条件、循环、导入、类等尚未实现,也不加入 FunctionType 这种 Python 专用入口。
AST 数据模型允许一般语句列表,不代表 parser 接受了所有组合。模块仍要求一个或多个函数;函数体接受 let/裸赋值序列后跟一个带值 return。Interactive 模式接受一次函数声明、let 绑定、裸名称赋值或表达式,必须消费完整输入;不创建交互会话,不处理续行提示,不执行语言代码。多条交互提交由宿主分别解析。条件、循环、导入、类等尚未实现,也不加入 FunctionType 这种 Python 专用入口。
## 解析与位置
+6 -6
View File
@@ -15,9 +15,9 @@
## 名称与作用域
首版是模块作用域和函数作用域。模块先登记全部函数名,再分析函数体,因此支持前向引用、自递归和函数作为普通值传递。函数参数占据最前面的局部槽,let 按声明顺序分配后续槽;引用优先使用函数局部,其次使用已声明全局。
首版是模块作用域和函数作用域。模块先登记全部函数名,再分析函数体,因此支持前向引用、自递归和函数作为普通值传递。函数参数占据最前面的局部槽,let 与首次赋值目标按出现顺序分配后续槽;引用优先使用函数局部,其次使用已声明全局。
函数内先收集全部局部名称,再按语句顺序检查初始化。后面声明的 let 会遮蔽整个函数中的同名全局,但提前读取报错;let x = x 也会报未初始化。参数、局部绑定和同一模块的函数不允许在各自作用域内重复声明;参数或局部可以遮蔽模块函数。let 当前没有赋值/重绑定语法。嵌套函数/闭包分析和块级作用域尚未实现。
函数内先收集全部局部名称,再按语句顺序检查初始化。后面声明的 let 会遮蔽整个函数中的同名全局,但提前读取报错;let x = x 也会报未初始化。参数、局部绑定和同一模块的函数不允许在各自作用域内重复声明;参数或局部可以遮蔽模块函数。裸名称赋值 `x = expression` 可在交互全局创建或更新名字;函数内赋值复用已有参数/局部槽,不存在时分配局部槽。赋值右侧先求值再写入,失败不会覆盖该目标;函数内赋值目标仍遮蔽同名全局,首次初始化前读取报错。`let` 的重复声明检查不变。嵌套函数/闭包分析和块级作用域尚未实现。
CompileOptions.globals 显式声明宿主会提供的 globals/builtins 名称,默认空。不认识的名称在编译时报错;已声明的宿主名称仍必须在执行时存在,否则 VM 返回 NameNotFound。编译器不自动注入 print、默认基础类或标准库,也不读取这些名字的值。类型注解只保留语法,不参与名称绑定、运行时求值或类型检查。
@@ -27,14 +27,14 @@ CompileOptions.globals 显式声明宿主会提供的 globals/builtins 名称
| --- | --- |
| 字符串、整数、十进制常量 | 放入常量表并 LoadConst;数字经现有 EndorNumber 解析,不转换为 f64 |
| 数字字面量前的一个或多个负号 | 折叠符号为精确常量;不是任意表达式的一元算术 |
| 局部读取/let | LoadFast/StoreFast;参数和局部名称在 CodeLayout 中 |
| 全局读取 | LoadGlobal,使用该代码对象的 names 表 |
| 局部读取/let/赋值 | LoadFast/StoreFast;参数和局部名称在 CodeLayout 中 |
| 全局读取/赋值 | LoadGlobal/StoreGlobal,使用该代码对象的 names 表 |
| 函数声明 | 子代码模板 → LoadConst(code) → MakeFunction(0, 0) → StoreGlobal |
| 普通调用 | 求值 callee → PushNull → 从左到右求值位置参数 → Call(argc) |
| 成员读取 | 求值对象 → LoadAttr;沿用 VM 属性协议,无自动 self 绑定 |
| return | 求值结果 → ReturnValue;无值 return 或 AST 函数末尾无 return 时生成 nil |
源码 parser 仍要求函数体为 let* 加一个带值 return;更通用的表达式语句和空 return 可以由 AST 调用方构造,但不能因此宣称源码语法已开放。模块初始化只发布函数并返回 nil,不自动调用 main。Lyra 宿主选择入口属于宿主行为。
源码 parser 仍要求函数体为 let/裸赋值序列加一个带值 return;更通用的表达式语句和空 return 可以由 AST 调用方构造,但不能因此宣称源码语法已开放。模块初始化只发布函数并返回 nil,不自动调用 main。Lyra 宿主选择入口属于宿主行为。
二元算术、非字面量的一元负号和符号值 .name 目前产生明确 Compilation 诊断,不伪造运行结果。导入、类、Agent、条件/循环、关键字参数等仍未实现。函数参数个数和对象是否可调用由 VM 在运行时检查。
@@ -44,7 +44,7 @@ CompiledCode 是纯 Rust 模板,嵌套代码常量在模板内部使用占位
宿主为模块提供 globals 字典,调用 vm.execute(code, globals) 发布函数,然后从字典借用函数句柄并调用 vm.call。函数共享装载后的代码,反复调用不重新编译;execute/call 的对象结果持有根,用后 release_value。函数指向 globals,globals 保存函数,因此宿主需保留命名空间期间的根,并在销毁时清空字典或释放根后显式 collect_cycles。编译器不清空宿主字典。
Expression 模式返回表达式值;Interactive 模式严格编译一次声明、let 或表达式。声明/绑定返回 nil,表达式返回值;宿主可以跨提交复用 globals,并在下一次 CompileOptions 中声明可见名称。Lyra 当前没有 REPL、续行或自动会话管理。
Expression 模式返回表达式值;Interactive 模式严格编译一次声明、let、裸赋值或表达式。声明/绑定/赋值返回 nil,表达式返回值;宿主可以跨提交复用 globals,并在下一次 CompileOptions 中声明可见名称。Lyra 当前没有 REPL、续行或自动会话管理。
代码对象保存来源字符串、函数名和定义起始行,现有 VM traceback 保存该位置及指令偏移;没有每条指令对应的精确源码行/列映射。现有 ExecutionLimits 帧数/指令数限制可由嵌入方继续使用;本链路不构成不可信代码沙箱。
+5 -2
View File
@@ -52,11 +52,12 @@ AST 结构及 Visitor、Spanned 和跨工具 JSON 契约见 [AST 模型](ast.md)
program = function+
function = "function" name "(" parameters? ")" ":" name block
parameter = name ":" name
block = "{" binding* "return" expression "}"
block = "{" (binding | assignment)* "return" expression "}"
binding = "let" name "=" expression
assignment = name "=" expression
```
parameters 和调用参数列表使用逗号分隔,不接受尾随逗号。函数体要求一个末尾 return;函数体尚不支持独立表达式语句、重赋值、嵌套函数声明或默认参数源码写法。新增 Interactive 解析模式接受一次函数声明、let 绑定或表达式,完整消费输入,但不创建 REPL 或执行代码;AST 可表示的语句组合比当前源码子集更广。参数和返回类型目前只是名称节点;并不据此创建语言 type 对象或检查类型。
parameters 和调用参数列表使用逗号分隔,不接受尾随逗号。函数体要求一个末尾 return;函数体尚不支持独立表达式语句、属性赋值、嵌套函数声明或默认参数源码写法。新增 Interactive 解析模式接受一次函数声明、let 绑定、裸名称赋值或表达式,完整消费输入,但不创建 REPL 或执行代码;AST 可表示的语句组合比当前源码子集更广。参数和返回类型目前只是名称节点;并不据此创建语言 type 对象或检查类型。
表达式包括名称、数字、字符串、`.name` 符号、括号分组、成员访问与位置参数调用、一元负号以及四则运算。优先级从高到低为:
@@ -76,3 +77,5 @@ use、namespace、class、agent、完整类型语法、条件/循环、比较
`lyra parse <file>` 与 `lyra parse -` 成功时输出 Rust Debug 格式的 AST,失败时在 stderr 输出来源/行/列和诊断并返回非零退出码;`-` 表示从 stdin 读到 EOF,不是 REPL。阅读与来源包装规则见 [阅读器](reader.md)。可用 --mode 选择 module/expression/interactive,用 --json 输出带 schema 标识的 AstDocument;普通输出为 AstRoot Debug 文本,稳定交换格式见 [AST 模型](ast.md)。命令不执行源码、不初始化 VM、不连接数据库;具体运行与本地禁测约定见 [运行](../operations/README.md) 和 [验证](../operations/validation.md)。
当前 Compiler 将 AST 经模块/函数名称分析转换为现有字节码,再交给 VM 解释执行。这是解释器内部的编译/执行准备步骤,不要求使用者先构建程序,见 [执行与版本模型](../architecture/execution-and-versions.md)。MakeFunction 已接入源码函数编译,但解析接受的二元算术和符号值仍没有执行语义;编译器会给出明确诊断,见 [编译链路](compiler.md)。源码闭包尚未实现。解析器也不是不可信代码沙箱,当前没有通用输入大小或解析递归深度限额。
裸赋值 `name = expression` 创建或更新当前作用域中的名称;它不是表达式,不能链式赋值,当前也不接受属性/索引目标。`let` 仍表示声明,保留重复声明检查;名称与初始化规则见 [Compiler](compiler.md)。两种源码写法产生独立的 Binding/Assignment 节点,宿主不改写输入。