feat: support bare name assignments in interactive and function scopes
This commit is contained in:
@@ -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)?;
|
||||
|
||||
@@ -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,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)。
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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 非法转义、未闭合字符串、注释/除法/成员/数字/字符串边界和空输入/尾随注释。
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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 专用入口。
|
||||
|
||||
## 解析与位置
|
||||
|
||||
|
||||
@@ -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 帧数/指令数限制可由嵌入方继续使用;本链路不构成不可信代码沙箱。
|
||||
|
||||
|
||||
@@ -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 节点,宿主不改写输入。
|
||||
|
||||
Reference in New Issue
Block a user