CYaRonC -- CYaRon! 语言到LLVM IR的编译器
项目地址:https://github.com/yuygfgg/CYaRonC
下面的内容主要是对README的翻译。
CYaRonC
CYaRonC 是 CYaRon! 编程语言的编译器。它能够将 CYaRon! 源代码编译成 LLVM 中间表示(IR)。
功能特性
CYaRonC 实现了一个 CYaRon! 编程语言的扩展版本,该语言最初定义于 P3695 。
- 静态类型系统,支持
int和float类型。 - 支持自定义索引范围(包含边界)的一维数组。
- 标准输入输出操作。
- C 风格的表达式,支持算术、比较和位运算符。
- 独特的
ihu、hor和while控制流语句。 - 生成 LLVM IR,可以通过 JIT 编译器直接执行,或编译为原生可执行文件。
- 支持使用
-g标志生成调试信息,以便与 GDB 或 LLDB 等调试器配合使用。
扩展语法 (与原题相比)
此实现为原版 CYaRon! 语言增加了几项新功能:
-
数据类型:
- 增加了
float类型,用于表示 64 位浮点数(即 C 语言的double)。 - 数组元素可以是
float类型 (例如,array[float, 1..10])。
- 增加了
-
运算符与表达式:
- 更多算术运算符: 增加了
*(乘法)、/(除法) 和%(取模)。 - 位运算符: 增加了
&(按位与)、|(按位或) 和^(按位异或),仅用于整数。 - 一元运算符: 支持一元
+和-(例如,:set x, -5)。 - 运算符优先级: 采用了标准的 C 风格运算符优先级规则。
- 括号: 可以使用
()来强制指定运算顺序。 - 复杂的数组索引: 数组索引可以是任意合法的整数表达式 (例如,
arr[my_array[a*2 + b] + 1])。 - 类型提升: 在混合类型的表达式中,整数会自动提升为浮点数。
- 更宽松的空白字符规则: 不再强制要求四空格缩进。
- 更多算术运算符: 增加了
-
语句:
:input: 一个新的语句,用于从标准输入读取一个数字并存入变量。
-
通用:
- 注释: 以
#开头的行被视为注释。 - 变量名: 标识符可以包含字母、数字和下划线 (
_),但不能以数字开头。
- 注释: 以
构建编译器
您需要预先安装 cmake、一个兼容 C++20 的编译器(如 clang 或 gcc)以及 llvm。
# 创建构建目录
mkdir -p build && cd build
# 配置并构建项目
cmake -DCMAKE_BUILD_TYPE=Release ..
make
编译器用法
1. 编译到 LLVM IR
使用 cyaronc 可执行文件将 .cyaron 源文件编译成 .ll 格式的 LLVM IR 文件。
# 语法
./cyaronc [-g] <input.cyaron> <output.ll>
# 示例
./cyaronc example/segment_tree.cyaron output.ll
# 带调试符号的示例
./cyaronc -g example/segment_tree.cyaron output.ll
- 可选的
-g标志会向 LLVM IR 中添加调试信息。
2. 执行代码
您可以通过多种方式运行生成的 LLVM IR:
# 1. 使用 lli 直接解释执行 LLVM IR
lli output.ll
# 2. 使用 clang 将 LLVM IR 编译为原生可执行文件
clang output.ll -o my_program
# 运行可执行文件
./my_program
# 3. 编译时附带调试符号,并使用调试器
clang -g output.ll -o my_program_debug
lldb ./my_program_debug
语言语法
注释
注释以 # 开始,并持续到行尾。
# 这是一行注释。
变量
所有变量必须在文件顶部的 {vars ...} 代码块中声明。变量默认初始化为零。
{ vars
my_int: int
my_float: float
my_array: array[int, 1..10]
}
类型
int: 64 位有符号整数,相当于 C 语言的long long。float: 64 位浮点数,相当于 C 语言的double。array[<type>, <start>..<end>]: 一维数组,元素类型为int或float,其索引范围是从<start>到<end>的闭区间。
语句
输出: :yosoro
将一个表达式的值输出到标准输出,并后跟一个空格。
:yosoro 123 # 输出 "123 "
:yosoro my_int + 5 # 输出表达式的计算结果和一个空格
赋值: :set
为一个变量或数组元素赋值。
:set my_int, 100
:set my_array[1], 200
:set my_int, my_int + my_array[1]
输入: :input
从标准输入读取一个数字,并存入一个变量或数组元素。
:input my_int
:input my_array[5]
控制流
控制流语句使用 {...} 定义的代码块结构。
比较运算符
以下运算符用于所有控制流语句:
lt: 小于 (<)gt: 大于 (>)le: 小于等于 (<=)ge: 大于等于 (>=)eq: 等于 (==)neq: 不等于 (!=)
If 语句: {ihu ...}
ihu 语句在条件为真时执行一个代码块。该语句没有 else 分支。
# 语法: {ihu <op>, <expr1>, <expr2> ... }
{ihu eq, my_int, 10
:yosoro 1
}
For 循环: {hor ...}
hor 语句创建一个在闭区间范围内进行迭代的循环。循环变量必须是整数。
循环结束后,循环变量的值会被设置为结束值。
# 语法: {hor <var>, <start_expr>, <end_expr> ... }
# 从 start_expr 循环到 end_expr (包含两者)。
{hor i, 1, 5
:yosoro i # 输出 "1 2 3 4 5 "
}
While 循环: {while ...}
while 语句在条件为真时,会持续执行一个代码块。
# 语法: {while <op>, <expr1>, <expr2> ... }
:set i, 1
{while le, i, 3
:yosoro i # 输出 "1 2 3 "
:set i, i + 1
}
表达式
运算符
- 算术运算符:
+,-,*,/,%(取模) - 位运算符:
|,&,^(仅对整数操作)
类型提升
在包含 int 和 float 类型的表达式中,整数会自动提升为浮点数进行计算,其结果也将是 float 类型。
:yosoro (5 + 0.5) # 输出 "5.500000 "
优先级
使用括号 () 对表达式进行分组,以强制指定运算顺序。
:yosoro (2 + 3) * 4 # 输出 "20 "
示例
下面的代码是P3372 【模板】线段树 1 的CYaRon! 语实现。
此代码也可以在github仓库的example目录下找到。
{ vars
n:int
m:int
i:int
op:int
x:int
y:int
k:int
res:int
N:int
logN:int
l:int
r:int
l0:int
r0:int
p:int
s:int
p_l:int
p_r:int
cur_p:int
cur_l:int
cur_r:int
a:array[int, 1..100001]
tree:array[int, 1..530000]
lazy:array[int, 1..530000]
len:array[int, 1..530000]
pow2:array[int, 0..18]
}
# --- Initialization ---
:input n
:input m
:set pow2[0], 1
{ hor i, 1, 18
:set pow2[i], pow2[i-1] * 2
}
:set N, 1
:set logN, 0
{ while lt, N, n
:set N, N * 2
:set logN, logN + 1
}
:set len[1], N
:set i, 1
{ while lt, i, N
:set len[2*i], len[i] / 2
:set len[2*i+1], len[i] / 2
:set i, i + 1
}
{ hor i, 1, n
:input a[i]
}
# --- Build tree ---
{ hor i, 1, n
:set tree[N+i-1], a[i]
}
:set i, N - 1
{ while ge, i, 1
:set tree[i], tree[2*i] + tree[2*i+1]
:set i, i - 1
}
# --- Main loop for m operations ---
{ hor i, 1, m
:input op
{ ihu eq, op, 1 # Update
:input x
:input y
:input k
:set l, x + N - 1
:set r, y + N - 1
:set l0, l
:set r0, r
# Push down from roots to leaves
:set s, logN
{ while ge, s, 1
:set p_l, l / pow2[s]
:set p_r, r / pow2[s]
# push(p_l)
{ ihu neq, lazy[p_l], 0
:set tree[2*p_l], tree[2*p_l] + lazy[p_l] * len[2*p_l]
:set lazy[2*p_l], lazy[2*p_l] + lazy[p_l]
:set tree[2*p_l+1], tree[2*p_l+1] + lazy[p_l] * len[2*p_l+1]
:set lazy[2*p_l+1], lazy[2*p_l+1] + lazy[p_l]
:set lazy[p_l], 0
}
# push(p_r) only if different
{ ihu neq, p_l, p_r
{ ihu neq, lazy[p_r], 0
:set tree[2*p_r], tree[2*p_r] + lazy[p_r] * len[2*p_r]
:set lazy[2*p_r], lazy[2*p_r] + lazy[p_r]
:set tree[2*p_r+1], tree[2*p_r+1] + lazy[p_r] * len[2*p_r+1]
:set lazy[2*p_r+1], lazy[2*p_r+1] + lazy[p_r]
:set lazy[p_r], 0
}
}
:set s, s - 1
}
# Apply updates to interval boundaries
:set cur_l, l
:set cur_r, r
{ while le, cur_l, cur_r
{ ihu eq, cur_l & 1, 1 # if cur_l is a right child
:set tree[cur_l], tree[cur_l] + k * len[cur_l]
{ ihu lt, cur_l, N # only non-leaf nodes have lazy tags
:set lazy[cur_l], lazy[cur_l] + k
}
:set cur_l, cur_l + 1
}
{ ihu eq, cur_r & 1, 0 # if cur_r is a left child
:set tree[cur_r], tree[cur_r] + k * len[cur_r]
{ ihu lt, cur_r, N
:set lazy[cur_r], lazy[cur_r] + k
}
:set cur_r, cur_r - 1
}
:set cur_l, cur_l / 2
:set cur_r, cur_r / 2
}
# Pull up sums from leaves to roots
:set cur_p, l0 / 2
{ while ge, cur_p, 1
:set tree[cur_p], tree[2*cur_p] + tree[2*cur_p+1] + lazy[cur_p] * len[cur_p]
:set cur_p, cur_p / 2
}
:set cur_p, r0 / 2
{ while ge, cur_p, 1
:set tree[cur_p], tree[2*cur_p] + tree[2*cur_p+1] + lazy[cur_p] * len[cur_p]
:set cur_p, cur_p / 2
}
}
{ ihu eq, op, 2 # Query
:input x
:input y
:set l, x + N - 1
:set r, y + N - 1
:set res, 0
# Push down from roots to leaves
:set s, logN
{ while ge, s, 1
:set p_l, l / pow2[s]
:set p_r, r / pow2[s]
# push(p_l)
{ ihu neq, lazy[p_l], 0
:set tree[2*p_l], tree[2*p_l] + lazy[p_l] * len[2*p_l]
:set lazy[2*p_l], lazy[2*p_l] + lazy[p_l]
:set tree[2*p_l+1], tree[2*p_l+1] + lazy[p_l] * len[2*p_l+1]
:set lazy[2*p_l+1], lazy[2*p_l+1] + lazy[p_l]
:set lazy[p_l], 0
}
# push(p_r) only if different
{ ihu neq, p_l, p_r
{ ihu neq, lazy[p_r], 0
:set tree[2*p_r], tree[2*p_r] + lazy[p_r] * len[2*p_r]
:set lazy[2*p_r], lazy[2*p_r] + lazy[p_r]
:set tree[2*p_r+1], tree[2*p_r+1] + lazy[p_r] * len[2*p_r+1]
:set lazy[2*p_r+1], lazy[2*p_r+1] + lazy[p_r]
:set lazy[p_r], 0
}
}
:set s, s - 1
}
# Query intervals
:set cur_l, l
:set cur_r, r
{ while le, cur_l, cur_r
{ ihu eq, cur_l & 1, 1 # if cur_l is a right child
:set res, res + tree[cur_l]
:set cur_l, cur_l + 1
}
{ ihu eq, cur_r & 1, 0 # if cur_r is a left child
:set res, res + tree[cur_r]
:set cur_r, cur_r - 1
}
:set cur_l, cur_l / 2
:set cur_r, cur_r / 2
}
:yosoro res
}
}