use super::*;
#[derive(Debug, Clone, PartialEq)]
pub struct LibFunction {
pub name: String,
pub params: Vec<(String, Type)>,
pub return_type: Type,
}
#[derive(Debug, Clone, PartialEq)]
pub struct LibBlock {
pub lib: String,
pub version: String,
pub funcs: Vec<LibFunction>,
}
fn scalar_type_noun(t: &Type) -> Option<&'static str> {
match t {
Type::Integer => Some("number"),
Type::Float => Some("float"),
Type::String => Some("text"),
Type::Boolean => Some("boolean"),
Type::File => Some("file"),
Type::Buffer => Some("buffer"),
Type::Time => Some("time"),
Type::Timer => Some("timer"),
Type::Value => Some("value"),
_ => None,
}
}
fn type_noun(t: &Type) -> Option<String> {
match t {
Type::List(elem) => Some(match scalar_type_noun(elem) {
Some(en) => format!("list of {}", en),
None => "list".to_string(),
}),
Type::Map(_) => Some("map".to_string()),
_ => scalar_type_noun(t).map(|s| s.to_string()),
}
}
fn is_trackable_scalar_type(t: &Type) -> bool {
matches!(
t,
Type::Integer
| Type::Float
| Type::String
| Type::Boolean
| Type::File
| Type::Buffer
| Type::Time
| Type::Timer
| Type::Value
)
}
fn scalar_expr_type(
expr: &Expr,
scalar_types: &HashMap<String, Type>,
fn_return_types: &HashMap<String, Type>,
) -> Option<Type> {
match expr {
Expr::StringLit(_) => Some(Type::String),
Expr::IntegerLit(_) => Some(Type::Integer),
Expr::FloatLit(_) => Some(Type::Float),
Expr::BoolLit(_) => Some(Type::Boolean),
Expr::FormatString { .. } => Some(Type::String),
Expr::Identifier(n) => scalar_types
.get(n)
.filter(|t| is_trackable_scalar_type(t))
.cloned(),
Expr::FunctionCall { name, .. } => fn_return_types
.get(name)
.filter(|t| is_trackable_scalar_type(t))
.cloned(),
_ => None,
}
}
fn note_element_type(found: &mut Option<Type>, conflict: &mut bool, observed: Option<Type>) {
match observed {
None => *conflict = true,
Some(t) => match found {
Some(prev) if *prev != t => *conflict = true,
Some(_) => {}
None => *found = Some(t),
},
}
}
fn collect_declared_scalar_types_walk(
body: &[Statement],
found: &mut HashMap<String, Type>,
conflicted: &mut std::collections::HashSet<String>,
) {
for stmt in body {
match stmt {
Statement::VarDecl {
name,
var_type: Some(t),
..
} if is_trackable_scalar_type(t) => match found.get(name) {
Some(prev) if prev != t => {
conflicted.insert(name.clone());
}
Some(_) => {}
None => {
found.insert(name.clone(), t.clone());
}
},
Statement::If {
then_block,
else_if_blocks,
else_block,
..
} => {
collect_declared_scalar_types_walk(then_block, found, conflicted);
for (_, blk) in else_if_blocks {
collect_declared_scalar_types_walk(blk, found, conflicted);
}
if let Some(blk) = else_block {
collect_declared_scalar_types_walk(blk, found, conflicted);
}
}
Statement::While { body, .. }
| Statement::ForRange { body, .. }
| Statement::ForEach { body, .. }
| Statement::Repeat { body, .. } => {
collect_declared_scalar_types_walk(body, found, conflicted);
}
Statement::OnError { actions } => {
collect_declared_scalar_types_walk(actions, found, conflicted);
}
_ => {}
}
}
}
fn collect_declared_scalar_types(body: &[Statement]) -> HashMap<String, Type> {
let mut found = HashMap::new();
let mut conflicted = std::collections::HashSet::new();
collect_declared_scalar_types_walk(body, &mut found, &mut conflicted);
for name in &conflicted {
found.remove(name);
}
found
}
fn scan_list_element_type(
target: &str,
scalar_types: &HashMap<String, Type>,
fn_return_types: &HashMap<String, Type>,
body: &[Statement],
found: &mut Option<Type>,
conflict: &mut bool,
) {
for stmt in body {
match stmt {
Statement::ListAppend { list, value } if list == target => {
note_element_type(
found,
conflict,
scalar_expr_type(value, scalar_types, fn_return_types),
);
}
Statement::VarDecl {
name,
value: Some(Expr::ListLit { elements }),
..
} if name == target => {
for e in elements {
note_element_type(
found,
conflict,
scalar_expr_type(e, scalar_types, fn_return_types),
);
}
}
Statement::If {
then_block,
else_if_blocks,
else_block,
..
} => {
scan_list_element_type(target, scalar_types, fn_return_types, then_block, found, conflict);
for (_, blk) in else_if_blocks {
scan_list_element_type(target, scalar_types, fn_return_types, blk, found, conflict);
}
if let Some(blk) = else_block {
scan_list_element_type(target, scalar_types, fn_return_types, blk, found, conflict);
}
}
Statement::While { body, .. }
| Statement::ForRange { body, .. }
| Statement::ForEach { body, .. }
| Statement::Repeat { body, .. } => {
scan_list_element_type(target, scalar_types, fn_return_types, body, found, conflict);
}
Statement::OnError { actions } => {
scan_list_element_type(target, scalar_types, fn_return_types, actions, found, conflict);
}
_ => {}
}
}
}
pub(crate) fn infer_list_element_type(
target: &str,
param_types: &HashMap<String, Type>,
fn_return_types: &HashMap<String, Type>,
body: &[Statement],
) -> Type {
let mut scalar_types = param_types.clone();
scalar_types.extend(collect_declared_scalar_types(body));
let mut found: Option<Type> = None;
let mut conflict = false;
scan_list_element_type(target, &scalar_types, fn_return_types, body, &mut found, &mut conflict);
if conflict {
Type::Unknown
} else {
found.unwrap_or(Type::Unknown)
}
}
fn scan_return_values<'a>(body: &'a [Statement], out: &mut Vec<&'a Expr>) {
for stmt in body {
match stmt {
Statement::Return { value: Some(v), .. } => out.push(v),
Statement::If {
then_block,
else_if_blocks,
else_block,
..
} => {
scan_return_values(then_block, out);
for (_, blk) in else_if_blocks {
scan_return_values(blk, out);
}
if let Some(blk) = else_block {
scan_return_values(blk, out);
}
}
Statement::While { body, .. }
| Statement::ForRange { body, .. }
| Statement::ForEach { body, .. }
| Statement::Repeat { body, .. } => {
scan_return_values(body, out);
}
Statement::OnError { actions } => {
scan_return_values(actions, out);
}
_ => {}
}
}
}
pub(crate) fn infer_return_list_element_type(
param_types: &HashMap<String, Type>,
fn_return_types: &HashMap<String, Type>,
body: &[Statement],
) -> Type {
let mut returns = Vec::new();
scan_return_values(body, &mut returns);
let mut scalar_types = param_types.clone();
scalar_types.extend(collect_declared_scalar_types(body));
let mut found: Option<Type> = None;
let mut conflict = false;
for expr in returns {
match expr {
Expr::ListLit { elements } => {
for e in elements {
note_element_type(
&mut found,
&mut conflict,
scalar_expr_type(e, &scalar_types, fn_return_types),
);
}
}
Expr::Identifier(name) => {
let mut sub_found: Option<Type> = None;
let mut sub_conflict = false;
scan_list_element_type(name, &scalar_types, fn_return_types, body, &mut sub_found, &mut sub_conflict);
if sub_conflict {
conflict = true;
} else {
note_element_type(&mut found, &mut conflict, sub_found);
}
}
_ => conflict = true,
}
}
if conflict {
Type::Unknown
} else {
found.unwrap_or(Type::Unknown)
}
}
pub(crate) fn collect_lib_function_return_types(program: &Program) -> HashMap<(String, String), HashMap<String, Type>> {
let mut out: HashMap<(String, String), HashMap<String, Type>> = HashMap::new();
let mut current_lib: Option<(String, String)> = None;
for stmt in &program.statements {
match stmt {
Statement::LibraryDecl { name, version } => {
current_lib = Some((name.clone(), version.clone()));
}
Statement::FunctionDef { name, return_type, .. } => {
if let Some(lib) = ¤t_lib {
out.entry(lib.clone())
.or_default()
.insert(name.clone(), return_type.clone());
}
}
_ => {}
}
}
out
}
pub(crate) fn list_element_vartype(t: &Type) -> VarType {
match t {
Type::Integer => VarType::Integer,
Type::Float => VarType::Float,
Type::String => VarType::String,
Type::Boolean => VarType::Boolean,
Type::Buffer => VarType::String,
Type::Value => VarType::Mixed,
_ => VarType::Unknown,
}
}
pub fn render_lib_file(blocks: &[LibBlock], so_filename: &str) -> String {
let mut out = String::new();
for (i, block) in blocks.iter().enumerate() {
if i > 0 {
out.push('\n');
}
out.push_str(&format!(
"Library {} version \"{}\".\n",
format_lib_name(&block.lib),
block.version
));
out.push_str(&format!("Location \"./{}\".\n", so_filename));
out.push_str("\nTable of Contents:\n");
for func in &block.funcs {
out.push_str(" To ");
out.push_str(&format_lib_name(&func.name));
if !func.params.is_empty() {
out.push_str(" with ");
let joined = func
.params
.iter()
.map(|(pname, ptype)| {
let noun = type_noun(ptype).unwrap_or_else(|| "number".to_string());
format!("a {} called {}", noun, format_lib_name(pname))
})
.collect::<Vec<_>>()
.join(" and ");
out.push_str(&joined);
}
if let Some(rnoun) = type_noun(&func.return_type) {
out.push_str(&format!(", returning a {}", rnoun));
}
out.push_str(".\n");
}
}
out
}
pub(crate) fn type_noun_name(t: &Type) -> &'static str {
match t {
Type::Integer => "number",
Type::Float => "decimal",
Type::String => "text",
Type::Boolean => "boolean",
Type::List(_) => "list",
Type::Map(_) => "map",
_ => "type",
}
}