From df1b77fc1ab5fd4dc37f4a4fce8744fc0e91e7d5 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Tue, 20 Oct 2026 18:16:54 +0800 Subject: [PATCH] feat: support bare name assignments in interactive and function scopes --- crates/compiler/src/analysis.rs | 18 ++++++ crates/compiler/src/codegen.rs | 34 ++++++----- crates/compiler/tests/README.md | 2 + crates/compiler/tests/pipeline.rs | 78 ++++++++++++++++++++++++++ crates/frontend/Endor.asdl | 2 + crates/frontend/editor/ast.schema.json | 40 ++++++++++++- crates/frontend/editor/ast.ts | 15 ++++- crates/frontend/src/ast/generated.rs | 16 +++++- crates/frontend/src/ast/visitor.rs | 23 +++++++- crates/frontend/src/grammar.peg | 15 ++++- crates/frontend/src/parser.rs | 4 +- crates/frontend/tests/README.md | 2 +- crates/frontend/tests/ast.rs | 21 +++++++ docs/README.md | 2 +- docs/runtime/ast.md | 5 +- docs/runtime/compiler.md | 12 ++-- docs/runtime/frontend.md | 7 ++- 17 files changed, 259 insertions(+), 37 deletions(-) diff --git a/crates/compiler/src/analysis.rs b/crates/compiler/src/analysis.rs index d8820ef..061967d 100644 --- a/crates/compiler/src/analysis.rs +++ b/crates/compiler/src/analysis.rs @@ -114,6 +114,11 @@ pub fn analyze(root: &AstRoot, options: &CompileOptions) -> Result { + 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) -> Result { + 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) -> Result { + 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)?; diff --git a/crates/compiler/src/codegen.rs b/crates/compiler/src/codegen.rs index e65d20e..5143102 100644 --- a/crates/compiler/src/codegen.rs +++ b/crates/compiler/src/codegen.rs @@ -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 { diff --git a/crates/compiler/tests/README.md b/crates/compiler/tests/README.md index 30990b7..109a5e1 100644 --- a/crates/compiler/tests/README.md +++ b/crates/compiler/tests/README.md @@ -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)。 diff --git a/crates/compiler/tests/pipeline.rs b/crates/compiler/tests/pipeline.rs index f333f97..ba5b67f 100644 --- a/crates/compiler/tests/pipeline.rs +++ b/crates/compiler/tests/pipeline.rs @@ -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("", 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("", "function f(): T { x = x return x }"), + ParseMode::Module, + &CompileOptions::default() + ) + .is_err() + ); + assert!( + compile_source( + &EndorReader::from_text( + "", + "function f(): T { let x = 1 let x = 2 return x }" + ), + ParseMode::Module, + &CompileOptions::default() + ) + .is_err() + ); +} diff --git a/crates/frontend/Endor.asdl b/crates/frontend/Endor.asdl index 5af0a67..6c7e7c5 100644 --- a/crates/frontend/Endor.asdl +++ b/crates/frontend/Endor.asdl @@ -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) diff --git a/crates/frontend/editor/ast.schema.json b/crates/frontend/editor/ast.schema.json index 48cf811..5aa7376 100644 --- a/crates/frontend/editor/ast.schema.json +++ b/crates/frontend/editor/ast.schema.json @@ -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": { diff --git a/crates/frontend/editor/ast.ts b/crates/frontend/editor/ast.ts index 0591004..b56c597 100644 --- a/crates/frontend/editor/ast.ts +++ b/crates/frontend/editor/ast.ts @@ -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; return_type: export type Parameter = { name: Name; type_name: Name; span: Span; }; export type Block = { statements: Array; 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; 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); diff --git a/crates/frontend/src/ast/generated.rs b/crates/frontend/src/ast/generated.rs index f33686b..a0487b9 100644 --- a/crates/frontend/src/ast/generated.rs +++ b/crates/frontend/src/ast/generated.rs @@ -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, pub span: Span, diff --git a/crates/frontend/src/ast/visitor.rs b/crates/frontend/src/ast/visitor.rs index 8ee1679..f615a88 100644 --- a/crates/frontend/src/ast/visitor.rs +++ b/crates/frontend/src/ast/visitor.rs @@ -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( 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(visitor: &mut V, node: &Binding) -> Res visitor.visit_span(&node.span)?; Ok(()) } +pub fn walk_assignment( + 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( visitor: &mut V, node: &ReturnStatement, diff --git a/crates/frontend/src/grammar.peg b/crates/frontend/src/grammar.peg index c46cb95..9624afb 100644 --- a/crates/frontend/src/grammar.peg +++ b/crates/frontend/src/grammar.peg @@ -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 = 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 } diff --git a/crates/frontend/src/parser.rs b/crates/frontend/src/parser.rs index f3a3a81..d5fca84 100644 --- a/crates/frontend/src/parser.rs +++ b/crates/frontend/src/parser.rs @@ -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}; diff --git a/crates/frontend/tests/README.md b/crates/frontend/tests/README.md index a4f2393..992c628 100644 --- a/crates/frontend/tests/README.md +++ b/crates/frontend/tests/README.md @@ -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 非法转义、未闭合字符串、注释/除法/成员/数字/字符串边界和空输入/尾随注释。 diff --git a/crates/frontend/tests/ast.rs b/crates/frontend/tests/ast.rs index a72887e..8eb7a6f 100644 --- a/crates/frontend/tests/ast.rs +++ b/crates/frontend/tests/ast.rs @@ -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()); +} diff --git a/docs/README.md b/docs/README.md index f7e48f4..7576d59 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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 | diff --git a/docs/runtime/ast.md b/docs/runtime/ast.md index c000606..c366575 100644 --- a/docs/runtime/ast.md +++ b/docs/runtime/ast.md @@ -33,13 +33,14 @@ Rust 用 struct/enum、Vec/Option 表达结构;生成器在按值依赖图 | Program | declarations: Vec 与 span;当前声明种类为 Function | | Decl/DeclKind | 声明的位置及其具体种类;Function 保存参数、返回类型名称和 Block | | Block | statements: Vec 与 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 专用入口。 ## 解析与位置 diff --git a/docs/runtime/compiler.md b/docs/runtime/compiler.md index 99b2d7f..2aef947 100644 --- a/docs/runtime/compiler.md +++ b/docs/runtime/compiler.md @@ -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 帧数/指令数限制可由嵌入方继续使用;本链路不构成不可信代码沙箱。 diff --git a/docs/runtime/frontend.md b/docs/runtime/frontend.md index ca482d3..012228c 100644 --- a/docs/runtime/frontend.md +++ b/docs/runtime/frontend.md @@ -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 ` 与 `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 节点,宿主不改写输入。