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.
- atlispcc-0.1.0/PKG-INFO +10 -0
- atlispcc-0.1.0/README.md +167 -0
- atlispcc-0.1.0/atlispcc.egg-info/PKG-INFO +10 -0
- atlispcc-0.1.0/atlispcc.egg-info/SOURCES.txt +42 -0
- atlispcc-0.1.0/atlispcc.egg-info/dependency_links.txt +1 -0
- atlispcc-0.1.0/atlispcc.egg-info/entry_points.txt +2 -0
- atlispcc-0.1.0/atlispcc.egg-info/requires.txt +6 -0
- atlispcc-0.1.0/atlispcc.egg-info/top_level.txt +4 -0
- atlispcc-0.1.0/compiler/__init__.py +0 -0
- atlispcc-0.1.0/compiler/analyzer.py +217 -0
- atlispcc-0.1.0/compiler/assembler.py +265 -0
- atlispcc-0.1.0/compiler/ast.py +120 -0
- atlispcc-0.1.0/compiler/cad_symbols.py +145 -0
- atlispcc-0.1.0/compiler/cli.py +434 -0
- atlispcc-0.1.0/compiler/codegen.py +767 -0
- atlispcc-0.1.0/compiler/fas_writer.py +123 -0
- atlispcc-0.1.0/compiler/lexer.py +102 -0
- atlispcc-0.1.0/compiler/linker.py +468 -0
- atlispcc-0.1.0/compiler/opcodes.py +134 -0
- atlispcc-0.1.0/compiler/opt.py +1376 -0
- atlispcc-0.1.0/compiler/packages.py +49 -0
- atlispcc-0.1.0/compiler/parser.py +275 -0
- atlispcc-0.1.0/compiler/resource_builder.py +263 -0
- atlispcc-0.1.0/compiler/symbol_table.py +217 -0
- atlispcc-0.1.0/compiler/vlx_builder.py +92 -0
- atlispcc-0.1.0/fas4_decompiler.py +2722 -0
- atlispcc-0.1.0/fas_core.py +699 -0
- atlispcc-0.1.0/pyproject.toml +41 -0
- atlispcc-0.1.0/setup.cfg +4 -0
- atlispcc-0.1.0/tests/test_analyzer.py +80 -0
- atlispcc-0.1.0/tests/test_assembler.py +168 -0
- atlispcc-0.1.0/tests/test_cli.py +146 -0
- atlispcc-0.1.0/tests/test_compile_decompile.py +419 -0
- atlispcc-0.1.0/tests/test_compiler_output.py +263 -0
- atlispcc-0.1.0/tests/test_disasm.py +130 -0
- atlispcc-0.1.0/tests/test_lexer.py +88 -0
- atlispcc-0.1.0/tests/test_linker.py +237 -0
- atlispcc-0.1.0/tests/test_optimizer.py +405 -0
- atlispcc-0.1.0/tests/test_packages.py +110 -0
- atlispcc-0.1.0/tests/test_parser.py +215 -0
- atlispcc-0.1.0/tests/test_symbol_table.py +99 -0
- atlispcc-0.1.0/tests/test_variadic_args.py +56 -0
- atlispcc-0.1.0/tests/test_vlx_builder.py +92 -0
- atlispcc-0.1.0/validate.py +240 -0
atlispcc-0.1.0/PKG-INFO
ADDED
|
@@ -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"
|
atlispcc-0.1.0/README.md
ADDED
|
@@ -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 @@
|
|
|
1
|
+
|
|
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
|
+
}
|