Skip to content

Commit 351fd42

Browse files
committed
feat: implement mdr frontend semantics and examples
1 parent e54fd54 commit 351fd42

25 files changed

Lines changed: 4565 additions & 11 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ dist
9191
examples-temp
9292
*.timestamp-*.mjs
9393
/.vitepress/
94+
code/项目/Mdr_Lang/compiler_c/build/
9495

9596
# linter&formatter
9697
.cpp-linter_cache
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Mdr Lang 编译器(起步版)
2+
3+
这个目录提供了一个可运行的前端原型,当前阶段完成了:
4+
5+
1. 词法分析(Token 流)。
6+
2. 语法分析(AST)。
7+
3. 命令行入口。
8+
9+
## 目录结构
10+
11+
```text
12+
compiler/
13+
cli.mjs
14+
src/
15+
compiler.mjs
16+
error.mjs
17+
lexer.mjs
18+
parser.mjs
19+
token.mjs
20+
tests/
21+
smoke.mjs
22+
```
23+
24+
## 用法
25+
26+
```bash
27+
node code/项目/Mdr_Lang/compiler/cli.mjs docs/教程/正文/项目/Mdr_Lang/example/vec2.mdr
28+
```
29+
30+
输出 AST 到文件:
31+
32+
```bash
33+
node code/项目/Mdr_Lang/compiler/cli.mjs docs/教程/正文/项目/Mdr_Lang/example/range_step.mdr --out temp/range_step.ast.json
34+
```
35+
36+
同时打印 Token:
37+
38+
```bash
39+
node code/项目/Mdr_Lang/compiler/cli.mjs docs/教程/正文/项目/Mdr_Lang/example/range_step.mdr --tokens
40+
```
41+
42+
## 已覆盖的核心语法
43+
44+
1. 顶层:`use``struct``function`
45+
2. 语句:`let` / `const``if` / `else``while``loop``for``return``break``continue`、赋值语句、表达式语句。
46+
3. 表达式:逻辑运算、比较、四则、前缀一元、成员访问、函数调用、元组字面量、数组字面量、`this``null`
47+
4. 范围表达式:`a..b``a...b``a..b..s``a...b..s`
48+
49+
## 下一步建议
50+
51+
1. 在 AST 之后加入语义分析(名称解析、类型检查)。
52+
2. 设计中间表示(IR)。
53+
3. 先实现解释执行,再接入机器码后端或 C 后端。
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#!/usr/bin/env node
2+
import fs from "node:fs";
3+
import path from "node:path";
4+
import process from "node:process";
5+
6+
import { compileSource } from "./src/compiler.mjs";
7+
import { MdrCompileError } from "./src/error.mjs";
8+
9+
const args = process.argv.slice(2);
10+
const options = {
11+
inputPath: "",
12+
astOutPath: "",
13+
dumpTokens: false,
14+
};
15+
16+
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
17+
printUsage();
18+
process.exit(args.length === 0 ? 1 : 0);
19+
}
20+
21+
for (let i = 0; i < args.length; i += 1) {
22+
const arg = args[i];
23+
if (arg === "--tokens") {
24+
options.dumpTokens = true;
25+
continue;
26+
}
27+
if (arg === "--out") {
28+
const next = args[i + 1];
29+
if (next == null) {
30+
console.error("missing output path after --out");
31+
process.exit(1);
32+
}
33+
options.astOutPath = next;
34+
i += 1;
35+
continue;
36+
}
37+
if (arg.startsWith("-")) {
38+
console.error(`unknown option: ${arg}`);
39+
process.exit(1);
40+
}
41+
if (options.inputPath !== "") {
42+
console.error("only one input file is supported");
43+
process.exit(1);
44+
}
45+
options.inputPath = arg;
46+
}
47+
48+
if (options.inputPath === "") {
49+
console.error("missing input file");
50+
printUsage();
51+
process.exit(1);
52+
}
53+
54+
const absoluteInput = path.resolve(options.inputPath);
55+
const source = fs.readFileSync(absoluteInput, "utf8");
56+
57+
try {
58+
const { tokens, ast } = compileSource(source, absoluteInput);
59+
const astJson = JSON.stringify(ast, null, 2);
60+
if (options.astOutPath !== "") {
61+
const outPath = path.resolve(options.astOutPath);
62+
fs.mkdirSync(path.dirname(outPath), { recursive: true });
63+
fs.writeFileSync(outPath, `${astJson}\n`, "utf8");
64+
} else {
65+
process.stdout.write(`${astJson}\n`);
66+
}
67+
68+
if (options.dumpTokens) {
69+
process.stdout.write(`${JSON.stringify(tokens, null, 2)}\n`);
70+
}
71+
} catch (error) {
72+
if (error instanceof MdrCompileError) {
73+
printCompileError(error, source);
74+
process.exit(1);
75+
}
76+
throw error;
77+
}
78+
79+
function printUsage() {
80+
process.stdout.write("Usage: node cli.mjs <input.mdr> [--out <file>] [--tokens]\n");
81+
}
82+
83+
/**
84+
* @param {MdrCompileError} error
85+
* @param {string} source
86+
*/
87+
function printCompileError(error, source) {
88+
const lines = source.split(/\r?\n/u);
89+
const lineText = lines[error.line - 1] ?? "";
90+
const pointer = `${" ".repeat(Math.max(error.column - 1, 0))}^`;
91+
92+
console.error(`${error.filePath}:${error.line}:${error.column}: ${error.message}`);
93+
console.error(lineText);
94+
console.error(pointer);
95+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { tokenize } from "./lexer.mjs";
2+
import { parse } from "./parser.mjs";
3+
4+
/**
5+
* @param {string} source
6+
* @param {string} filePath
7+
*/
8+
export function compileSource(source, filePath) {
9+
const tokens = tokenize(source, filePath);
10+
const ast = parse(tokens, filePath);
11+
return { tokens, ast };
12+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
export class MdrCompileError extends Error {
2+
/**
3+
* @param {string} message
4+
* @param {{
5+
* filePath: string;
6+
* line: number;
7+
* column: number;
8+
* }} options
9+
*/
10+
constructor(message, options) {
11+
super(message);
12+
this.name = "MdrCompileError";
13+
this.filePath = options.filePath;
14+
this.line = options.line;
15+
this.column = options.column;
16+
}
17+
}

0 commit comments

Comments
 (0)