Skip to content

Commit 6bb4bfa

Browse files
kov repl (interactive compile+eval), kov import-c (C header to extern decls)
1 parent d55d76a commit 6bb4bfa

3 files changed

Lines changed: 271 additions & 0 deletions

File tree

src/codegen/cheader.rs

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
// minimal C header parser — extracts function declarations
2+
// generates extern "C" fn declarations in Kov syntax
3+
4+
pub fn parse_header(content: &str) -> Vec<String> {
5+
let mut decls = Vec::new();
6+
7+
for line in content.lines() {
8+
let line = line.trim();
9+
// skip preprocessor, comments, empty
10+
if line.starts_with('#') || line.starts_with("//") || line.is_empty() {
11+
continue;
12+
}
13+
// skip typedefs, structs for now
14+
if line.starts_with("typedef") || line.starts_with("struct") {
15+
continue;
16+
}
17+
// try to parse: return_type name(params);
18+
if let Some(decl) = try_parse_fn_decl(line) {
19+
decls.push(decl);
20+
}
21+
}
22+
23+
decls
24+
}
25+
26+
fn try_parse_fn_decl(line: &str) -> Option<String> {
27+
let line = line.trim_end_matches(';').trim();
28+
let paren = line.find('(')?;
29+
let before_paren = &line[..paren].trim();
30+
let params_str = &line[paren + 1..line.rfind(')')?];
31+
32+
// split "void HAL_GPIO_Write" into return type + name
33+
let last_space = before_paren.rfind(' ')?;
34+
let ret_type = before_paren[..last_space].trim();
35+
let name = before_paren[last_space + 1..].trim();
36+
37+
// skip if name starts with _ (internal)
38+
if name.starts_with("__") {
39+
return None;
40+
}
41+
42+
// convert C types to Kov types
43+
let kov_ret = c_type_to_kov(ret_type);
44+
let kov_params = parse_c_params(params_str);
45+
46+
let params_kov: Vec<String> = kov_params
47+
.iter()
48+
.enumerate()
49+
.map(|(i, ty)| format!("arg{}: {}", i, ty))
50+
.collect();
51+
52+
let ret_str = if kov_ret == "void" {
53+
String::new()
54+
} else {
55+
format!(" {}", kov_ret)
56+
};
57+
58+
Some(format!(
59+
"extern \"C\" fn {}({}){};",
60+
name,
61+
params_kov.join(", "),
62+
ret_str
63+
))
64+
}
65+
66+
fn parse_c_params(params: &str) -> Vec<String> {
67+
if params.trim() == "void" || params.trim().is_empty() {
68+
return Vec::new();
69+
}
70+
params
71+
.split(',')
72+
.map(|p| {
73+
let p = p.trim();
74+
// extract type (everything before the last word which is the param name)
75+
let parts: Vec<&str> = p.split_whitespace().collect();
76+
if parts.len() >= 2 {
77+
c_type_to_kov(&parts[..parts.len() - 1].join(" "))
78+
} else if parts.len() == 1 {
79+
c_type_to_kov(parts[0])
80+
} else {
81+
"u32".into()
82+
}
83+
})
84+
.collect()
85+
}
86+
87+
fn c_type_to_kov(c_type: &str) -> String {
88+
let t = c_type
89+
.trim()
90+
.replace("const ", "")
91+
.replace("volatile ", "")
92+
.replace("unsigned ", "u")
93+
.replace("signed ", "i");
94+
match t.trim() {
95+
"void" => "void".into(),
96+
"int" | "iint" => "i32".into(),
97+
"uint" | "uint32_t" | "ulong" => "u32".into(),
98+
"uint8_t" | "uchar" => "u8".into(),
99+
"uint16_t" | "ushort" => "u16".into(),
100+
"uint64_t" => "u64".into(),
101+
"int8_t" | "ichar" | "char" => "i8".into(),
102+
"int16_t" | "ishort" | "short" => "i16".into(),
103+
"int32_t" | "ilong" | "long" => "i32".into(),
104+
"int64_t" => "i64".into(),
105+
"bool" | "_Bool" => "bool".into(),
106+
"float" | "double" => "u32".into(), // no float support yet
107+
s if s.contains('*') => "u32".into(), // pointers → u32 on rv32
108+
_ => "u32".into(),
109+
}
110+
}
111+
112+
pub fn generate_kov(decls: &[String]) -> String {
113+
let mut out = String::from("// generated from C header\n\n");
114+
for d in decls {
115+
out.push_str(d);
116+
out.push('\n');
117+
}
118+
out
119+
}
120+
121+
#[cfg(test)]
122+
mod tests {
123+
use super::*;
124+
125+
#[test]
126+
fn parse_simple_header() {
127+
let header = r#"
128+
#include <stdint.h>
129+
130+
void HAL_GPIO_WritePin(uint32_t port, uint32_t pin, uint32_t state);
131+
uint32_t HAL_GPIO_ReadPin(uint32_t port, uint32_t pin);
132+
void HAL_Delay(uint32_t ms);
133+
"#;
134+
let decls = parse_header(header);
135+
assert_eq!(decls.len(), 3);
136+
assert!(decls[0].contains("HAL_GPIO_WritePin"));
137+
assert!(decls[1].contains("HAL_GPIO_ReadPin"));
138+
assert!(decls[1].contains("u32")); // return type
139+
assert!(decls[2].contains("HAL_Delay"));
140+
}
141+
142+
#[test]
143+
fn c_types_converted() {
144+
assert_eq!(c_type_to_kov("uint32_t"), "u32");
145+
assert_eq!(c_type_to_kov("int"), "i32");
146+
assert_eq!(c_type_to_kov("void"), "void");
147+
assert_eq!(c_type_to_kov("uint8_t"), "u8");
148+
assert_eq!(c_type_to_kov("char*"), "u32");
149+
}
150+
}

src/codegen/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
pub mod alloc;
22
pub mod arm;
33
pub mod builtins;
4+
pub mod cheader;
45
pub mod compress;
56
pub mod defmt;
67
pub mod disasm;

src/main.rs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ fn main() {
3939
eprintln!(" boards list supported boards");
4040
eprintln!(" svd <file.svd> [--name X] generate board def from SVD");
4141
eprintln!(" check <file.kov> type check only");
42+
eprintln!(" repl interactive compile + run");
4243
eprintln!(" lsp start language server");
4344
eprintln!(" lex <file.kov> dump tokens");
4445
eprintln!();
@@ -240,6 +241,15 @@ fn main() {
240241
funcs.len()
241242
);
242243
}
244+
"import-c" => {
245+
if args.len() < 3 {
246+
eprintln!("usage: kov import-c <header.h>");
247+
process::exit(1);
248+
}
249+
let content = read_file(&args[2]);
250+
let decls = codegen::cheader::parse_header(&content);
251+
println!("{}", codegen::cheader::generate_kov(&decls));
252+
}
243253
"svd" => {
244254
if args.len() < 3 {
245255
eprintln!("usage: kov svd <file.svd> [--name <board>]");
@@ -250,6 +260,7 @@ fn main() {
250260
let peripherals = codegen::svd::parse_svd(&xml);
251261
println!("{}", codegen::svd::generate_kov(&peripherals, &name));
252262
}
263+
"repl" => cmd_repl(),
253264
"lsp" => lsp::run_lsp(),
254265
"check" => cmd_check(&args),
255266
_ => {
@@ -758,6 +769,115 @@ fn cmd_flash(args: &[String]) {
758769
let _ = std::fs::remove_file(&elf_path);
759770
}
760771

772+
fn cmd_repl() {
773+
use std::io::{self, BufRead, Write};
774+
775+
eprintln!("kov repl v0.1.0 — type expressions, see results");
776+
eprintln!(" expressions are wrapped in fn main() {{ return <expr>; }}");
777+
eprintln!(" type :q to quit, :asm to show assembly");
778+
eprintln!();
779+
780+
let stdin = io::stdin();
781+
let stdout = io::stdout();
782+
let mut show_asm = false;
783+
784+
loop {
785+
print!("kov> ");
786+
let _ = stdout.lock().flush();
787+
788+
let mut line = String::new();
789+
if stdin.lock().read_line(&mut line).is_err() || line.is_empty() {
790+
break;
791+
}
792+
let line = line.trim();
793+
if line.is_empty() {
794+
continue;
795+
}
796+
if line == ":q" || line == ":quit" {
797+
break;
798+
}
799+
if line == ":asm" {
800+
show_asm = !show_asm;
801+
eprintln!(" asm mode: {}", if show_asm { "on" } else { "off" });
802+
continue;
803+
}
804+
805+
// wrap as a function that returns the expression
806+
let source = if line.contains("fn ") || line.contains("let ") || line.contains("board ") {
807+
line.to_string()
808+
} else {
809+
format!("fn __repl__() u32 {{ return {}; }}", line)
810+
};
811+
812+
// compile
813+
let tokens = match lexer::Lexer::tokenize(&source) {
814+
Ok(t) => t,
815+
Err(e) => {
816+
eprintln!(" error: {e}");
817+
continue;
818+
}
819+
};
820+
let mut program = match parser::Parser::new(tokens).parse() {
821+
Ok(p) => p,
822+
Err(errors) => {
823+
for e in &errors {
824+
eprintln!(" error: {}", e.message);
825+
}
826+
continue;
827+
}
828+
};
829+
parser::monomorph::monomorphize(&mut program);
830+
831+
let mut ir_result = ir::lower::Lowering::lower(&program);
832+
ir::opt::inline_functions(&mut ir_result.functions);
833+
for func in &mut ir_result.functions {
834+
ir::opt::optimize(func);
835+
}
836+
837+
// try const eval first
838+
if let Some(func) = ir_result.functions.iter().find(|f| f.name == "__repl__") {
839+
if let Some(val) = ir::consteval::eval(func, &[]) {
840+
eprintln!(" = {} (0x{:X})", val, val as u32);
841+
if show_asm {
842+
let mut cg = codegen::CodeGen::new();
843+
cg.gen_function(func);
844+
if let Ok(code) = cg.finish() {
845+
let labels = cg.emitter.labels.clone();
846+
eprintln!("{}", codegen::disasm::disassemble(&code, 0, &labels));
847+
}
848+
}
849+
continue;
850+
}
851+
}
852+
853+
// fall back to emulator
854+
let mut cg = codegen::CodeGen::new_with_globals(0x2000_0000, &ir_result.globals);
855+
for func in &ir_result.functions {
856+
cg.gen_function(func);
857+
}
858+
let labels = cg.emitter.labels.clone();
859+
match cg.finish() {
860+
Ok(code) => {
861+
if show_asm {
862+
eprintln!(
863+
"{}",
864+
codegen::disasm::disassemble(&code, 0x0800_0000, &labels)
865+
);
866+
}
867+
let mut cpu = emu::Cpu::with_memory(0x0800_0000, 0x0800_0000, 0x2000_0000);
868+
cpu.mem.load_flash(&code);
869+
cpu.regs[2] = 0x2000_8000;
870+
cpu.run(10_000);
871+
eprintln!(
872+
" = {} (0x{:X}) [{} cycles]",
873+
cpu.regs[10] as i32, cpu.regs[10], cpu.cycles
874+
);
875+
}
876+
Err(e) => eprintln!(" codegen error: {e}"),
877+
}
878+
}
879+
}
880+
761881
fn cmd_check(args: &[String]) {
762882
if args.len() < 3 {
763883
eprintln!("usage: kov check <file.kov>");

0 commit comments

Comments
 (0)