atlispcc 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. atlispcc-0.1.0/PKG-INFO +10 -0
  2. atlispcc-0.1.0/README.md +167 -0
  3. atlispcc-0.1.0/atlispcc.egg-info/PKG-INFO +10 -0
  4. atlispcc-0.1.0/atlispcc.egg-info/SOURCES.txt +42 -0
  5. atlispcc-0.1.0/atlispcc.egg-info/dependency_links.txt +1 -0
  6. atlispcc-0.1.0/atlispcc.egg-info/entry_points.txt +2 -0
  7. atlispcc-0.1.0/atlispcc.egg-info/requires.txt +6 -0
  8. atlispcc-0.1.0/atlispcc.egg-info/top_level.txt +4 -0
  9. atlispcc-0.1.0/compiler/__init__.py +0 -0
  10. atlispcc-0.1.0/compiler/analyzer.py +217 -0
  11. atlispcc-0.1.0/compiler/assembler.py +265 -0
  12. atlispcc-0.1.0/compiler/ast.py +120 -0
  13. atlispcc-0.1.0/compiler/cad_symbols.py +145 -0
  14. atlispcc-0.1.0/compiler/cli.py +434 -0
  15. atlispcc-0.1.0/compiler/codegen.py +767 -0
  16. atlispcc-0.1.0/compiler/fas_writer.py +123 -0
  17. atlispcc-0.1.0/compiler/lexer.py +102 -0
  18. atlispcc-0.1.0/compiler/linker.py +468 -0
  19. atlispcc-0.1.0/compiler/opcodes.py +134 -0
  20. atlispcc-0.1.0/compiler/opt.py +1376 -0
  21. atlispcc-0.1.0/compiler/packages.py +49 -0
  22. atlispcc-0.1.0/compiler/parser.py +275 -0
  23. atlispcc-0.1.0/compiler/resource_builder.py +263 -0
  24. atlispcc-0.1.0/compiler/symbol_table.py +217 -0
  25. atlispcc-0.1.0/compiler/vlx_builder.py +92 -0
  26. atlispcc-0.1.0/fas4_decompiler.py +2722 -0
  27. atlispcc-0.1.0/fas_core.py +699 -0
  28. atlispcc-0.1.0/pyproject.toml +41 -0
  29. atlispcc-0.1.0/setup.cfg +4 -0
  30. atlispcc-0.1.0/tests/test_analyzer.py +80 -0
  31. atlispcc-0.1.0/tests/test_assembler.py +168 -0
  32. atlispcc-0.1.0/tests/test_cli.py +146 -0
  33. atlispcc-0.1.0/tests/test_compile_decompile.py +419 -0
  34. atlispcc-0.1.0/tests/test_compiler_output.py +263 -0
  35. atlispcc-0.1.0/tests/test_disasm.py +130 -0
  36. atlispcc-0.1.0/tests/test_lexer.py +88 -0
  37. atlispcc-0.1.0/tests/test_linker.py +237 -0
  38. atlispcc-0.1.0/tests/test_optimizer.py +405 -0
  39. atlispcc-0.1.0/tests/test_packages.py +110 -0
  40. atlispcc-0.1.0/tests/test_parser.py +215 -0
  41. atlispcc-0.1.0/tests/test_symbol_table.py +99 -0
  42. atlispcc-0.1.0/tests/test_variadic_args.py +56 -0
  43. atlispcc-0.1.0/tests/test_vlx_builder.py +92 -0
  44. atlispcc-0.1.0/validate.py +240 -0
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.4
2
+ Name: atlispcc
3
+ Version: 0.1.0
4
+ Summary: FAS4 bytecode decompiler and compiler toolchain for AutoLISP-compatible CAD platforms
5
+ Requires-Python: >=3.8
6
+ Requires-Dist: typing-extensions>=4.5.0
7
+ Provides-Extra: dev
8
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
9
+ Requires-Dist: black>=22.3.0; extra == "dev"
10
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
@@ -0,0 +1,167 @@
1
+ # atlispcc — FAS4 字节码编译器 & 反编译器
2
+
3
+ `atlispcc` 是一个统一的 CLI 工具链,用于将 AutoLISP (`.lsp`) 编译为 FAS4 字节码 (`.fas`),反编译 FAS4 回 Lisp,链接多个 FAS 文件,以及验证 Lisp 语法。
4
+
5
+ ## CLI
6
+
7
+ ```sh
8
+ atlispcc compile [-O {0,1,2,3}] <input.lsp> [output.fas] # 编译
9
+ atlispcc decompile <input.fas> [output.lsp] # 反编译 FAS → Lisp
10
+ atlispcc disasm <input.fas> [output.lasm] # 反汇编 FAS → 字节码指令列表
11
+ atlispcc asm <input.lasm> [output.fas] # 汇编 .lasm → FAS
12
+ atlispcc validate <input.lsp> # 验证
13
+ atlispcc link [-o OUTPUT] <input.fas> <input.fas> [...] # 链接 FAS
14
+ atlispcc build-vlx <input.fas>... -o output.vlx [--export ...] # 构建 VLX 容器
15
+ ```
16
+
17
+ 无子命令时默认为 compile:`atlispcc <input.lsp> [output.fas]`
18
+
19
+ `disasm` / `asm` 互为逆操作,支持 `.fas → .lasm → .fas` 往返。
20
+
21
+ ## 编译器管线
22
+
23
+ ```
24
+ Lisp 源码 → Parser.parse() → AST
25
+ → SymbolTableBuilder.build() → SymbolTable
26
+ → Codegen.generate() → List[FunctionDef](指令流)
27
+ → [OptPipeline.optimize() # 可选:-O1/-O2/-O3]
28
+ → ResourceBuilder.build() → 资源段
29
+ → FasWriter.write() → FAS4 二进制
30
+ ```
31
+
32
+ ## 反编译器管线
33
+
34
+ ```
35
+ Fas4Parser.parse() → Disassembler.disassemble()
36
+ → ControlFlowAnalyzer.find_functions() → LispGenerator.generate()
37
+ ```
38
+
39
+ ## 链接器管线
40
+
41
+ ```
42
+ FAS A + FAS B + ... → 合并符号表/函数目录 → 重排索引 → 修复交叉引用 → 合并 FAS
43
+ ```
44
+
45
+ ## 包系统 (CL-style Namespace)
46
+
47
+ ### 语法
48
+
49
+ ```lisp
50
+ (defpackage vec
51
+ (:use)
52
+ (:export vec2 vec3))
53
+
54
+ (in-package vec)
55
+
56
+ (defun vec2 (x) (+ x 1)) ; 内部名混淆为 vec|vec2
57
+
58
+ ;; 跨包调用:
59
+ (in-package app)
60
+ (defun foo (x) (vec:vec2 x)) ; 编译时解析为 vec|vec2
61
+ ```
62
+
63
+ ### 原理
64
+
65
+ - CL 风格的包系统在**编译期**工作,不影响 FAS4 格式
66
+ - 包限定的函数名通过名混淆(`pkg|name`)编码为平展符号名
67
+ - FAS4 二进制格式完全不变,兼容所有 CAD
68
+ - 反编译器自动还原 `pkg|name` → `pkg:name`
69
+ - 只对 `defun` 名和 `pkg:name` 语法的引用生效,不干扰局部变量
70
+
71
+ ## VLX 容器
72
+
73
+ `atlispcc build-vlx` 将 FAS 文件打包为 VLX 容器,支持独立命名空间:
74
+
75
+ ```sh
76
+ atlispcc build-vlx app.fas lib.fas -o app.vlx --export main setup
77
+ ```
78
+
79
+ - 自动生成 `_VLX` 元数据条目(含 `vl-doc-export` 调用)
80
+ - 加载 VLX 时 CAD 运行时创建隔离命名空间
81
+ - 仅导出的函数可被外部访问
82
+ - VLX 格式基于逆向工程,标记为实验性
83
+
84
+ ## 优化级别
85
+
86
+ | 级别 | Pass 列表 | 说明 |
87
+ |-------|--------|------|
88
+ | `-O0` | 无 | 原始编译,不做优化 |
89
+ | `-O1` | ConstantFold, DeadBranchElim, Peephole | 基本清理:`(+ 1 2)` → `3`、`(if T e1 e2)` → `e1`、`(progn x)` → `x` |
90
+ | `-O2` | O1 + ConstantPropagate, DeadAssignElim | 激进优化:`(setq x 3) (+ x 1)` → `(+ 3 1)`、移除未使用的 `setq` |
91
+ | `-O3` | O2 + CrossFuncConstPropagate, DeadCodeElim, InlineSmallFuncs | 极致优化:内联单次调用函数、跨函数常量替换、删除 `exit`/`quit` 后的死代码 |
92
+
93
+ ### 分析
94
+
95
+ 使用 `--analyze` 配合任何 `-O` 级别查看编译统计:
96
+
97
+ ```sh
98
+ atlispcc compile --analyze -O3 input.lsp output.fas
99
+ ```
100
+
101
+ 输出包含:
102
+ - 函数数、指令数、常量估算数
103
+ - 尾递归函数检测
104
+ - 每个函数的参数数、局部变量、调用关系
105
+ - 开启优化时输出优化前后对比
106
+
107
+ ## 关键文件
108
+
109
+ | 文件 | 作用 |
110
+ |------|------|
111
+ | `fas4_decompiler.py` | 主反编译器:解析 + 反汇编 + 分析 + 生成 |
112
+ | `fas_core.py` | 共享加密/解析/符号工具函数 |
113
+ | `compiler/cli.py` | CLI 入口,子命令分发 |
114
+ | `compiler/parser.py` | Lisp 源码解析器 → AST |
115
+ | `compiler/ast.py` | AST 节点定义(含 Defpackage、InPackage) |
116
+ | `compiler/packages.py` | 包系统:PackageTable、名混淆/还原 |
117
+ | `compiler/symbol_table.py` | 符号表构建与作用域分析(含包解析) |
118
+ | `compiler/codegen.py` | 字节码生成器(AST → 指令流) |
119
+ | `compiler/resource_builder.py` | 常量/符号/函数索引 → 资源段 |
120
+ | `compiler/fas_writer.py` | 指令流 → FAS4 二进制 |
121
+ | `compiler/opt.py` | 优化器:8 个 pass 跨 O1-O3 级别 |
122
+ | `compiler/analyzer.py` | 编译分析与统计 |
123
+ | `compiler/assembler.py` | 反汇编/汇编(.fas ↔ .lasm) |
124
+ | `compiler/linker.py` | 多文件 FAS 链接器 |
125
+ | `compiler/vlx_builder.py` | VLX 容器构建器 |
126
+ | `validate.py` | 生成的 `.lsp` 语法验证器 |
127
+
128
+ ## 安装
129
+
130
+ ```sh
131
+ pip install -e ".[dev]" # 开发模式(含 dev 工具)
132
+ ```
133
+
134
+ ## 测试
135
+
136
+ ```sh
137
+ python -m pytest # 运行所有测试(161 用例)
138
+ python -m pytest tests/ -v # 详细输出
139
+ ```
140
+
141
+ 测试文件在 `tests/` 目录:
142
+ - `test_parser.py` — AST 解析器(19 用例)
143
+ - `test_lexer.py` — 词法分析(11 用例)
144
+ - `test_symbol_table.py` — 符号表/作用域(10 用例)
145
+ - `test_analyzer.py` — 编译分析/尾递归(9 用例)
146
+ - `test_cli.py` — CLI 路径(10 用例)
147
+ - `test_compile_decompile.py` — 编译-反编译往返(28 用例)
148
+ - `test_compiler_output.py` — FAS 输出正确性(12 用例)
149
+ - `test_optimizer.py` — 所有优化 pass(32 用例)
150
+ - `test_linker.py` — 多文件链接(10 用例)
151
+ - `test_assembler.py` — 反汇编/汇编往返(5 用例)
152
+ - `test_disasm.py` — 反汇编健壮性(8 用例)
153
+ - `test_packages.py` — 包系统(4 用例)
154
+ - `test_vlx_builder.py` — VLX 容器(3 用例)
155
+
156
+ ## 文档
157
+
158
+ - `docs/cad-stub-format.md` — AutoCAD 紧凑 stub 格式规范与兼容实现(核心)
159
+ - `docs/development-plan.md` — 开发计划、遗留问题与路线图
160
+ - `docs/fas-format.md` — FAS4 二进制文件格式详解
161
+ - `docs/opcodes.md` — 操作码参考
162
+ - `docs/development.md` — 架构详解、代码规范、调试指南
163
+ - `docs/usage.md` — 用户安装、编译/反编译工作流、工具参考
164
+
165
+ ## 安全
166
+
167
+ 反编译生成的 `.lsp` 应先审查再使用。先在空白图形中测试。某些命令可能会创建、修改或删除图形实体。
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.4
2
+ Name: atlispcc
3
+ Version: 0.1.0
4
+ Summary: FAS4 bytecode decompiler and compiler toolchain for AutoLISP-compatible CAD platforms
5
+ Requires-Python: >=3.8
6
+ Requires-Dist: typing-extensions>=4.5.0
7
+ Provides-Extra: dev
8
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
9
+ Requires-Dist: black>=22.3.0; extra == "dev"
10
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
@@ -0,0 +1,42 @@
1
+ README.md
2
+ fas4_decompiler.py
3
+ fas_core.py
4
+ pyproject.toml
5
+ validate.py
6
+ atlispcc.egg-info/PKG-INFO
7
+ atlispcc.egg-info/SOURCES.txt
8
+ atlispcc.egg-info/dependency_links.txt
9
+ atlispcc.egg-info/entry_points.txt
10
+ atlispcc.egg-info/requires.txt
11
+ atlispcc.egg-info/top_level.txt
12
+ compiler/__init__.py
13
+ compiler/analyzer.py
14
+ compiler/assembler.py
15
+ compiler/ast.py
16
+ compiler/cad_symbols.py
17
+ compiler/cli.py
18
+ compiler/codegen.py
19
+ compiler/fas_writer.py
20
+ compiler/lexer.py
21
+ compiler/linker.py
22
+ compiler/opcodes.py
23
+ compiler/opt.py
24
+ compiler/packages.py
25
+ compiler/parser.py
26
+ compiler/resource_builder.py
27
+ compiler/symbol_table.py
28
+ compiler/vlx_builder.py
29
+ tests/test_analyzer.py
30
+ tests/test_assembler.py
31
+ tests/test_cli.py
32
+ tests/test_compile_decompile.py
33
+ tests/test_compiler_output.py
34
+ tests/test_disasm.py
35
+ tests/test_lexer.py
36
+ tests/test_linker.py
37
+ tests/test_optimizer.py
38
+ tests/test_packages.py
39
+ tests/test_parser.py
40
+ tests/test_symbol_table.py
41
+ tests/test_variadic_args.py
42
+ tests/test_vlx_builder.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ atlispcc = compiler.cli:main
@@ -0,0 +1,6 @@
1
+ typing-extensions>=4.5.0
2
+
3
+ [dev]
4
+ pytest>=7.0.0
5
+ black>=22.3.0
6
+ mypy>=1.0.0
@@ -0,0 +1,4 @@
1
+ compiler
2
+ fas4_decompiler
3
+ fas_core
4
+ validate
File without changes
@@ -0,0 +1,217 @@
1
+ from typing import Dict, List, Set, Tuple, Optional
2
+ from compiler.ast import (
3
+ Node,
4
+ Program,
5
+ Symbol,
6
+ Integer,
7
+ Real,
8
+ String,
9
+ ListExpr,
10
+ QuotedExpr,
11
+ Defun,
12
+ Setq,
13
+ IfNode,
14
+ WhileNode,
15
+ ForeachNode,
16
+ RepeatNode,
17
+ Call,
18
+ Progn,
19
+ LambdaNode,
20
+ Cond,
21
+ )
22
+ from compiler.codegen import CodeGenerator, BytecodeBuffer
23
+ from compiler.symbol_table import SymbolTable
24
+
25
+
26
+ def count_instructions(func: Defun, symtab: SymbolTable) -> int:
27
+ codegen = CodeGenerator(symtab)
28
+ bytecode, _ = codegen.generate_function(func)
29
+ return len(bytecode)
30
+
31
+
32
+ def _collect_call_names(node: Optional[Node], calls: Set[str]):
33
+ if node is None:
34
+ return
35
+ if isinstance(node, Call):
36
+ calls.add(node.name)
37
+ for a in node.args:
38
+ _collect_call_names(a, calls)
39
+ elif isinstance(node, Setq):
40
+ for _, v in node.pairs:
41
+ _collect_call_names(v, calls)
42
+ elif isinstance(node, IfNode):
43
+ _collect_call_names(node.cond, calls)
44
+ if node.then:
45
+ _collect_call_names(node.then, calls)
46
+ if node.else_expr:
47
+ _collect_call_names(node.else_expr, calls)
48
+ elif isinstance(node, WhileNode):
49
+ _collect_call_names(node.cond, calls)
50
+ for b in node.body:
51
+ _collect_call_names(b, calls)
52
+ elif isinstance(node, ForeachNode):
53
+ _collect_call_names(node.var, calls)
54
+ _collect_call_names(node.list_expr, calls)
55
+ for b in node.body:
56
+ _collect_call_names(b, calls)
57
+ elif isinstance(node, RepeatNode):
58
+ _collect_call_names(node.count, calls)
59
+ for b in node.body:
60
+ _collect_call_names(b, calls)
61
+ elif isinstance(node, Progn):
62
+ for f in node.forms:
63
+ _collect_call_names(f, calls)
64
+ elif isinstance(node, Cond):
65
+ for cl in node.clauses:
66
+ for c in cl:
67
+ _collect_call_names(c, calls)
68
+ elif isinstance(node, LambdaNode):
69
+ for b in node.body:
70
+ _collect_call_names(b, calls)
71
+
72
+
73
+ def _is_tail_call(node: Node, func_name: str) -> bool:
74
+ if isinstance(node, Call) and node.name == func_name:
75
+ return True
76
+ if isinstance(node, IfNode):
77
+ if node.then and _is_tail_call(node.then, func_name):
78
+ return True
79
+ if node.else_expr and _is_tail_call(node.else_expr, func_name):
80
+ return True
81
+ return False
82
+ if isinstance(node, Cond):
83
+ for clause in node.clauses:
84
+ if clause:
85
+ tail = clause[-1] if len(clause) > 1 else clause[0]
86
+ if _is_tail_call(tail, func_name):
87
+ return True
88
+ return False
89
+ if isinstance(node, Progn):
90
+ if node.forms:
91
+ return _is_tail_call(node.forms[-1], func_name)
92
+ return False
93
+ return False
94
+
95
+
96
+ def find_tail_recursive(func: Defun) -> bool:
97
+ if not func.body:
98
+ return False
99
+ return _is_tail_call(func.body[-1], func.name)
100
+
101
+
102
+ def estimate_constants(program: Program) -> int:
103
+ count = 0
104
+
105
+ def walk(node: Optional[Node]):
106
+ nonlocal count
107
+ if node is None:
108
+ return
109
+ if isinstance(node, (Integer, Real, String)):
110
+ count += 1
111
+ elif isinstance(node, Call):
112
+ for a in node.args:
113
+ walk(a)
114
+ elif isinstance(node, Setq):
115
+ for _, v in node.pairs:
116
+ walk(v)
117
+ elif isinstance(node, IfNode):
118
+ walk(node.cond)
119
+ if node.then:
120
+ walk(node.then)
121
+ if node.else_expr:
122
+ walk(node.else_expr)
123
+ elif isinstance(node, WhileNode):
124
+ walk(node.cond)
125
+ for b in node.body:
126
+ walk(b)
127
+ elif isinstance(node, ForeachNode):
128
+ walk(node.var)
129
+ walk(node.list_expr)
130
+ for b in node.body:
131
+ walk(b)
132
+ elif isinstance(node, RepeatNode):
133
+ walk(node.count)
134
+ for b in node.body:
135
+ walk(b)
136
+ elif isinstance(node, Progn):
137
+ for f in node.forms:
138
+ walk(f)
139
+ elif isinstance(node, Cond):
140
+ for cl in node.clauses:
141
+ for c in cl:
142
+ walk(c)
143
+ elif isinstance(node, ListExpr):
144
+ for i in node.items:
145
+ walk(i)
146
+ elif isinstance(node, QuotedExpr):
147
+ if node.expr:
148
+ walk(node.expr)
149
+ elif isinstance(node, LambdaNode):
150
+ for b in node.body:
151
+ walk(b)
152
+
153
+ for f in program.forms:
154
+ if isinstance(f, Defun):
155
+ for e in f.body:
156
+ walk(e)
157
+ else:
158
+ walk(f)
159
+ return count
160
+
161
+
162
+ def analyze(program: Program) -> Dict:
163
+ symtab = SymbolTable()
164
+ symtab.collect_symbols(program)
165
+
166
+ defuns = [f for f in program.forms if isinstance(f, Defun)]
167
+ other = [f for f in program.forms if not isinstance(f, Defun)]
168
+
169
+ func_stats = []
170
+ total_instructions = 0
171
+ tail_recursive = []
172
+
173
+ for f in defuns:
174
+ inst_count = count_instructions(f, symtab)
175
+ total_instructions += inst_count
176
+ calls: Set[str] = set()
177
+ for e in f.body:
178
+ _collect_call_names(e, calls)
179
+
180
+ is_tail_rec = find_tail_recursive(f)
181
+ if is_tail_rec:
182
+ tail_recursive.append(f.name)
183
+
184
+ func_stats.append(
185
+ {
186
+ "name": f.name,
187
+ "args": len(f.args),
188
+ "locals": len(f.locals),
189
+ "instructions": inst_count,
190
+ "calls": sorted(calls) if calls else [],
191
+ "tail_recursive": is_tail_rec,
192
+ }
193
+ )
194
+
195
+ all_calls: Set[str] = set()
196
+ for f in defuns:
197
+ for e in f.body:
198
+ _collect_call_names(e, all_calls)
199
+ for e in other:
200
+ _collect_call_names(e, all_calls)
201
+
202
+ builtin_calls = {c for c in all_calls if c[0].islower() and c != "setq"}
203
+ user_funcs = {f.name for f in defuns}
204
+ extern_calls = builtin_calls - user_funcs
205
+
206
+ return {
207
+ "functions": len(defuns),
208
+ "top_level_forms": len(other),
209
+ "total_instructions": total_instructions,
210
+ "constants_estimate": estimate_constants(program),
211
+ "symbol_table_entries": len(symtab.entries),
212
+ "function_offsets": len(symtab.function_offsets),
213
+ "locals": sum(len(f.locals) for f in defuns),
214
+ "tail_recursive_functions": tail_recursive,
215
+ "functions_detail": func_stats,
216
+ "builtin_calls": sorted(extern_calls),
217
+ }