Skip to content

Commit

Permalink
Imports/exports: basic variables/functions
Browse files Browse the repository at this point in the history
  • Loading branch information
kengorab committed Jan 4, 2024
1 parent 7b82212 commit 855d47b
Show file tree
Hide file tree
Showing 10 changed files with 198 additions and 92 deletions.
20 changes: 6 additions & 14 deletions abra_cli/abra-files/example.abra
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
// TODO: This shouldn't stackoverflow
//println([])

enum Color {
Red
Blue
Green
RGB(r: Int, g: Int, b: Int)
}
import a, arr from "./example2"
import sum from "./example3"

val c = Color.RGB(r: 1, g: 2, b: 3)
match c {
Color.RGB(r, g, b) c => {
c.b = 100
}
_ => {}
}
c
val b = 1
arr[b] = a

(arr, sum(a, b))
5 changes: 4 additions & 1 deletion abra_cli/abra-files/example2.abra
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
export func f<T>(t: T): T = t
import s from "./example3"

export val a = 123
export val arr = [1, 2, s.length]
3 changes: 2 additions & 1 deletion abra_cli/abra-files/example3.abra
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export val a = "a"
export val s = "abcd"
export func sum(x: Int, y: Int): Int = x + y
10 changes: 5 additions & 5 deletions abra_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -375,8 +375,8 @@ fn cmd_compile_llvm_and_run_2(opts: BuildOpts) -> Result<(), ()> {
}
}

match tc.typecheck_module(&module_id) {
Ok(_) => {}
let entrypoint_module_id = match tc.typecheck_module(&module_id) {
Ok(m_id) => m_id,
Err(e) => {
match e {
Either::Left(Either::Left(e)) => {
Expand All @@ -396,17 +396,17 @@ fn cmd_compile_llvm_and_run_2(opts: BuildOpts) -> Result<(), ()> {

std::process::exit(1);
}
}
};

if opts.run {
let exit_status = LLVMCompiler2::compile_and_run(&project, &dotabra_dir, opts.out_file_name);
let exit_status = LLVMCompiler2::compile_and_run(&entrypoint_module_id, &project, &dotabra_dir, opts.out_file_name);
if let Some(status_code) = exit_status.code() {
std::process::exit(status_code)
} else {
// Process terminated by signal
}
} else {
LLVMCompiler2::compile(&project, &dotabra_dir, opts.out_file_name);
LLVMCompiler2::compile(&entrypoint_module_id, &project, &dotabra_dir, opts.out_file_name);
}

Ok(())
Expand Down
114 changes: 72 additions & 42 deletions abra_core/src/typechecker/typechecker2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -901,10 +901,18 @@ pub enum ExportedValue {
Variable(VarId),
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub enum ImportedValue {
Function(FuncId),
Type(TypeKind),
Variable(VarId),
}

#[derive(Debug, PartialEq)]
pub struct TypedModule {
pub id: ModuleId,
pub name: String,
pub imports: Vec<ModuleId>,
pub type_ids: Vec<TypeId>,
// TODO: is this necessary?
pub functions: Vec<FuncId>,
Expand Down Expand Up @@ -954,6 +962,13 @@ pub struct TypedMatchCase {
pub block_terminator: Option<TerminatorKind>,
}

#[derive(Clone, Debug, PartialEq)]
pub enum TypedImportKind {
ImportAll(/* star_token: */ Token),
ImportList(/* imports: */ Vec<ImportedValue>),
Alias(/* alias_token: */ Token),
}

#[derive(Clone, Debug, PartialEq)]
pub enum TypedNode {
// Expressions
Expand All @@ -979,13 +994,13 @@ pub enum TypedNode {
FuncDeclaration(FuncId),
TypeDeclaration(StructId),
EnumDeclaration(EnumId),
BindingDeclaration { token: Token, pattern: BindingPattern, vars: Vec<VarId>, expr: Option<Box<TypedNode>> },
BindingDeclaration { token: Token, is_exported: bool, pattern: BindingPattern, vars: Vec<VarId>, expr: Option<Box<TypedNode>> },
ForLoop { token: Token, binding: BindingPattern, binding_var_ids: Vec<VarId>, index_var_id: Option<VarId>, iterator: Box<TypedNode>, body: Vec<TypedNode>, block_terminator: Option<TerminatorKind> },
WhileLoop { token: Token, condition: Box<TypedNode>, condition_var_id: Option<VarId>, body: Vec<TypedNode>, block_terminator: Option<TerminatorKind> },
Break { token: Token },
Continue { token: Token },
Return { token: Token, expr: Option<Box<TypedNode>> },
Import { token: Token, kind: ImportKind, module_id: ModuleId },
Import { token: Token, kind: TypedImportKind, module_id: ModuleId },
}

impl TypedNode {
Expand Down Expand Up @@ -1864,7 +1879,7 @@ impl TypeError {

let second_line = match kind {
ImmutableAssignmentKind::Parameter => "Function parameters are automatically declared as immutable".to_string(),
ImmutableAssignmentKind::Variable => "Variable is declared as immutable. Use 'var' when declaring variable to allow it to be reassigned".to_string(),
ImmutableAssignmentKind::Variable => "Variable is declared as immutable".to_string(),
ImmutableAssignmentKind::Field(type_name) => format!("Field '{}' is marked readonly in type '{}'", var_name, type_name),
ImmutableAssignmentKind::Method(type_name) => format!("Function '{}' is a method on type '{}'", var_name, type_name),
ImmutableAssignmentKind::StaticMethod(type_name) => format!("Function '{}' is a static method on type '{}'", var_name, type_name),
Expand Down Expand Up @@ -2761,7 +2776,7 @@ impl<'a, L: LoadModule> Typechecker2<'a, L> {
debug_assert!(self.project.modules.is_empty());

self.module_loader.register(&parser::ast::ModuleId::prelude(), &PRELUDE_MODULE_ID);
let mut prelude_module = TypedModule { id: PRELUDE_MODULE_ID, name: "prelude".to_string(), type_ids: vec![], functions: vec![], structs: vec![], enums: vec![], code: vec![], scopes: vec![], exports: HashMap::new(), completed: false };
let mut prelude_module = TypedModule { id: PRELUDE_MODULE_ID, name: "prelude".to_string(), imports: vec![], type_ids: vec![], functions: vec![], structs: vec![], enums: vec![], code: vec![], scopes: vec![], exports: HashMap::new(), completed: false };
let mut prelude_scope = Scope { label: "prelude.root".to_string(), kind: ScopeKind::Module(PRELUDE_MODULE_ID), terminator: None, id: PRELUDE_SCOPE_ID, parent: None, types: vec![], vars: vec![], funcs: vec![] };

let primitives = [
Expand Down Expand Up @@ -2833,7 +2848,7 @@ impl<'a, L: LoadModule> Typechecker2<'a, L> {
Ok(())
}

pub fn typecheck_module(&mut self, m_id: &parser::ast::ModuleId) -> Result<(), TypecheckError> {
pub fn typecheck_module(&mut self, m_id: &parser::ast::ModuleId) -> Result<ModuleId, TypecheckError> {
debug_assert!(self.project.modules.len() >= 1 && self.project.modules[0].name == "prelude", "Prelude must be loaded in order to typecheck further modules");

let (file_name, parse_result) = self.module_loader.load_untyped_ast(&m_id).map_err(Either::Left)?
Expand All @@ -2848,6 +2863,7 @@ impl<'a, L: LoadModule> Typechecker2<'a, L> {
self.project.modules.push(TypedModule {
id: module_id,
name: file_name,
imports: vec![],
type_ids: vec![],
functions: vec![],
structs: vec![],
Expand All @@ -2860,27 +2876,26 @@ impl<'a, L: LoadModule> Typechecker2<'a, L> {

self.current_scope_id = scope_id;

if !parse_result.imports.is_empty() {
for (_, import_m_id, module_token) in parse_result.imports {
if !self.module_loader.module_exists(&import_m_id) { continue; }
if let Some(m) = self.module_loader.get_module_id(&import_m_id).and_then(|module_id| self.project.modules.get(module_id.0)) {
if m.completed { continue; }
for (_, import_m_id, module_token) in parse_result.imports {
if !self.module_loader.module_exists(&import_m_id) { continue; }
if let Some(m) = self.module_loader.get_module_id(&import_m_id).and_then(|module_id| self.project.modules.get(module_id.0)) {
if m.completed { continue; }

let span = self.make_span(&module_token.get_range());
return Err(Either::Right(TypeError::CircularModuleImport { span }));
}

let mut tc = Typechecker2::new(self.module_loader, self.project);
tc.typecheck_module(&import_m_id)?;
let span = self.make_span(&module_token.get_range());
return Err(Either::Right(TypeError::CircularModuleImport { span }));
}

let mut tc = Typechecker2::new(self.module_loader, self.project);
let imported_module_id = tc.typecheck_module(&import_m_id)?;
self.current_module_mut().imports.push(imported_module_id);
}

self.current_scope_id = scope_id;
self.typecheck_block(parse_result.nodes).map_err(Either::Right)?;

self.current_module_mut().completed = true;

Ok(())
Ok(module_id)
}

fn typecheck_block(&mut self, nodes: Vec<AstNode>) -> Result<(), TypeError> {
Expand Down Expand Up @@ -3884,7 +3899,7 @@ impl<'a, L: LoadModule> Typechecker2<'a, L> {
}
}

Ok(TypedNode::BindingDeclaration { token, pattern: binding, vars: var_ids, expr: typed_expr })
Ok(TypedNode::BindingDeclaration { token, is_exported, pattern: binding, vars: var_ids, expr: typed_expr })
}
AstNode::IfStatement(token, if_node) => self.typecheck_if_node(token, if_node, false, type_hint),
AstNode::ForLoop(token, for_loop_node) => {
Expand Down Expand Up @@ -4052,74 +4067,89 @@ impl<'a, L: LoadModule> Typechecker2<'a, L> {
let module_id = *module_id;
let ModuleId(module_idx) = module_id;

match &kind {
let kind = match kind {
ImportKind::ImportAll(star_token) => {
let import_module = &self.project.modules[module_idx];
let exports = import_module.exports.values().map(|e| e.clone()).collect_vec();

for export in exports {
self.add_imported_value(export, star_token)?;
self.add_imported_value(export, &star_token)?;
}

TypedImportKind::ImportAll(star_token)
}
ImportKind::ImportList(imports) => {
let mut imported_values = Vec::with_capacity(imports.len());
for import_tok in imports {
let import_name = Token::get_ident_name(import_tok);
let import_name = Token::get_ident_name(&import_tok);
let import_module = &self.project.modules[module_idx];
let Some(export) = import_module.exports.get(&import_name) else {
let span = self.make_span(&import_tok.get_range());
return Err(TypeError::UnknownExport { span, module_id, import_name, is_aliased: false });
};

self.add_imported_value(*export, import_tok)?;
let imported_value = self.add_imported_value(*export, &import_tok)?;
imported_values.push(imported_value);
}
TypedImportKind::ImportList(imported_values)
}
ImportKind::Alias(alias_token) => {
let alias_name = Token::get_ident_name(alias_token);
let alias_name = Token::get_ident_name(&alias_token);
let module_type_id = TypeId::module_type_alias(&module_id);
let span = self.make_span(&alias_token.get_range());
self.add_variable_to_current_scope(alias_name, module_type_id, false, true, &span, false)?;

TypedImportKind::Alias(alias_token)
}
}
};

Ok(TypedNode::Import { token, kind, module_id })
}
n => self.typecheck_expression(n, type_hint)
}
}

fn add_imported_value(&mut self, exported_value: ExportedValue, import_token: &Token) -> Result<(), TypeError> {
fn add_imported_value(&mut self, exported_value: ExportedValue, import_token: &Token) -> Result<ImportedValue, TypeError> {
let span = self.make_span(&import_token.get_range());

match exported_value {
let imported_value = match exported_value {
ExportedValue::Function(func_id) => {
self.add_function_variable_alias_to_current_scope(import_token, &func_id)?;

ImportedValue::Function(func_id)
}
ExportedValue::Type(type_kind) => match type_kind {
TypeKind::Struct(struct_id) => {
let struct_type_id = self.add_or_find_type_id(self.project.struct_type(struct_id));
let struct_ = self.project.get_struct_by_id(&struct_id);
let struct_var_id = self.add_variable_to_current_scope(struct_.name.clone(), struct_type_id, false, true, &span, false)?;
let variable = self.project.get_var_by_id_mut(&struct_var_id);
variable.alias = VariableAlias::Type(TypeKind::Struct(struct_id));
}
TypeKind::Enum(enum_id) => {
let enum_type_id = self.add_or_find_type_id(self.project.enum_type(enum_id));
let enum_ = self.project.get_enum_by_id(&enum_id);
let enum_var_id = self.add_variable_to_current_scope(enum_.name.clone(), enum_type_id, false, true, &span, false)?;
let variable = self.project.get_var_by_id_mut(&enum_var_id);
variable.alias = VariableAlias::Type(TypeKind::Enum(enum_id));
ExportedValue::Type(type_kind) => {
match type_kind {
TypeKind::Struct(struct_id) => {
let struct_type_id = self.add_or_find_type_id(self.project.struct_type(struct_id));
let struct_ = self.project.get_struct_by_id(&struct_id);
let struct_var_id = self.add_variable_to_current_scope(struct_.name.clone(), struct_type_id, false, true, &span, false)?;
let variable = self.project.get_var_by_id_mut(&struct_var_id);
variable.alias = VariableAlias::Type(TypeKind::Struct(struct_id));
}
TypeKind::Enum(enum_id) => {
let enum_type_id = self.add_or_find_type_id(self.project.enum_type(enum_id));
let enum_ = self.project.get_enum_by_id(&enum_id);
let enum_var_id = self.add_variable_to_current_scope(enum_.name.clone(), enum_type_id, false, true, &span, false)?;
let variable = self.project.get_var_by_id_mut(&enum_var_id);
variable.alias = VariableAlias::Type(TypeKind::Enum(enum_id));
}
}

ImportedValue::Type(type_kind)
}
ExportedValue::Variable(var_id) => {
let imported_var = self.project.get_var_by_id(&var_id);
let is_captured = imported_var.is_captured;
let var_id = self.add_variable_to_current_scope(imported_var.name.clone(), imported_var.type_id, false, imported_var.is_initialized, &span, false)?;
let var = self.project.get_var_by_id_mut(&var_id);
var.is_captured = is_captured;

ImportedValue::Variable(var_id)
}
}
};

Ok(())
Ok(imported_value)
}

fn typecheck_if_node(&mut self, if_token: Token, if_node: IfNode, is_expr: bool, type_hint: Option<TypeId>) -> Result<TypedNode, TypeError> {
Expand Down
3 changes: 3 additions & 0 deletions abra_core/src/typechecker/typechecker2_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1408,6 +1408,7 @@ fn typecheck_for_loop() {
body: vec![
TypedNode::BindingDeclaration {
token: Token::Val(Position::new(2, 16)),
is_exported: false,
pattern: BindingPattern::Variable(Token::Ident(Position::new(2, 20), "a".to_string())),
vars: vec![VarId(ScopeId(ModuleId(1), 1), 1)],
expr: Some(Box::new(TypedNode::Identifier {
Expand Down Expand Up @@ -1493,6 +1494,7 @@ fn typecheck_while_loop() {
body: vec![
TypedNode::BindingDeclaration {
token: Token::Val(Position::new(2, 18)),
is_exported: false,
pattern: BindingPattern::Variable(Token::Ident(Position::new(2, 22), "a".to_string())),
vars: vec![VarId(ScopeId(ModuleId(1), 1), 1)],
expr: Some(Box::new(TypedNode::Identifier {
Expand Down Expand Up @@ -2806,6 +2808,7 @@ fn typecheck_function_declaration() {
body: vec![
TypedNode::BindingDeclaration {
token: Token::Val(Position::new(3, 1)),
is_exported: false,
pattern: BindingPattern::Variable(Token::Ident(Position::new(3, 5), "y".to_string())),
vars: vec![
VarId(ScopeId(ModuleId(1), 1), 1)
Expand Down
Loading

0 comments on commit 855d47b

Please sign in to comment.