2023-07-12 00:53:52 -05:00
|
|
|
use crate::ast::*;
|
2023-07-14 00:51:52 -05:00
|
|
|
|
2023-07-13 02:00:56 -05:00
|
|
|
use std::collections::btree_map::Entry::Vacant;
|
2023-07-12 00:53:52 -05:00
|
|
|
use std::collections::{BTreeMap, HashMap};
|
|
|
|
|
use std::fmt::{Display, Formatter};
|
|
|
|
|
|
|
|
|
|
#[derive(Eq, Clone, PartialEq, Debug)]
|
|
|
|
|
pub struct CompileError(usize, usize, String);
|
|
|
|
|
|
|
|
|
|
impl Display for CompileError {
|
|
|
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
write!(f, "({}:{}) {}", self.0, self.1, self.2)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-13 02:00:56 -05:00
|
|
|
/// A variable, of various different types:
|
|
|
|
|
/// - Literals have static values and can just be pushed to the stack
|
2023-07-17 22:57:58 -05:00
|
|
|
/// - IndirectLabels contain the label pointing to the value (think `foo: .db 0`)
|
|
|
|
|
/// - Direct labels are the value themselves (think `call foo`)
|
2023-07-13 02:00:56 -05:00
|
|
|
/// - Locals contain an index into the local frame
|
|
|
|
|
#[derive(Clone, PartialEq, Debug)]
|
|
|
|
|
pub enum Variable {
|
|
|
|
|
Literal(i32),
|
2023-07-17 22:57:58 -05:00
|
|
|
IndirectLabel(String),
|
|
|
|
|
DirectLabel(String),
|
2023-07-13 02:00:56 -05:00
|
|
|
Local(usize),
|
|
|
|
|
}
|
2023-07-12 00:53:52 -05:00
|
|
|
|
2023-07-13 02:00:56 -05:00
|
|
|
/// The data associated with a function signature:
|
|
|
|
|
/// - The frame size is in bytes
|
|
|
|
|
/// - The local scope is full of `Variable::Local`s, and contains args and local vars
|
|
|
|
|
/// - The body is just the statements of the function, not the code to move the frame
|
|
|
|
|
/// pointer around (which can't be generated until the variable declarations are all found)
|
|
|
|
|
///
|
|
|
|
|
/// A complete function implementation consists of:
|
|
|
|
|
/// - A label for the entrypoint
|
|
|
|
|
/// - Preamble code to set up the stack frame
|
|
|
|
|
/// - The function body
|
|
|
|
|
///
|
|
|
|
|
/// The stack frame is managed by the function through a global pointer called "frame". When
|
|
|
|
|
/// the function is called, it can assume that all memory after "frame" is free for use (this
|
2023-07-14 00:51:52 -05:00
|
|
|
/// isn't actually true because you can blow out the stack, but within reason it is). So when
|
|
|
|
|
/// you make a call to another function, you need to increment frame by the current frame size,
|
|
|
|
|
/// and then after the other function has returned, decrement it back so that frame again points
|
|
|
|
|
/// at your stack frame. Locals can be found by adding some offset from the frame pointer.
|
2023-07-17 22:57:58 -05:00
|
|
|
///
|
|
|
|
|
/// todo: the allocator problem has (probably) been solved! Make an alloca() that increases the
|
|
|
|
|
/// current frame pointer by some size. To dynamically allocate memory, just put it in the stack
|
|
|
|
|
/// frame of the current fn. All fns return one word and all params are one word long (structs
|
|
|
|
|
/// get passed around by reference)
|
|
|
|
|
///
|
|
|
|
|
/// todo: more of a global todo. Add a register that stores an offset that's added implicitly to
|
|
|
|
|
/// all absolute addresses. This makes it a lot simpler to make relocatable code
|
|
|
|
|
///
|
|
|
|
|
/// alternate todo: add a callr instruction. This moves the basic unit of linking up from "fn" to
|
|
|
|
|
/// "library". Near calls are callr, far calls are call. Globals, reserve a word in the zero page
|
|
|
|
|
/// for a global frame pointer, save / restore that around a far call (using the rstack).
|
|
|
|
|
///
|
|
|
|
|
/// Weird todo: add an abs (and maybe rel) instruction that converts a relative address on the
|
|
|
|
|
/// stack to an absolute one by adding the instruction pointer (of the abs / rel). Make room for it
|
|
|
|
|
/// by replacing sdp / setsdp / setint with just reg / setreg, which will push / pop register
|
|
|
|
|
/// values to the stack, given a register index (0 for data ptr, 1 for rstack, 2 for int enabled,
|
|
|
|
|
/// whatever). For a second removable opcode, how often is peekr actually used?
|
|
|
|
|
///
|
|
|
|
|
/// Another instruction todo: remove the copy instruction (currently a dumb copy-region command)
|
|
|
|
|
/// and replace it with the design from last year with the mode argument... but as a DMA "device"
|
|
|
|
|
/// with an interface in the zero page.
|
2023-07-13 02:00:56 -05:00
|
|
|
#[derive(Clone, PartialEq, Debug, Default)]
|
2023-08-13 18:58:47 -05:00
|
|
|
pub struct CompiledFn {
|
2023-07-14 01:53:10 -05:00
|
|
|
pub label: Label,
|
2023-07-13 02:00:56 -05:00
|
|
|
pub frame_size: usize,
|
|
|
|
|
pub local_scope: Scope,
|
|
|
|
|
pub body: Vec<String>,
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 18:58:47 -05:00
|
|
|
impl CompiledFn {
|
2023-07-13 02:00:56 -05:00
|
|
|
/// Add a name to the local scope, which:
|
|
|
|
|
/// - Increases the size of the stack frame by that much
|
|
|
|
|
/// - Records the offset into the local stack frame where that variable is stored
|
2023-07-14 00:51:52 -05:00
|
|
|
fn add_local(&mut self, name: &str) -> Result<(), CompileError> {
|
|
|
|
|
if let Vacant(e) = self.local_scope.entry(name.into()) {
|
2023-07-13 02:00:56 -05:00
|
|
|
e.insert(Variable::Local(self.frame_size));
|
|
|
|
|
self.frame_size += 3; // todo: variously-sized structs
|
|
|
|
|
Ok(())
|
|
|
|
|
} else {
|
|
|
|
|
Err(CompileError(0, 0, format!("Duplicate name {}", name)))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Emit a string (ideally one instruction, but whatever) to the function body.
|
|
|
|
|
/// This doesn't actually emit anything to output, the body will eventually be emitted
|
|
|
|
|
/// in a final pass by the compiler once all functions are compiled.
|
|
|
|
|
fn emit(&mut self, opcode: &str) {
|
|
|
|
|
self.body.push(String::from(opcode))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A shorthand method to emit something with a `Display` arg, because emitting a
|
|
|
|
|
/// single instruction with a variable (numeric or label) arg is very common.
|
|
|
|
|
fn emit_arg<T: Display>(&mut self, opcode: &str, arg: T) {
|
|
|
|
|
self.body.push(format!("{} {}", opcode, arg))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Maps from names to the variables they represent
|
|
|
|
|
pub type Scope = BTreeMap<String, Variable>;
|
|
|
|
|
|
2023-07-14 01:53:10 -05:00
|
|
|
/// So we don't get confused between string-strings and assembly-label strings
|
|
|
|
|
pub type Label = String;
|
|
|
|
|
|
2023-07-13 02:00:56 -05:00
|
|
|
/// The compiler state:
|
2023-07-12 00:53:52 -05:00
|
|
|
#[derive(Clone, PartialEq, Debug, Default)]
|
|
|
|
|
struct State {
|
2023-07-13 02:00:56 -05:00
|
|
|
/// Used by gensym to generate unique symbols
|
|
|
|
|
pub gensym_index: usize,
|
|
|
|
|
/// The globally-defined names
|
|
|
|
|
pub global_scope: Scope,
|
|
|
|
|
/// The functions
|
2023-08-13 18:58:47 -05:00
|
|
|
pub functions: BTreeMap<String, CompiledFn>,
|
2023-07-14 01:53:10 -05:00
|
|
|
/// The string table
|
|
|
|
|
pub strings: Vec<(Label, String)>,
|
2023-07-12 00:53:52 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl State {
|
|
|
|
|
/// Generate a guaranteed-unique symbolic name
|
2023-07-14 01:53:10 -05:00
|
|
|
fn gensym(&mut self) -> Label {
|
2023-07-12 00:53:52 -05:00
|
|
|
self.gensym_index += 1;
|
|
|
|
|
format!("_gensym_{}", self.gensym_index)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Return whether a name exists in the global scope already
|
|
|
|
|
fn defined(&self, name: &str) -> bool {
|
2023-07-13 02:00:56 -05:00
|
|
|
self.global_scope.contains_key(name)
|
2023-07-12 00:53:52 -05:00
|
|
|
}
|
2023-07-14 00:51:52 -05:00
|
|
|
|
2023-07-14 01:53:10 -05:00
|
|
|
/// Add a symbol to the global namespace, catching name collisions
|
2023-07-14 00:51:52 -05:00
|
|
|
fn add_global<F: Fn(&mut State) -> Variable>(
|
|
|
|
|
&mut self,
|
|
|
|
|
name: &str,
|
|
|
|
|
val: F,
|
|
|
|
|
) -> Result<(), CompileError> {
|
|
|
|
|
if self.defined(name) {
|
|
|
|
|
Err(CompileError(0, 0, format!("name {} already defined", name)))
|
|
|
|
|
} else {
|
|
|
|
|
let val = val(self);
|
|
|
|
|
self.global_scope.insert(name.into(), val);
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-07-14 01:53:10 -05:00
|
|
|
|
|
|
|
|
fn add_string(&mut self, string: &str) -> Label {
|
|
|
|
|
let sym = self.gensym();
|
|
|
|
|
self.strings.push((sym.clone(), string.into()));
|
|
|
|
|
sym
|
|
|
|
|
}
|
2023-07-12 00:53:52 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
trait Compilable {
|
2023-07-13 02:00:56 -05:00
|
|
|
// Every AST node we visit can see the global compiler state (to make unique
|
|
|
|
|
// symbols and reach for global names) as well as optionally the current
|
|
|
|
|
// function (for AST nodes within functions)
|
|
|
|
|
fn process(
|
|
|
|
|
self,
|
|
|
|
|
state: &mut State,
|
2023-08-13 18:58:47 -05:00
|
|
|
function: Option<&mut CompiledFn>,
|
2023-07-13 02:00:56 -05:00
|
|
|
) -> Result<(), CompileError>;
|
2023-07-12 00:53:52 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
impl Compilable for Program {
|
2023-08-13 18:58:47 -05:00
|
|
|
fn process(self, state: &mut State, _: Option<&mut CompiledFn>) -> Result<(), CompileError> {
|
2023-07-12 00:53:52 -05:00
|
|
|
for decl in self.0 {
|
2023-07-13 02:00:56 -05:00
|
|
|
decl.process(state, None)?
|
2023-07-12 00:53:52 -05:00
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
impl Compilable for Declaration {
|
2023-08-13 18:58:47 -05:00
|
|
|
fn process(self, state: &mut State, _: Option<&mut CompiledFn>) -> Result<(), CompileError> {
|
2023-07-12 00:53:52 -05:00
|
|
|
match self {
|
2023-07-13 02:00:56 -05:00
|
|
|
Declaration::Function(f) => f.process(state, None),
|
|
|
|
|
Declaration::Global(g) => g.process(state, None),
|
|
|
|
|
Declaration::Struct(_) => todo!("Structs are not yet supported"),
|
|
|
|
|
Declaration::Const(c) => c.process(state, None),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
impl Compilable for Function {
|
2023-08-13 18:58:47 -05:00
|
|
|
fn process(self, state: &mut State, _: Option<&mut CompiledFn>) -> Result<(), CompileError> {
|
2023-07-13 02:00:56 -05:00
|
|
|
// The signature for this function, which will eventually get added to the state
|
2023-08-13 18:58:47 -05:00
|
|
|
let mut sig = CompiledFn {
|
2023-07-13 02:00:56 -05:00
|
|
|
label: state.gensym(),
|
|
|
|
|
..Default::default()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Add each argument as a local
|
|
|
|
|
for arg in self.args {
|
|
|
|
|
if arg.typename.is_some() {
|
|
|
|
|
todo!("Structs are not yet supported")
|
|
|
|
|
}
|
2023-07-14 00:51:52 -05:00
|
|
|
sig.add_local(&arg.name)?
|
2023-07-13 02:00:56 -05:00
|
|
|
}
|
|
|
|
|
|
2023-07-17 22:57:58 -05:00
|
|
|
// todo we need to store arity somehow in the state, so we can check arglists even
|
|
|
|
|
// with recursive calls. This probably becomes splitting "signature" from "function context"
|
|
|
|
|
// and setting signature immutably right now, passing context down ("block?") and setting it
|
|
|
|
|
// at the end
|
|
|
|
|
|
|
|
|
|
// Add it to the global namespace so we can make recursive calls
|
|
|
|
|
state.add_global(&self.name, |_| Variable::DirectLabel(sig.label.clone()))?;
|
|
|
|
|
|
2023-07-14 01:53:10 -05:00
|
|
|
// Compile each statement:
|
2023-07-13 02:00:56 -05:00
|
|
|
for stmt in self.body.0 {
|
|
|
|
|
match stmt {
|
|
|
|
|
Statement::Return(_) => {}
|
2023-07-14 00:51:52 -05:00
|
|
|
Statement::Assignment(assign) => assign.process(state, Some(&mut sig))?,
|
2023-08-02 21:56:26 -05:00
|
|
|
Statement::Expr(expr) => {
|
2023-08-04 17:27:28 -05:00
|
|
|
expr.process(state, Some(&mut sig))?;
|
|
|
|
|
// Every expr leaves a single-word return value on the stack. In an rvalue this
|
|
|
|
|
// is useful but in a statement it's garbage (because nothing else is about to
|
|
|
|
|
// pick it up) so, drop it:
|
|
|
|
|
sig.emit("pop")
|
2023-07-17 22:57:58 -05:00
|
|
|
}
|
2023-07-14 01:53:10 -05:00
|
|
|
Statement::VarDecl(vardecl) => vardecl.process(state, Some(&mut sig))?,
|
2023-07-13 02:00:56 -05:00
|
|
|
Statement::Conditional(_) | Statement::WhileLoop(_) | Statement::RepeatLoop(_) => {
|
|
|
|
|
todo!()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-17 22:57:58 -05:00
|
|
|
// This can't fail because if it were a dupe name, adding the global would have failed
|
2023-07-14 00:51:52 -05:00
|
|
|
state.functions.insert(self.name.clone(), sig);
|
2023-07-17 22:57:58 -05:00
|
|
|
|
2023-07-14 00:51:52 -05:00
|
|
|
Ok(())
|
2023-07-13 02:00:56 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Look up a name first in the local scope, and failing that in the global scope.
|
|
|
|
|
fn lookup<'a>(name: &str, global_scope: &'a Scope, local_scope: &'a Scope) -> Option<&'a Variable> {
|
|
|
|
|
if let Some(var) = local_scope.get(name) {
|
|
|
|
|
Some(var)
|
|
|
|
|
} else if let Some(var) = global_scope.get(name) {
|
|
|
|
|
Some(var)
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-14 00:51:52 -05:00
|
|
|
///////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
impl Compilable for Assignment {
|
2023-08-13 18:58:47 -05:00
|
|
|
fn process(self, state: &mut State, sig: Option<&mut CompiledFn>) -> Result<(), CompileError> {
|
2023-07-14 00:51:52 -05:00
|
|
|
let Assignment { lvalue, rvalue } = self;
|
|
|
|
|
let sig = sig.expect("Assignment outside function");
|
2023-08-04 17:27:28 -05:00
|
|
|
// First the value, then the address we'll storew it to
|
|
|
|
|
rvalue.process(state, Some(sig))?;
|
2023-07-14 01:53:10 -05:00
|
|
|
lvalue.process(state, Some(sig))?;
|
|
|
|
|
sig.emit("storew");
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
impl Compilable for VarDecl {
|
2023-08-13 18:58:47 -05:00
|
|
|
fn process(self, state: &mut State, sig: Option<&mut CompiledFn>) -> Result<(), CompileError> {
|
2023-08-14 00:26:02 -05:00
|
|
|
let sig = sig.expect("Var declaration outside function");
|
2023-08-04 17:27:28 -05:00
|
|
|
if self.typename.is_some() || self.size.is_some() {
|
|
|
|
|
todo!("Structs and arrays are not yet supported")
|
|
|
|
|
}
|
|
|
|
|
if let Some(initial) = self.initial {
|
|
|
|
|
// If it's got an initial value, we have to compile that before we add
|
|
|
|
|
// the name to scope, or else UB will ensue if it refers to itself:
|
|
|
|
|
initial.process(state, Some(sig))?;
|
|
|
|
|
// But then add it to scope and assign:
|
|
|
|
|
sig.add_local(&self.name)?;
|
|
|
|
|
// We'll just whip up an lvalue real quick...
|
|
|
|
|
Lvalue::from(self.name).process(state, Some(sig))?;
|
|
|
|
|
sig.emit("storew"); // And store the initial value there
|
|
|
|
|
} else {
|
|
|
|
|
// Otherwise, just add it to scope and leave garbage in there:
|
|
|
|
|
sig.add_local(&self.name)?;
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////
|
|
|
|
|
|
2023-07-13 02:00:56 -05:00
|
|
|
/// Evaluate an lvalue and leave its address on the stack (ready to be consumed by storew)
|
2023-07-14 00:51:52 -05:00
|
|
|
impl Compilable for Lvalue {
|
2023-08-13 18:58:47 -05:00
|
|
|
fn process(self, state: &mut State, sig: Option<&mut CompiledFn>) -> Result<(), CompileError> {
|
2023-07-14 00:51:52 -05:00
|
|
|
let global_scope = &state.global_scope;
|
|
|
|
|
let mut sig = sig.expect("lvalue outside a function");
|
2023-08-04 17:27:28 -05:00
|
|
|
|
2023-08-13 23:55:49 -05:00
|
|
|
match *(self.0.0) {
|
|
|
|
|
// A bunch of different things that aren't allowed
|
|
|
|
|
Expr::Number(_) |
|
|
|
|
|
Expr::Neg(_) |
|
|
|
|
|
Expr::Not(_) |
|
|
|
|
|
Expr::Address(_) |
|
|
|
|
|
Expr::Call(_, _) |
|
2023-08-14 00:26:02 -05:00
|
|
|
Expr::Infix(_, _, _) |
|
|
|
|
|
Expr::String(_) => {
|
2023-08-13 23:55:49 -05:00
|
|
|
Err(CompileError(0, 0, String::from("Not a valid lvalue")))
|
|
|
|
|
}
|
2023-08-13 19:29:50 -05:00
|
|
|
|
2023-08-13 23:55:49 -05:00
|
|
|
// Names we look up and leave the address on the stack:
|
|
|
|
|
Expr::Name(name) => {
|
2023-08-13 19:29:50 -05:00
|
|
|
if let Some(var) = lookup(&name, global_scope, &sig.local_scope) {
|
|
|
|
|
match var {
|
|
|
|
|
Variable::Literal(_) | Variable::DirectLabel(_) => {
|
|
|
|
|
// Direct labels are (probably) functions, the important part is the
|
|
|
|
|
// label itself, which we can't alter, so, error:
|
|
|
|
|
Err(CompileError(0, 0, format!("Invalid lvalue {}", name)))
|
|
|
|
|
}
|
|
|
|
|
Variable::IndirectLabel(label) => {
|
|
|
|
|
// Indirect labels are variables, the label is where the data is stored,
|
|
|
|
|
// so we push that label so we can store stuff there
|
|
|
|
|
let label = label.clone();
|
|
|
|
|
sig.emit_arg("push", label);
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
Variable::Local(offset) => {
|
|
|
|
|
let offset = *offset;
|
|
|
|
|
sig.emit_arg("loadw", "frame");
|
|
|
|
|
if offset > 0 {
|
|
|
|
|
sig.emit_arg("add", offset);
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2023-07-13 02:00:56 -05:00
|
|
|
}
|
2023-08-13 19:29:50 -05:00
|
|
|
} else {
|
|
|
|
|
Err(CompileError(0, 0, format!("Unknown name {}", name)))
|
2023-07-13 02:00:56 -05:00
|
|
|
}
|
|
|
|
|
}
|
2023-08-13 23:55:49 -05:00
|
|
|
// Derefs are just evaluating the expr and leaving its value (an address) on the stack
|
|
|
|
|
Expr::Deref(BoxExpr(expr)) => {
|
2023-08-13 19:29:50 -05:00
|
|
|
(*expr).process(state, Some(sig))
|
|
|
|
|
}
|
2023-08-13 23:55:49 -05:00
|
|
|
Expr::Subscript(_, _) |
|
|
|
|
|
Expr::Member(_, _) => {
|
|
|
|
|
Err(CompileError(0, 0, String::from("Arrays and structs are not yet supported")))
|
|
|
|
|
}
|
2023-07-13 02:00:56 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-14 00:51:52 -05:00
|
|
|
///////////////////////////////////////////////////////////
|
|
|
|
|
|
2023-07-13 02:00:56 -05:00
|
|
|
/// Evaluate an expression in the context of a local scope. The runtime brother to eval_const.
|
|
|
|
|
/// This recursively evaluates a Node and leaves its value on the stack.
|
2023-08-02 21:56:26 -05:00
|
|
|
impl Compilable for Expr {
|
2023-08-13 18:58:47 -05:00
|
|
|
fn process(self, state: &mut State, sig: Option<&mut CompiledFn>) -> Result<(), CompileError> {
|
2023-08-04 17:27:28 -05:00
|
|
|
let mut sig = sig.expect("Non-const expression outside a function");
|
|
|
|
|
let global_scope = &state.global_scope;
|
|
|
|
|
|
|
|
|
|
// First, a sanity check: try and eval_const this. If it's something incredibly basic
|
|
|
|
|
// that just becomes an i32, then we don't need to do anything else:
|
|
|
|
|
if let Ok(val) = eval_const(self.clone(), &state.global_scope) {
|
|
|
|
|
sig.emit_arg("push", val);
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Okay, looks like we need something that's in scope. Let's recurse:
|
|
|
|
|
match self {
|
|
|
|
|
Expr::Number(n) => {
|
|
|
|
|
// Numbers are just pushed as literals
|
|
|
|
|
sig.body.push(format!("push {}", n));
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
Expr::Name(name) => {
|
|
|
|
|
match lookup(&name, global_scope, &sig.local_scope) {
|
|
|
|
|
// Names are treated differently depending on what they are
|
|
|
|
|
Some(Variable::Literal(val)) => {
|
|
|
|
|
// Names of constants are just that number
|
|
|
|
|
sig.emit_arg("push", *val);
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
Some(Variable::IndirectLabel(label)) => {
|
|
|
|
|
// Names pointing at labels are loaded (rvalue; for lvalues they aren't)
|
|
|
|
|
// Indirect labels are the address of where the value is stored (a var, .db)
|
|
|
|
|
sig.emit_arg("loadw", label.clone());
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
Some(Variable::DirectLabel(label)) => {
|
|
|
|
|
// Direct labels are like functions, the label itself is the value, so just
|
|
|
|
|
// push it:
|
|
|
|
|
sig.emit_arg("push", label.clone());
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
Some(Variable::Local(offset)) => {
|
|
|
|
|
// Names of locals are added from the frame pointer
|
|
|
|
|
let offset = *offset;
|
|
|
|
|
sig.emit("loadw frame");
|
|
|
|
|
if offset > 0 {
|
|
|
|
|
sig.emit_arg("add", offset);
|
|
|
|
|
sig.emit("loadw");
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
None => Err(CompileError(0, 0, format!("Unknown name {}", name))),
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-04 21:38:09 -05:00
|
|
|
Expr::Neg(e) => {
|
2023-08-04 17:27:28 -05:00
|
|
|
(*e.0).process(state, Some(sig))?;
|
2023-08-04 21:38:09 -05:00
|
|
|
// To arithmetically negate something, invert and increment (2s complement)
|
|
|
|
|
sig.emit("xor -1");
|
|
|
|
|
sig.emit("add 1");
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
Expr::Not(e) => {
|
|
|
|
|
(*e.0).process(state, Some(sig))?;
|
|
|
|
|
sig.emit("not");
|
2023-08-04 17:27:28 -05:00
|
|
|
Ok(())
|
|
|
|
|
}
|
2023-08-04 21:38:09 -05:00
|
|
|
// Handling addresses is very easy because processing an lvalue leaves the address on the stack
|
|
|
|
|
Expr::Address(lvalue) => lvalue.process(state, Some(sig)),
|
2023-08-13 18:58:47 -05:00
|
|
|
Expr::Deref(BoxExpr(e)) => {
|
|
|
|
|
(*e).process(state, Some(sig))?;
|
|
|
|
|
sig.emit("loadw");
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2023-08-14 00:26:02 -05:00
|
|
|
Expr::String(string) => {
|
|
|
|
|
let label = state.add_string(&string);
|
|
|
|
|
sig.emit_arg("push", label);
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2023-08-05 11:07:09 -05:00
|
|
|
Expr::Call(_, _) => todo!(),
|
|
|
|
|
Expr::Subscript(_, _) => todo!("Structs and arrays are not yen supported"),
|
|
|
|
|
Expr::Member(_, _) => todo!("Structs and arrays are not yen supported"),
|
2023-08-04 17:27:28 -05:00
|
|
|
Expr::Infix(lhs, op, rhs) => {
|
|
|
|
|
// Recurse on expressions, handling operators
|
|
|
|
|
(*lhs.0).process(state, Some(&mut sig))?;
|
|
|
|
|
(*rhs.0).process(state, Some(&mut sig))?;
|
|
|
|
|
match op {
|
|
|
|
|
// Basic math
|
|
|
|
|
Operator::Add => sig.emit("add"),
|
|
|
|
|
Operator::Sub => sig.emit("sub"),
|
|
|
|
|
Operator::Mul => sig.emit("mul"),
|
|
|
|
|
Operator::Div => sig.emit("div"),
|
|
|
|
|
Operator::Mod => sig.emit("mod"),
|
|
|
|
|
Operator::And => {
|
|
|
|
|
// Vulcan "and" is bitwise, so we need to flag-ify both args to make it logical
|
|
|
|
|
sig.emit("gt 0");
|
|
|
|
|
sig.emit("swap");
|
|
|
|
|
sig.emit("gt 0");
|
|
|
|
|
sig.emit("and");
|
|
|
|
|
}
|
|
|
|
|
Operator::Or => {
|
|
|
|
|
// Same as and, flag-ify both args
|
|
|
|
|
sig.emit("gt 0");
|
|
|
|
|
sig.emit("swap");
|
|
|
|
|
sig.emit("gt 0");
|
|
|
|
|
sig.emit("or");
|
|
|
|
|
}
|
|
|
|
|
Operator::BitAnd => sig.emit("and"),
|
|
|
|
|
Operator::BitOr => sig.emit("or"),
|
|
|
|
|
Operator::Xor => sig.emit("xor"),
|
|
|
|
|
Operator::Lt => sig.emit("alt"),
|
|
|
|
|
Operator::Le => {
|
|
|
|
|
// LE and GE are the inverses of GT and LT (arithmetic versions)
|
|
|
|
|
sig.emit("agt");
|
|
|
|
|
sig.emit("not");
|
|
|
|
|
}
|
|
|
|
|
Operator::Gt => sig.emit("agt"),
|
|
|
|
|
Operator::Ge => {
|
|
|
|
|
sig.emit("alt");
|
|
|
|
|
sig.emit("not");
|
|
|
|
|
}
|
|
|
|
|
Operator::Eq => {
|
|
|
|
|
sig.emit("xor");
|
|
|
|
|
sig.emit("not");
|
|
|
|
|
}
|
|
|
|
|
Operator::Ne => sig.emit("xor"),
|
|
|
|
|
Operator::Lshift => sig.emit("lshift"),
|
|
|
|
|
Operator::Rshift => sig.emit("arshift"),
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-07-12 00:53:52 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
impl Compilable for Global {
|
2023-08-13 18:58:47 -05:00
|
|
|
fn process(self, state: &mut State, _: Option<&mut CompiledFn>) -> Result<(), CompileError> {
|
2023-07-12 00:53:52 -05:00
|
|
|
if self.typename.is_some() || self.size.is_some() {
|
|
|
|
|
todo!("Structs and arrays are not yet supported")
|
|
|
|
|
}
|
2023-07-17 22:57:58 -05:00
|
|
|
state.add_global(&self.name, |s| Variable::IndirectLabel(s.gensym()))
|
2023-07-12 00:53:52 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
impl Compilable for Const {
|
2023-08-13 18:58:47 -05:00
|
|
|
fn process(self, state: &mut State, _: Option<&mut CompiledFn>) -> Result<(), CompileError> {
|
2023-08-04 17:27:28 -05:00
|
|
|
let var = if self.string.is_some() {
|
|
|
|
|
// If it's a string, add it to the string table
|
|
|
|
|
Variable::DirectLabel(state.add_string(&self.string.unwrap()))
|
|
|
|
|
} else if let Some(expr) = self.value {
|
|
|
|
|
// Otherwise eval_const it
|
|
|
|
|
Variable::Literal(eval_const(expr, &state.global_scope)?)
|
|
|
|
|
} else {
|
|
|
|
|
unreachable!()
|
|
|
|
|
};
|
|
|
|
|
// Add it to the global namespace
|
|
|
|
|
state.add_global(&self.name, |_| var.clone())
|
2023-07-12 00:53:52 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
fn to_flag(val: bool) -> i32 {
|
|
|
|
|
if val {
|
|
|
|
|
1
|
|
|
|
|
} else {
|
|
|
|
|
0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Evaluate a node in a static context, for const definitions and array sizes, that sort of thing.
|
2023-08-02 21:56:26 -05:00
|
|
|
pub fn eval_const(expr: Expr, scope: &Scope) -> Result<i32, CompileError> {
|
2023-08-04 17:27:28 -05:00
|
|
|
match expr {
|
|
|
|
|
Expr::Number(n) => Ok(n), // That was easy
|
2023-08-04 21:38:09 -05:00
|
|
|
Expr::Name(n) => {
|
|
|
|
|
if let Some(Variable::Literal(val)) = scope.get(&n) {
|
2023-08-04 17:27:28 -05:00
|
|
|
Ok(*val)
|
|
|
|
|
} else {
|
|
|
|
|
Err(CompileError(0, 0, format!("Unknown const {}", n)))
|
|
|
|
|
}
|
2023-08-04 21:38:09 -05:00
|
|
|
}
|
|
|
|
|
Expr::Neg(e) => {
|
2023-08-04 17:27:28 -05:00
|
|
|
let val = eval_const(*e.0, scope)?;
|
2023-08-04 21:38:09 -05:00
|
|
|
Ok(-val)
|
|
|
|
|
}
|
|
|
|
|
Expr::Not(e) => {
|
|
|
|
|
let val = eval_const(*e.0, scope)?;
|
|
|
|
|
Ok(if val != 0 { 0 } else { 1 })
|
2023-08-04 17:27:28 -05:00
|
|
|
}
|
2023-08-14 00:26:02 -05:00
|
|
|
// A note about Expr::String here: 'const foo="banana"' won't hit this point; it'll be
|
|
|
|
|
// (currently) parsed as a special case of const. The only things that will hit this are
|
|
|
|
|
// const expressions that include strings, like '"foo"[2]' or something. Those can't be
|
|
|
|
|
// (generally) calculated at compile time, so, they're an error.
|
|
|
|
|
Expr::Address(_) | Expr::Deref(_) | Expr::String(_) => Err(CompileError(
|
2023-08-04 21:38:09 -05:00
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
String::from("Addresses are not known at compile time"),
|
|
|
|
|
)),
|
2023-08-05 11:07:09 -05:00
|
|
|
Expr::Call(_, _) | Expr::Subscript(_, _) | Expr::Member(_, _) => Err(CompileError(
|
2023-08-04 21:38:09 -05:00
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
String::from("Constants must be statically defined"),
|
|
|
|
|
)),
|
2023-08-04 17:27:28 -05:00
|
|
|
Expr::Infix(lhs, op, rhs) => {
|
|
|
|
|
let lhs = eval_const(*lhs.0, scope)?;
|
|
|
|
|
let rhs = eval_const(*rhs.0, scope)?;
|
|
|
|
|
match op {
|
|
|
|
|
Operator::Add => Ok(lhs + rhs),
|
|
|
|
|
Operator::Sub => Ok(lhs - rhs),
|
|
|
|
|
Operator::Mul => Ok(lhs * rhs),
|
|
|
|
|
Operator::Div => Ok(lhs / rhs),
|
|
|
|
|
Operator::Mod => Ok(lhs % rhs),
|
|
|
|
|
Operator::And => Ok(to_flag(lhs != 0 && rhs != 0)),
|
|
|
|
|
Operator::Or => Ok(to_flag(lhs != 0 || rhs != 0)),
|
|
|
|
|
Operator::BitAnd => Ok(lhs & rhs),
|
|
|
|
|
Operator::BitOr => Ok(lhs | rhs),
|
|
|
|
|
Operator::Xor => Ok(lhs ^ rhs),
|
|
|
|
|
Operator::Lt => Ok(to_flag(lhs < rhs)),
|
|
|
|
|
Operator::Le => Ok(to_flag(lhs <= rhs)),
|
|
|
|
|
Operator::Gt => Ok(to_flag(lhs > rhs)),
|
|
|
|
|
Operator::Ge => Ok(to_flag(lhs >= rhs)),
|
|
|
|
|
Operator::Eq => Ok(to_flag(lhs == rhs)),
|
|
|
|
|
Operator::Ne => Ok(to_flag(lhs != rhs)),
|
|
|
|
|
Operator::Lshift => Ok(lhs << rhs),
|
|
|
|
|
Operator::Rshift => Ok(lhs >> rhs),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-07-12 00:53:52 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod test {
|
|
|
|
|
use super::*;
|
2023-07-14 00:51:52 -05:00
|
|
|
use crate::forge_parser::parse;
|
2023-07-12 00:53:52 -05:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_eval_const() {
|
2023-07-13 02:00:56 -05:00
|
|
|
let empty_scope = Scope::new();
|
2023-08-02 21:56:26 -05:00
|
|
|
let to_expr = |s| Expr::parse(s).unwrap();
|
2023-07-12 00:53:52 -05:00
|
|
|
|
|
|
|
|
// Basic arithmetic
|
2023-08-02 21:56:26 -05:00
|
|
|
assert_eq!(eval_const(to_expr("2 * 3 + 4"), &empty_scope), Ok(10));
|
|
|
|
|
assert_eq!(eval_const(to_expr("1 + -2"), &empty_scope), Ok(-1));
|
|
|
|
|
assert_eq!(eval_const(to_expr("1 << 3"), &empty_scope), Ok(8));
|
2023-07-12 00:53:52 -05:00
|
|
|
|
|
|
|
|
// Names
|
2023-07-13 02:00:56 -05:00
|
|
|
let scope: Scope = [
|
|
|
|
|
("foo".into(), Variable::Literal(10)),
|
|
|
|
|
("bar".into(), Variable::Literal(5)),
|
|
|
|
|
]
|
|
|
|
|
.into();
|
2023-08-02 21:56:26 -05:00
|
|
|
assert_eq!(eval_const(to_expr("foo + 5"), &scope), Ok(15));
|
|
|
|
|
assert_eq!(eval_const(to_expr("bar * foo"), &scope), Ok(50));
|
2023-07-12 00:53:52 -05:00
|
|
|
|
|
|
|
|
// Error
|
|
|
|
|
assert_eq!(
|
2023-08-02 21:56:26 -05:00
|
|
|
eval_const(to_expr("nope"), &scope),
|
2023-07-12 00:53:52 -05:00
|
|
|
Err(CompileError(0, 0, String::from("Unknown const nope")))
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_const_decl() {
|
|
|
|
|
let mut state = State::default();
|
|
|
|
|
parse("const foo = 17 + 3;")
|
|
|
|
|
.unwrap()
|
2023-07-13 02:00:56 -05:00
|
|
|
.process(&mut state, None)
|
2023-07-12 00:53:52 -05:00
|
|
|
.unwrap();
|
2023-07-13 02:00:56 -05:00
|
|
|
assert_eq!(
|
|
|
|
|
state.global_scope,
|
|
|
|
|
[("foo".into(), Variable::Literal(20))].into()
|
|
|
|
|
)
|
2023-07-12 00:53:52 -05:00
|
|
|
}
|
|
|
|
|
|
2023-08-14 00:36:26 -05:00
|
|
|
#[test]
|
|
|
|
|
fn test_string_const() {
|
|
|
|
|
let mut state = State::default();
|
|
|
|
|
parse("const foo = \"bar\";")
|
|
|
|
|
.unwrap()
|
|
|
|
|
.process(&mut state, None)
|
|
|
|
|
.unwrap();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
state.global_scope,
|
|
|
|
|
[("foo".into(), Variable::DirectLabel("_gensym_1".into()))].into()
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-12 00:53:52 -05:00
|
|
|
#[test]
|
|
|
|
|
fn test_global_decl() {
|
|
|
|
|
let mut state = State::default();
|
2023-07-13 02:00:56 -05:00
|
|
|
parse("global a;")
|
|
|
|
|
.unwrap()
|
|
|
|
|
.process(&mut state, None)
|
|
|
|
|
.unwrap();
|
2023-07-12 00:53:52 -05:00
|
|
|
assert_eq!(
|
|
|
|
|
state.global_scope,
|
2023-07-17 22:57:58 -05:00
|
|
|
[("a".into(), Variable::IndirectLabel("_gensym_1".into()))].into()
|
2023-07-12 00:53:52 -05:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_name_collision() {
|
|
|
|
|
let mut state = State::default();
|
|
|
|
|
assert!(parse("const a = 7; global a;")
|
|
|
|
|
.unwrap()
|
2023-07-13 02:00:56 -05:00
|
|
|
.process(&mut state, None)
|
2023-07-12 00:53:52 -05:00
|
|
|
.is_err());
|
|
|
|
|
}
|
2023-07-13 02:00:56 -05:00
|
|
|
|
2023-08-13 18:58:47 -05:00
|
|
|
fn body_as_string(sig: &CompiledFn) -> String {
|
2023-07-13 02:00:56 -05:00
|
|
|
sig.body.join("\n")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_basic_fns() {
|
|
|
|
|
let mut state = State::default();
|
|
|
|
|
parse("fn blah(a, b) { b = 17 + a; }")
|
|
|
|
|
.unwrap()
|
|
|
|
|
.process(&mut state, None)
|
|
|
|
|
.expect("Failed to compile");
|
|
|
|
|
let body = body_as_string(state.functions.get("blah").unwrap());
|
|
|
|
|
assert_eq!(
|
|
|
|
|
body,
|
|
|
|
|
vec![
|
|
|
|
|
"push 17", // Start calculating the rvalue, push the literal
|
|
|
|
|
"loadw frame", // This is looking up the "a" arg, at frame + 0
|
|
|
|
|
"add", // 17 + a
|
|
|
|
|
"loadw frame", // Calculate the lvalue
|
2023-07-14 00:51:52 -05:00
|
|
|
"add 3", // "b" arg is frame + 3
|
2023-07-13 02:00:56 -05:00
|
|
|
"storew", // Finally store
|
|
|
|
|
]
|
|
|
|
|
.join("\n")
|
|
|
|
|
)
|
|
|
|
|
}
|
2023-07-14 00:51:52 -05:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_var_decls() {
|
|
|
|
|
let mut state = State::default();
|
|
|
|
|
parse("fn blah() { var a; var b = 7; a = b * 2; }")
|
|
|
|
|
.unwrap()
|
|
|
|
|
.process(&mut state, None)
|
|
|
|
|
.expect("Failed to compile");
|
|
|
|
|
let body = body_as_string(state.functions.get("blah").unwrap());
|
|
|
|
|
assert_eq!(
|
|
|
|
|
body,
|
|
|
|
|
vec![
|
|
|
|
|
"push 7", // Start calculating the rvalue, push the literal
|
|
|
|
|
"loadw frame", // "b" is the second local var at frame - 3
|
|
|
|
|
"add 3",
|
|
|
|
|
"storew", // Do the initialization
|
|
|
|
|
"loadw frame", // Now we're evaluating b * 2
|
|
|
|
|
"add 3", // b is at frame + 3...
|
|
|
|
|
"loadw", // load it to the stack
|
|
|
|
|
"push 2",
|
|
|
|
|
"mul", // b * 2 evaluated
|
|
|
|
|
"loadw frame", // Loading "a" as an lvalue
|
|
|
|
|
"storew", // doing the assignment
|
|
|
|
|
]
|
|
|
|
|
.join("\n")
|
|
|
|
|
)
|
|
|
|
|
}
|
2023-07-14 01:53:10 -05:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_literal_strings() {
|
|
|
|
|
let mut state = State::default();
|
2023-08-04 17:27:28 -05:00
|
|
|
parse("const s1 = \"foo\"; fn blah() { var x; x = \"bar\"; var y = \"norp\"; }")
|
2023-07-14 01:53:10 -05:00
|
|
|
.unwrap()
|
|
|
|
|
.process(&mut state, None)
|
|
|
|
|
.expect("Failed to compile");
|
|
|
|
|
let body = body_as_string(state.functions.get("blah").unwrap());
|
|
|
|
|
assert_eq!(
|
|
|
|
|
state.strings,
|
|
|
|
|
vec![
|
|
|
|
|
("_gensym_1".into(), "foo".into()),
|
2023-08-04 17:27:28 -05:00
|
|
|
("_gensym_3".into(), "bar".into()), // gensym 2 is the entrypoint of blah()
|
|
|
|
|
("_gensym_4".into(), "norp".into())
|
2023-07-14 01:53:10 -05:00
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
body,
|
2023-08-04 17:27:28 -05:00
|
|
|
vec![
|
|
|
|
|
"push _gensym_3",
|
|
|
|
|
"loadw frame",
|
|
|
|
|
"storew", // the assignment for x
|
|
|
|
|
"push _gensym_4",
|
|
|
|
|
"loadw frame",
|
|
|
|
|
"add 3", // the address of y (frame + 3) and put gensym_4 in it
|
2023-08-04 21:38:09 -05:00
|
|
|
"storew"
|
|
|
|
|
]
|
|
|
|
|
.join("\n")
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_addresses() {
|
|
|
|
|
let mut state = State::default();
|
|
|
|
|
parse("fn blah() { var x = 3; var y = &x; }")
|
|
|
|
|
.unwrap()
|
|
|
|
|
.process(&mut state, None)
|
|
|
|
|
.expect("Failed to compile");
|
|
|
|
|
let body = body_as_string(state.functions.get("blah").unwrap());
|
|
|
|
|
assert_eq!(
|
|
|
|
|
body,
|
|
|
|
|
vec![
|
|
|
|
|
"push 3",
|
|
|
|
|
"loadw frame",
|
|
|
|
|
"storew", // the assignment for x
|
|
|
|
|
"loadw frame", // The addr of x
|
|
|
|
|
"loadw frame",
|
|
|
|
|
"add 3", // the address of y (frame + 3) and put the addr of x (frame) in it
|
|
|
|
|
"storew"
|
|
|
|
|
]
|
|
|
|
|
.join("\n")
|
2023-08-04 17:27:28 -05:00
|
|
|
)
|
2023-07-14 01:53:10 -05:00
|
|
|
}
|
2023-08-13 19:29:50 -05:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_derefs() {
|
|
|
|
|
let mut state = State::default();
|
|
|
|
|
parse("fn blah() { var x = 3; *1000 = *x; }")
|
|
|
|
|
.unwrap()
|
|
|
|
|
.process(&mut state, None)
|
|
|
|
|
.expect("Failed to compile");
|
|
|
|
|
let body = body_as_string(state.functions.get("blah").unwrap());
|
|
|
|
|
assert_eq!(
|
|
|
|
|
body,
|
|
|
|
|
vec![
|
|
|
|
|
"push 3",
|
|
|
|
|
"loadw frame",
|
|
|
|
|
"storew", // the assignment for x
|
|
|
|
|
"loadw frame", // Now we're compiling *x, so load x's value, which is 3
|
|
|
|
|
"loadw", // Then load the value at 3
|
|
|
|
|
"push 1000", // Push the addr 1000, for the lvalue
|
|
|
|
|
"storew" // Store whatever's at 3 to 1000
|
|
|
|
|
]
|
|
|
|
|
.join("\n")
|
|
|
|
|
)
|
2023-08-14 00:36:26 -05:00
|
|
|
}
|
2023-08-13 19:29:50 -05:00
|
|
|
|
2023-08-14 00:36:26 -05:00
|
|
|
#[test]
|
|
|
|
|
fn test_string_exprs() {
|
|
|
|
|
let mut state = State::default();
|
|
|
|
|
parse("const foo = \"foo\"; fn blah() { var x = \"bar\" + 3; }")
|
|
|
|
|
.unwrap()
|
|
|
|
|
.process(&mut state, None)
|
|
|
|
|
.expect("Failed to compile");
|
|
|
|
|
let body = body_as_string(state.functions.get("blah").unwrap());
|
|
|
|
|
assert_eq!(
|
|
|
|
|
body,
|
|
|
|
|
vec![
|
|
|
|
|
"push _gensym_3", // 1 is the label in the string table for "foo", 2 for "blah,"
|
|
|
|
|
"push 3", // so 3 is the string "bar"
|
|
|
|
|
"add", // Add 3 to that address
|
|
|
|
|
"loadw frame", // Store it in the first var
|
|
|
|
|
"storew"
|
|
|
|
|
]
|
|
|
|
|
.join("\n")
|
|
|
|
|
)
|
2023-08-13 19:29:50 -05:00
|
|
|
}
|
2023-07-12 00:53:52 -05:00
|
|
|
}
|