mod go;
mod rust;
mod swift;
pub use go::to_go;
pub use rust::to_rust;
pub use swift::to_swift;
use std::collections::HashMap;
use crate::ast::*;
use crate::diagnostic::Span;
#[derive(Clone, PartialEq)]
pub(crate) enum Ty {
Int,
Float,
Str,
Bool,
Array(Box<Ty>),
User(String),
Option(Box<Ty>),
Result(Box<Ty>, Box<Ty>),
Range,
Unit,
Unknown,
}
impl Ty {
fn has_unknown(&self) -> bool {
match self {
Ty::Unknown => true,
Ty::Array(t) | Ty::Option(t) => t.has_unknown(),
Ty::Result(a, b) => a.has_unknown() || b.has_unknown(),
_ => false,
}
}
fn has_int(&self) -> bool {
match self {
Ty::Int => true,
Ty::Array(t) | Ty::Option(t) => t.has_int(),
Ty::Result(a, b) => a.has_int() || b.has_int(),
_ => false,
}
}
fn is_scalar(&self) -> bool {
matches!(self, Ty::Int | Ty::Float | Ty::Str | Ty::Bool)
}
}
fn ty_from_ann(a: &TypeAnn) -> Ty {
match &a.kind {
TypeKind::Named(n) => match n.as_str() {
"int" => Ty::Int,
"float" => Ty::Float,
"string" => Ty::Str,
"bool" => Ty::Bool,
"Unit" => Ty::Unit,
_ => Ty::User(n.clone()),
},
TypeKind::Array(inner) => Ty::Array(Box::new(ty_from_ann(inner))),
TypeKind::Generic(name, args) => match (name.as_str(), args.as_slice()) {
("Option", [t]) => Ty::Option(Box::new(ty_from_ann(t))),
("Result", [a, b]) => Ty::Result(Box::new(ty_from_ann(a)), Box::new(ty_from_ann(b))),
_ => Ty::Unknown,
},
}
}
pub(crate) struct Env {
structs: HashMap<String, Vec<FieldDef>>,
enums: HashMap<String, Vec<VariantDef>>,
funcs: HashMap<String, (Vec<Param>, Option<TypeAnn>)>,
}
pub(crate) struct Types {
env: Env,
scopes: Vec<HashMap<String, Ty>>,
}
impl Types {
fn new(program: &[Stmt]) -> Self {
let mut env = Env {
structs: HashMap::new(),
enums: HashMap::new(),
funcs: HashMap::new(),
};
for stmt in program {
match stmt {
Stmt::Struct { name, fields, .. } => {
env.structs.insert(name.clone(), fields.clone());
}
Stmt::Enum { name, variants, .. } => {
env.enums.insert(name.clone(), variants.clone());
}
Stmt::Func {
name, params, ret, ..
} => {
env.funcs
.insert(name.clone(), (params.clone(), ret.clone()));
}
_ => {}
}
}
env.structs.insert("Output".to_string(), output_fields());
Types {
env,
scopes: vec![HashMap::new()],
}
}
fn push_scope(&mut self) {
self.scopes.push(HashMap::new());
}
fn pop_scope(&mut self) {
self.scopes.pop();
}
fn declare(&mut self, name: String, ty: Ty) {
self.scopes.last_mut().unwrap().insert(name, ty);
}
fn lookup(&self, name: &str) -> Ty {
for scope in self.scopes.iter().rev() {
if let Some(t) = scope.get(name) {
return t.clone();
}
}
Ty::Unknown
}
fn type_of(&self, e: &Expr) -> Ty {
match e {
Expr::Int(..) => Ty::Int,
Expr::Float(..) => Ty::Float,
Expr::Str(..) => Ty::Str,
Expr::Bool(..) => Ty::Bool,
Expr::Ident(name, _) => {
if name == "none" {
Ty::Option(Box::new(Ty::Unknown))
} else {
self.lookup(name)
}
}
Expr::Array(els, _) => match els.first() {
Some(first) => Ty::Array(Box::new(self.type_of(first))),
None => Ty::Array(Box::new(Ty::Unknown)),
},
Expr::Unary { op, rhs, .. } => match op {
UnOp::Neg => self.type_of(rhs),
UnOp::Not => Ty::Bool,
},
Expr::Binary { op, lhs, .. } => match op {
BinOp::Eq
| BinOp::Ne
| BinOp::Lt
| BinOp::Gt
| BinOp::Le
| BinOp::Ge
| BinOp::And
| BinOp::Or => Ty::Bool,
_ => self.type_of(lhs),
},
Expr::Index { base, .. } => match self.type_of(base) {
Ty::Array(t) => *t,
other => other,
},
Expr::Range { .. } => Ty::Range,
Expr::Call { name, args, .. } => self.call_type(name, args),
Expr::StructLit { name, .. } => Ty::User(name.clone()),
Expr::EnumLit { enum_name, .. } => Ty::User(enum_name.clone()),
Expr::Field { base, field, .. } => self.field_type(base, field),
Expr::Match { arms, .. } => arms
.first()
.map(|a| self.type_of(&a.body))
.unwrap_or(Ty::Unknown),
}
}
fn call_type(&self, name: &str, args: &[Expr]) -> Ty {
match name {
"print" => Ty::Unit,
"string" => Ty::Str,
"int" | "length" => Ty::Int,
"float" => Ty::Float,
"some" => Ty::Option(Box::new(self.type_of(&args[0]))),
"ok" => Ty::Result(Box::new(self.type_of(&args[0])), Box::new(Ty::Unknown)),
"err" => Ty::Result(Box::new(Ty::Unknown), Box::new(self.type_of(&args[0]))),
"readFile" => Ty::Result(Box::new(Ty::Str), Box::new(Ty::Str)),
"writeFile" => Ty::Result(Box::new(Ty::Unit), Box::new(Ty::Str)),
"args" => Ty::Array(Box::new(Ty::Str)),
"readLine" => Ty::Option(Box::new(Ty::Str)),
"parseInt" => Ty::Option(Box::new(Ty::Int)),
"parseFloat" => Ty::Option(Box::new(Ty::Float)),
"eprint" => Ty::Unit,
"run" => Ty::Result(Box::new(Ty::User("Output".into())), Box::new(Ty::Str)),
_ => match self.env.funcs.get(name) {
Some((_, Some(ret))) => ty_from_ann(ret),
Some((_, None)) => Ty::Unit,
None => Ty::Unknown,
},
}
}
fn field_type(&self, base: &Expr, field: &str) -> Ty {
if let Expr::Ident(n, _) = base
&& let Some(variants) = self.env.enums.get(n)
&& variants.iter().any(|v| v.name == *field)
{
return Ty::User(n.clone());
}
match self.type_of(base) {
Ty::User(s) => self
.env
.structs
.get(&s)
.and_then(|fields| fields.iter().find(|f| f.name == *field))
.map(|f| ty_from_ann(&f.ty))
.unwrap_or(Ty::Unknown),
_ => Ty::Unknown,
}
}
}
fn output_fields() -> Vec<FieldDef> {
let field = |name: &str, ty: &str| FieldDef {
name: name.to_string(),
ty: TypeAnn {
kind: TypeKind::Named(ty.to_string()),
span: Span::new(0, 0),
},
span: Span::new(0, 0),
};
vec![
field("status", "int"),
field("stdout", "string"),
field("stderr", "string"),
]
}
fn bin_prec(op: BinOp) -> u8 {
match op {
BinOp::Or => 1,
BinOp::And => 2,
BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Gt | BinOp::Le | BinOp::Ge => 3,
BinOp::Add | BinOp::Sub => 4,
BinOp::Mul | BinOp::Div | BinOp::Mod => 5,
}
}
fn op_str(op: BinOp) -> &'static str {
match op {
BinOp::Add => "+",
BinOp::Sub => "-",
BinOp::Mul => "*",
BinOp::Div => "/",
BinOp::Mod => "%",
BinOp::Eq => "==",
BinOp::Ne => "!=",
BinOp::Lt => "<",
BinOp::Gt => ">",
BinOp::Le => "<=",
BinOp::Ge => ">=",
BinOp::And => "&&",
BinOp::Or => "||",
}
}
fn indent(n: usize) -> String {
" ".repeat(n)
}
fn to_snake(s: &str) -> String {
let mut out = String::new();
for (i, c) in s.chars().enumerate() {
if c.is_uppercase() {
if i != 0 {
out.push('_');
}
out.extend(c.to_lowercase());
} else {
out.push(c);
}
}
out
}
fn to_pascal(s: &str) -> String {
let mut out = String::new();
let mut upper = true;
for c in s.chars() {
if c == '_' {
upper = true;
} else if upper {
out.extend(c.to_uppercase());
upper = false;
} else {
out.push(c);
}
}
out
}
fn escape(s: &str) -> String {
let mut out = String::new();
for c in s.chars() {
match c {
'\\' => out.push_str("\\\\"),
'"' => out.push_str("\\\""),
'\n' => out.push_str("\\n"),
'\t' => out.push_str("\\t"),
'\r' => out.push_str("\\r"),
_ => out.push(c),
}
}
out
}
fn format_float(f: f64) -> String {
let s = format!("{}", f);
if s.contains('.') || s.contains('e') || s.contains("inf") || s.contains("NaN") {
s
} else {
format!("{}.0", s)
}
}
fn reserve(name: &str, words: &[&str]) -> String {
if words.contains(&name) {
format!("{name}_")
} else {
name.to_string()
}
}
const GO_RESERVED: &[&str] = &[
"break",
"case",
"chan",
"const",
"continue",
"default",
"defer",
"else",
"fallthrough",
"for",
"func",
"go",
"goto",
"if",
"import",
"interface",
"map",
"package",
"range",
"return",
"select",
"struct",
"switch",
"type",
"var",
"append",
"cap",
"copy",
"delete",
"len",
"make",
"new",
"panic",
"recover",
"ptr",
"any",
"nil",
"iota",
];
fn go_ident(name: &str) -> String {
reserve(name, GO_RESERVED)
}
const RUST_RESERVED: &[&str] = &[
"as", "async", "await", "break", "const", "continue", "crate", "dyn", "else", "enum", "extern",
"false", "fn", "for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub",
"ref", "return", "self", "Self", "static", "struct", "super", "trait", "true", "type", "union",
"unsafe", "use", "where", "while", "abstract", "become", "box", "do", "final", "gen", "macro",
"override", "priv", "try", "typeof", "unsized", "virtual", "yield",
];
fn rust_ident(name: &str) -> String {
reserve(name, RUST_RESERVED)
}
const SWIFT_RESERVED: &[&str] = &[
"associatedtype",
"class",
"deinit",
"enum",
"extension",
"fileprivate",
"func",
"import",
"init",
"inout",
"internal",
"let",
"open",
"operator",
"private",
"protocol",
"public",
"rethrows",
"static",
"struct",
"subscript",
"typealias",
"var",
"actor",
"break",
"case",
"continue",
"default",
"defer",
"do",
"else",
"fallthrough",
"for",
"guard",
"if",
"in",
"repeat",
"return",
"switch",
"where",
"while",
"as",
"Any",
"catch",
"false",
"is",
"nil",
"super",
"self",
"Self",
"throw",
"throws",
"true",
"try",
"await",
"async",
"some",
"any",
];
fn swift_ident(name: &str) -> String {
reserve(name, SWIFT_RESERVED)
}