1mod go;
18mod rust;
19mod swift;
20
21pub use go::to_go;
22pub use rust::to_rust;
23pub use swift::to_swift;
24
25use std::collections::HashMap;
26
27use crate::ast::*;
28use crate::diagnostic::Span;
29
30#[derive(Clone, PartialEq)]
35pub(crate) enum Ty {
36 Int,
37 Float,
38 Str,
39 Bool,
40 Array(Box<Ty>),
41 User(String),
42 Option(Box<Ty>),
43 Result(Box<Ty>, Box<Ty>),
44 Range,
45 Unit,
46 Unknown,
47}
48
49impl Ty {
50 fn has_unknown(&self) -> bool {
51 match self {
52 Ty::Unknown => true,
53 Ty::Array(t) | Ty::Option(t) => t.has_unknown(),
54 Ty::Result(a, b) => a.has_unknown() || b.has_unknown(),
55 _ => false,
56 }
57 }
58
59 fn has_int(&self) -> bool {
63 match self {
64 Ty::Int => true,
65 Ty::Array(t) | Ty::Option(t) => t.has_int(),
66 Ty::Result(a, b) => a.has_int() || b.has_int(),
67 _ => false,
68 }
69 }
70
71 fn is_scalar(&self) -> bool {
73 matches!(self, Ty::Int | Ty::Float | Ty::Str | Ty::Bool)
74 }
75}
76
77fn ty_from_ann(a: &TypeAnn) -> Ty {
80 match &a.kind {
81 TypeKind::Named(n) => match n.as_str() {
82 "int" => Ty::Int,
83 "float" => Ty::Float,
84 "string" => Ty::Str,
85 "bool" => Ty::Bool,
86 "Unit" => Ty::Unit,
87 _ => Ty::User(n.clone()),
88 },
89 TypeKind::Array(inner) => Ty::Array(Box::new(ty_from_ann(inner))),
90 TypeKind::Generic(name, args) => match (name.as_str(), args.as_slice()) {
91 ("Option", [t]) => Ty::Option(Box::new(ty_from_ann(t))),
92 ("Result", [a, b]) => Ty::Result(Box::new(ty_from_ann(a)), Box::new(ty_from_ann(b))),
93 _ => Ty::Unknown,
94 },
95 }
96}
97
98pub(crate) struct Env {
101 structs: HashMap<String, Vec<FieldDef>>,
102 enums: HashMap<String, Vec<VariantDef>>,
103 funcs: HashMap<String, (Vec<Param>, Option<TypeAnn>)>,
104}
105
106pub(crate) struct Types {
110 env: Env,
111 scopes: Vec<HashMap<String, Ty>>,
112}
113
114impl Types {
115 fn new(program: &[Stmt]) -> Self {
116 let mut env = Env {
117 structs: HashMap::new(),
118 enums: HashMap::new(),
119 funcs: HashMap::new(),
120 };
121 for stmt in program {
122 match stmt {
123 Stmt::Struct { name, fields, .. } => {
124 env.structs.insert(name.clone(), fields.clone());
125 }
126 Stmt::Enum { name, variants, .. } => {
127 env.enums.insert(name.clone(), variants.clone());
128 }
129 Stmt::Func {
130 name, params, ret, ..
131 } => {
132 env.funcs
133 .insert(name.clone(), (params.clone(), ret.clone()));
134 }
135 _ => {}
136 }
137 }
138 env.structs.insert("Output".to_string(), output_fields());
142 Types {
143 env,
144 scopes: vec![HashMap::new()],
145 }
146 }
147
148 fn push_scope(&mut self) {
149 self.scopes.push(HashMap::new());
150 }
151
152 fn pop_scope(&mut self) {
153 self.scopes.pop();
154 }
155
156 fn declare(&mut self, name: String, ty: Ty) {
157 self.scopes.last_mut().unwrap().insert(name, ty);
158 }
159
160 fn lookup(&self, name: &str) -> Ty {
161 for scope in self.scopes.iter().rev() {
162 if let Some(t) = scope.get(name) {
163 return t.clone();
164 }
165 }
166 Ty::Unknown
167 }
168
169 fn type_of(&self, e: &Expr) -> Ty {
170 match e {
171 Expr::Int(..) => Ty::Int,
172 Expr::Float(..) => Ty::Float,
173 Expr::Str(..) => Ty::Str,
174 Expr::Bool(..) => Ty::Bool,
175 Expr::Ident(name, _) => {
176 if name == "none" {
177 Ty::Option(Box::new(Ty::Unknown))
178 } else {
179 self.lookup(name)
180 }
181 }
182 Expr::Array(els, _) => match els.first() {
183 Some(first) => Ty::Array(Box::new(self.type_of(first))),
184 None => Ty::Array(Box::new(Ty::Unknown)),
185 },
186 Expr::Unary { op, rhs, .. } => match op {
187 UnOp::Neg => self.type_of(rhs),
188 UnOp::Not => Ty::Bool,
189 },
190 Expr::Binary { op, lhs, .. } => match op {
191 BinOp::Eq
192 | BinOp::Ne
193 | BinOp::Lt
194 | BinOp::Gt
195 | BinOp::Le
196 | BinOp::Ge
197 | BinOp::And
198 | BinOp::Or => Ty::Bool,
199 _ => self.type_of(lhs),
200 },
201 Expr::Index { base, .. } => match self.type_of(base) {
202 Ty::Array(t) => *t,
203 other => other,
204 },
205 Expr::Range { .. } => Ty::Range,
206 Expr::Call { name, args, .. } => self.call_type(name, args),
207 Expr::StructLit { name, .. } => Ty::User(name.clone()),
208 Expr::EnumLit { enum_name, .. } => Ty::User(enum_name.clone()),
209 Expr::Field { base, field, .. } => self.field_type(base, field),
210 Expr::Match { arms, .. } => arms
211 .first()
212 .map(|a| self.type_of(&a.body))
213 .unwrap_or(Ty::Unknown),
214 }
215 }
216
217 fn call_type(&self, name: &str, args: &[Expr]) -> Ty {
218 match name {
219 "print" => Ty::Unit,
220 "string" => Ty::Str,
221 "int" | "length" => Ty::Int,
222 "float" => Ty::Float,
223 "some" => Ty::Option(Box::new(self.type_of(&args[0]))),
224 "ok" => Ty::Result(Box::new(self.type_of(&args[0])), Box::new(Ty::Unknown)),
225 "err" => Ty::Result(Box::new(Ty::Unknown), Box::new(self.type_of(&args[0]))),
226 "readFile" => Ty::Result(Box::new(Ty::Str), Box::new(Ty::Str)),
229 "writeFile" => Ty::Result(Box::new(Ty::Unit), Box::new(Ty::Str)),
230 "args" => Ty::Array(Box::new(Ty::Str)),
231 "readLine" => Ty::Option(Box::new(Ty::Str)),
232 "parseInt" => Ty::Option(Box::new(Ty::Int)),
234 "parseFloat" => Ty::Option(Box::new(Ty::Float)),
235 "eprint" => Ty::Unit,
236 "run" => Ty::Result(Box::new(Ty::User("Output".into())), Box::new(Ty::Str)),
239 _ => match self.env.funcs.get(name) {
240 Some((_, Some(ret))) => ty_from_ann(ret),
241 Some((_, None)) => Ty::Unit,
242 None => Ty::Unknown,
243 },
244 }
245 }
246
247 fn field_type(&self, base: &Expr, field: &str) -> Ty {
248 if let Expr::Ident(n, _) = base
249 && let Some(variants) = self.env.enums.get(n)
250 && variants.iter().any(|v| v.name == *field)
251 {
252 return Ty::User(n.clone());
253 }
254 match self.type_of(base) {
255 Ty::User(s) => self
256 .env
257 .structs
258 .get(&s)
259 .and_then(|fields| fields.iter().find(|f| f.name == *field))
260 .map(|f| ty_from_ann(&f.ty))
261 .unwrap_or(Ty::Unknown),
262 _ => Ty::Unknown,
263 }
264 }
265}
266
267fn output_fields() -> Vec<FieldDef> {
270 let field = |name: &str, ty: &str| FieldDef {
271 name: name.to_string(),
272 ty: TypeAnn {
273 kind: TypeKind::Named(ty.to_string()),
274 span: Span::new(0, 0),
275 },
276 span: Span::new(0, 0),
277 };
278 vec![
279 field("status", "int"),
280 field("stdout", "string"),
281 field("stderr", "string"),
282 ]
283}
284
285fn bin_prec(op: BinOp) -> u8 {
291 match op {
292 BinOp::Or => 1,
293 BinOp::And => 2,
294 BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Gt | BinOp::Le | BinOp::Ge => 3,
295 BinOp::Add | BinOp::Sub => 4,
296 BinOp::Mul | BinOp::Div | BinOp::Mod => 5,
297 }
298}
299
300fn op_str(op: BinOp) -> &'static str {
302 match op {
303 BinOp::Add => "+",
304 BinOp::Sub => "-",
305 BinOp::Mul => "*",
306 BinOp::Div => "/",
307 BinOp::Mod => "%",
308 BinOp::Eq => "==",
309 BinOp::Ne => "!=",
310 BinOp::Lt => "<",
311 BinOp::Gt => ">",
312 BinOp::Le => "<=",
313 BinOp::Ge => ">=",
314 BinOp::And => "&&",
315 BinOp::Or => "||",
316 }
317}
318
319fn indent(n: usize) -> String {
320 " ".repeat(n)
321}
322
323fn to_snake(s: &str) -> String {
326 let mut out = String::new();
327 for (i, c) in s.chars().enumerate() {
328 if c.is_uppercase() {
329 if i != 0 {
330 out.push('_');
331 }
332 out.extend(c.to_lowercase());
333 } else {
334 out.push(c);
335 }
336 }
337 out
338}
339
340fn to_pascal(s: &str) -> String {
343 let mut out = String::new();
344 let mut upper = true;
345 for c in s.chars() {
346 if c == '_' {
347 upper = true;
348 } else if upper {
349 out.extend(c.to_uppercase());
350 upper = false;
351 } else {
352 out.push(c);
353 }
354 }
355 out
356}
357
358fn escape(s: &str) -> String {
361 let mut out = String::new();
362 for c in s.chars() {
363 match c {
364 '\\' => out.push_str("\\\\"),
365 '"' => out.push_str("\\\""),
366 '\n' => out.push_str("\\n"),
367 '\t' => out.push_str("\\t"),
368 '\r' => out.push_str("\\r"),
369 _ => out.push(c),
370 }
371 }
372 out
373}
374
375fn format_float(f: f64) -> String {
378 let s = format!("{}", f);
379 if s.contains('.') || s.contains('e') || s.contains("inf") || s.contains("NaN") {
380 s
381 } else {
382 format!("{}.0", s)
383 }
384}
385
386fn reserve(name: &str, words: &[&str]) -> String {
396 if words.contains(&name) {
397 format!("{name}_")
398 } else {
399 name.to_string()
400 }
401}
402
403const GO_RESERVED: &[&str] = &[
407 "break",
408 "case",
409 "chan",
410 "const",
411 "continue",
412 "default",
413 "defer",
414 "else",
415 "fallthrough",
416 "for",
417 "func",
418 "go",
419 "goto",
420 "if",
421 "import",
422 "interface",
423 "map",
424 "package",
425 "range",
426 "return",
427 "select",
428 "struct",
429 "switch",
430 "type",
431 "var",
432 "append",
433 "cap",
434 "copy",
435 "delete",
436 "len",
437 "make",
438 "new",
439 "panic",
440 "recover",
441 "ptr",
442 "any",
443 "nil",
444 "iota",
445];
446
447fn go_ident(name: &str) -> String {
448 reserve(name, GO_RESERVED)
449}
450
451const RUST_RESERVED: &[&str] = &[
453 "as", "async", "await", "break", "const", "continue", "crate", "dyn", "else", "enum", "extern",
454 "false", "fn", "for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub",
455 "ref", "return", "self", "Self", "static", "struct", "super", "trait", "true", "type", "union",
456 "unsafe", "use", "where", "while", "abstract", "become", "box", "do", "final", "gen", "macro",
457 "override", "priv", "try", "typeof", "unsized", "virtual", "yield",
458];
459
460fn rust_ident(name: &str) -> String {
461 reserve(name, RUST_RESERVED)
462}
463
464const SWIFT_RESERVED: &[&str] = &[
466 "associatedtype",
467 "class",
468 "deinit",
469 "enum",
470 "extension",
471 "fileprivate",
472 "func",
473 "import",
474 "init",
475 "inout",
476 "internal",
477 "let",
478 "open",
479 "operator",
480 "private",
481 "protocol",
482 "public",
483 "rethrows",
484 "static",
485 "struct",
486 "subscript",
487 "typealias",
488 "var",
489 "actor",
490 "break",
491 "case",
492 "continue",
493 "default",
494 "defer",
495 "do",
496 "else",
497 "fallthrough",
498 "for",
499 "guard",
500 "if",
501 "in",
502 "repeat",
503 "return",
504 "switch",
505 "where",
506 "while",
507 "as",
508 "Any",
509 "catch",
510 "false",
511 "is",
512 "nil",
513 "super",
514 "self",
515 "Self",
516 "throw",
517 "throws",
518 "true",
519 "try",
520 "await",
521 "async",
522 "some",
523 "any",
524];
525
526fn swift_ident(name: &str) -> String {
527 reserve(name, SWIFT_RESERVED)
528}