Skip to content

Commit

Permalink
Merge pull request #47 from sezna/alex/variable-exprs
Browse files Browse the repository at this point in the history
Introduce  variable exprs and VM runtime tests
  • Loading branch information
sezna authored Jun 25, 2024
2 parents 937f597 + 6601ae4 commit b47e3aa
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 20 deletions.
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pete/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ fn main() -> Result<(), error::PeteError> {
timings.start("execution");
match target.to_lowercase().as_str() {
"vm" => {
let mut vm = Vm::new(instructions, data);
let vm = Vm::new(instructions, data);
vm.run().expect("Failed to run vm");
},
"native" => todo!(),
Expand Down
6 changes: 3 additions & 3 deletions petr-bind/src/binder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -560,7 +560,7 @@ mod tests {
__Scopes__
0: Root (parent none):
test: Module Module { root_scope: ScopeId(1), exports: {} }
1: Module test (parent none):
1: Module test (parent scopeid0):
trinary_boolean: Type TypeId(0)
True: Function FunctionId(0)
False: Function FunctionId(1)
Expand All @@ -579,7 +579,7 @@ mod tests {
__Scopes__
0: Root (parent none):
test: Module Module { root_scope: ScopeId(1), exports: {} }
1: Module test (parent none):
1: Module test (parent scopeid0):
add: Function FunctionId(0)
2: Function (parent scopeid1):
a: FunctionParameter Named(Identifier { id: SymbolId(3) })
Expand All @@ -596,7 +596,7 @@ mod tests {
__Scopes__
0: Root (parent none):
test: Module Module { root_scope: ScopeId(1), exports: {} }
1: Module test (parent none):
1: Module test (parent scopeid0):
add: Function FunctionId(0)
2: Function (parent scopeid1):
a: FunctionParameter Named(Identifier { id: SymbolId(3) })
Expand Down
39 changes: 36 additions & 3 deletions petr-ir/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,12 @@ impl Lowerer {
let mut buf = vec![];
self.with_variable_context(|ctx| -> Result<_, _> {
// TODO: func should have type checked types...not just the AST type
for (param_name, param_ty) in &func.params {
// Pop parameters off the stack in reverse order -- the last parameter for the function
// will be the first thing popped off the stack
// When we lower a function call, we push them onto the stack from first to last. Since
// the stack is FILO, we reverse that order here.

for (param_name, param_ty) in func.params.iter().rev() {
// in order, assign parameters to registers
if fits_in_reg(param_ty) {
// load from stack into register
Expand Down Expand Up @@ -485,7 +490,7 @@ mod tests {
function 0:
0 pop v0
1 pop v1
2 push v0
2 push v1
3 ret
ENTRY: function 1:
4 ld v2 datalabel0
Expand Down Expand Up @@ -525,7 +530,35 @@ mod tests {
a
function main() returns 'int ~hi(1, 2)
"#,
expect![[r#""#]],
expect![[r#"
; DATA_SECTION
0: Int64(20)
1: Int64(30)
2: Int64(42)
3: Int64(1)
4: Int64(2)
; PROGRAM_SECTION
ENTRY: 1
function 0:
0 pop v0
1 pop v1
2 cp v2 v1
3 cp v3 v0
4 ld v4 datalabel0
5 ld v5 datalabel1
6 ld v6 datalabel2
7 push v2
8 ret
ENTRY: function 1:
9 ld v7 datalabel3
10 push v7
11 ld v8 datalabel4
12 push v8
13 ppc
14 jumpi functionid0
15 ret
"#]],
);
}
}
49 changes: 39 additions & 10 deletions petr-typecheck/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ pub struct TypeChecker {
typed_functions: BTreeMap<FunctionId, Function>,
errors: Vec<TypeCheckError>,
resolved: QueryableResolvedItems,
generics_in_scope: Vec<BTreeMap<Identifier, TypeVariable>>,
variable_scope: Vec<BTreeMap<Identifier, TypeVariable>>,
}

pub type TypeVariable = Type<&'static str>;
Expand Down Expand Up @@ -102,29 +102,41 @@ impl TypeChecker {
&mut self,
f: impl FnOnce(&mut Self) -> T,
) -> T {
self.generics_in_scope.push(Default::default());
self.variable_scope.push(Default::default());
let res = f(self);
self.generics_in_scope.pop();
self.variable_scope.pop();
res
}

fn generic_type(
&mut self,
id: &Identifier,
) -> TypeVariable {
for scope in self.generics_in_scope.iter().rev() {
for scope in self.variable_scope.iter().rev() {
if let Some(ty) = scope.get(id) {
return ty.clone();
}
}
let fresh_ty = self.fresh_ty_var();
self.generics_in_scope
self.variable_scope
.last_mut()
.expect("looked for generic when no scope existed")
.insert(*id, fresh_ty.clone());
fresh_ty
}

fn find_variable(
&self,
id: Identifier,
) -> Option<TypeVariable> {
for scope in self.variable_scope.iter().rev() {
if let Some(ty) = scope.get(&id) {
return Some(ty.clone());
}
}
None
}

fn fully_type_check(&mut self) {
// TODO collects on these iters is not ideal
for (id, _) in self.resolved.types() {
Expand Down Expand Up @@ -157,13 +169,24 @@ impl TypeChecker {
errors: Default::default(),
typed_functions: Default::default(),
resolved,
generics_in_scope: Default::default(),
variable_scope: Default::default(),
};

type_checker.fully_type_check();
type_checker
}

pub fn insert_variable(
&mut self,
id: Identifier,
ty: TypeVariable,
) {
self.variable_scope
.last_mut()
.expect("inserted variable when no scope existed")
.insert(id, ty);
}

pub fn fresh_ty_var(&mut self) -> TypeVariable {
self.ctx.new_variable()
}
Expand Down Expand Up @@ -438,11 +461,12 @@ impl TypeCheck for Expr {
ExprKind::Variable { name, ty } => {
// look up variable in scope
// find its expr return type
let var_ty = ctx.find_variable(*name).expect("variable not found in scope");
let ty = ctx.to_type_var(ty);

TypedExpr::Variable {
ty: ctx.to_type_var(ty),
name: *name,
}
ctx.unify(&var_ty, &ty);

TypedExpr::Variable { ty, name: *name }
},
ExprKind::Intrinsic(intrinsic) => intrinsic.type_check(ctx),
ExprKind::TypeConstructor => {
Expand All @@ -455,6 +479,7 @@ impl TypeCheck for Expr {
let mut type_checked_bindings = Vec::with_capacity(bindings.len());
for binding in bindings {
let binding_ty = binding.expression.type_check(ctx);
ctx.insert_variable(binding.name, binding_ty.ty());
type_checked_bindings.push((binding.name, binding_ty));
}

Expand Down Expand Up @@ -519,6 +544,10 @@ impl TypeCheck for petr_resolve::Function {
ctx.with_type_scope(|ctx| {
let params = self.params.iter().map(|(name, ty)| (*name, ctx.to_type_var(ty))).collect::<Vec<_>>();

for (name, ty) in &params {
ctx.insert_variable(*name, ty.clone());
}

// unify types within the body with the parameter
let body = self.body.type_check(ctx);

Expand Down
6 changes: 6 additions & 0 deletions petr-vm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,9 @@ edition = "2021"
petr-ir = { path = "../petr-ir" }
petr-utils = { path = "../petr-utils" }
thiserror = "1.0.61"

[dev-dependencies]
petr-parse = { path = "../petr-parse" }
expect-test = "1.5.0"
petr-resolve = { path = "../petr-resolve" }
petr-typecheck = { path = "../petr-typecheck" }
62 changes: 59 additions & 3 deletions petr-vm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,62 @@ use petr_ir::{DataLabel, DataSectionEntry, Intrinsic, IrOpcode, Reg};
use petr_utils::{idx_map_key, IndexMap};
use thiserror::Error;

#[cfg(test)]
mod tests {

use expect_test::{expect, Expect};
use petr_ir::Lowerer;
use petr_resolve::resolve_symbols;
use petr_typecheck::TypeChecker;
use petr_utils::render_error;

use super::*;
fn check(
input: impl Into<String>,
expect: Expect,
) {
let input = input.into();
let parser = petr_parse::Parser::new(vec![("test", input)]);
let (ast, errs, interner, source_map) = parser.into_result();
if !errs.is_empty() {
errs.into_iter().for_each(|err| eprintln!("{:?}", render_error(&source_map, err)));
panic!("fmt failed: code didn't parse");
}
let (errs, resolved) = resolve_symbols(ast, interner, Default::default());
if !errs.is_empty() {
dbg!(&errs);
}
let type_checker = TypeChecker::new(resolved);
let lowerer = Lowerer::new(type_checker);
let (data, ir) = lowerer.finalize();
let vm = Vm::new(ir, data);
let res = vm.run();

let res_as_u64 = res.unwrap().iter().map(|val| val.0).collect::<Vec<_>>();
let res = format!("{res_as_u64:?}");

expect.assert_eq(&res);
}

#[test]

fn let_bindings() {
check(
r#"
function hi(x in 'int, y in 'int) returns 'int
let a = x,
b = y,
c = 20,
d = 30,
e = 12,
a
function main() returns 'int ~hi(42, 3)
"#,
expect!["[42]"],
)
}
}

pub struct Vm {
state: VmState,
instructions: IndexMap<ProgramOffset, IrOpcode>,
Expand All @@ -34,7 +90,7 @@ impl Default for ProgramOffset {
}
}

#[derive(Clone, Copy)]
#[derive(Clone, Copy, Debug)]
pub struct Value(u64);

#[derive(Debug, Error)]
Expand Down Expand Up @@ -78,7 +134,7 @@ impl Vm {
}
}

pub fn run(&mut self) -> Result<()> {
pub fn run(mut self) -> Result<Vec<Value>> {
use VmControlFlow::*;
loop {
match self.execute() {
Expand All @@ -87,7 +143,7 @@ impl Vm {
Err(e) => return Err(e),
}
}
Ok(())
Ok(self.state.stack)
}

fn execute(&mut self) -> Result<VmControlFlow> {
Expand Down

0 comments on commit b47e3aa

Please sign in to comment.