Skip to content

Commit

Permalink
First stage on macros
Browse files Browse the repository at this point in the history
Signed-off-by: Miquel Sabaté Solà <[email protected]>
  • Loading branch information
mssola committed Oct 15, 2024
1 parent 02c4021 commit ca58683
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 14 deletions.
116 changes: 102 additions & 14 deletions lib/xixanta/src/assembler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::parser::Parser;
use std::cmp::Ordering;
use std::collections::HashMap;
use std::io::Read;
use std::ops::Range;

/// A Bundle represents a set of bytes that can be encoded as binary data.
#[derive(Debug, Default, Clone)]
Expand Down Expand Up @@ -48,10 +49,17 @@ pub enum Stage {
Bundling,
}

#[derive(Debug)]
pub struct Macro {
nodes: Range<usize>,
args: Vec<String>,
}

pub struct Assembler {
context: Context,
literal_mode: Option<LiteralMode>,
stage: Stage,
macros: HashMap<String, Macro>,
}

impl Assembler {
Expand All @@ -69,6 +77,7 @@ impl Assembler {
context: Context::new(),
literal_mode: None,
stage: Stage::Init,
macros: HashMap::new(),
}
}

Expand All @@ -82,11 +91,11 @@ impl Assembler {
}

// Build the context by iterating over the parsed nodes and checking
// where scopes start/end, evaluating values for variables, etc.
// where scopes start/end, evaluating values for variables, labels, etc.
self.stage = Stage::Context;
self.eval_context(&parser.nodes)?;

// TODO: unroll macros, etc.
// TODO: unroll macros, fill out labels, etc.
self.stage = Stage::Unrolling;

// Finally convert the relevant nodes into binary bundles which can be
Expand All @@ -97,19 +106,61 @@ impl Assembler {

pub fn eval_context(&mut self, nodes: &Vec<PNode>) -> Result<(), Vec<Error>> {
let mut errors = Vec::new();
let mut current_macro = None;

for node in nodes {
for (idx, node) in nodes.iter().enumerate() {
// TODO: initilize labels on each scope.
match node.node_type {
NodeType::Assignment => match self.evaluate_node(node.left.as_ref().unwrap()) {
Ok(value) => {
if let Err(err) = self.context.set_variable(&node.value, &value) {
errors.push(Error::Context(err));
NodeType::Assignment => {
// TODO: in fact, we cannot have assignments in many places.
if current_macro.is_some() {
errors.push(Error::Eval(EvalError {
message: "cannot have assignments inside of macro definitions"
.to_string(),
line: node.value.line,
}));
continue;
}
match self.evaluate_node(node.left.as_ref().unwrap()) {
Ok(value) => {
if let Err(err) = self.context.set_variable(&node.value, &value) {
errors.push(Error::Context(err));
}
}
Err(e) => errors.push(Error::Eval(e)),
}
Err(e) => errors.push(Error::Eval(e)),
},
}
NodeType::Control => {
// TODO: prevent nesting of control statements depending on
// a definition (e.g. .macro's cannot be nested inside of
// another control statement, but .if yes).
let id = node.value.value.as_str();

if id == ".macro" {
// TODO: macros are only on the global scope.
//
// TODO: boy this is ugly. In fact, this stupid shit if
// current_macro might not be relevant anymore.
current_macro = Some(&node.left.as_ref().unwrap().value);
self.macros
.entry(node.left.as_ref().unwrap().value.value.clone())
.or_insert(Macro {
nodes: Range {
start: idx + 1,
end: idx + 1,
},
args: vec![], // TODO
});
} else if id == ".endmacro" {
if let Some(name) = current_macro {
self.macros
.entry(name.value.clone())
.and_modify(|m| m.nodes.end = idx - 1);
}
current_macro = None;
}
if let Err(err) = self.context.change_context(node) {
// TODO: forbid if inside_macro
errors.push(Error::Context(err));
}
}
Expand All @@ -127,18 +178,55 @@ impl Assembler {
pub fn bundle(&mut self, nodes: &Vec<PNode>) -> Result<Vec<Bundle>, Vec<Error>> {
let mut bundles = Vec::new();
let mut errors = Vec::new();
let mut inside_macro = false;

for node in nodes {
match node.node_type {
NodeType::Instruction => match self.evaluate_node(node) {
Ok(bundle) => bundles.push(bundle),
Err(e) => errors.push(Error::Eval(e)),
},
NodeType::Instruction => {
if !inside_macro {
match self.evaluate_node(node) {
Ok(bundle) => bundles.push(bundle),
Err(e) => errors.push(Error::Eval(e)),
}
}
}
NodeType::Control => {
if let Err(err) = self.context.change_context(node) {
let id = node.value.value.as_str();

// TODO: skip macros altogether.
if id == ".macro" {
if inside_macro {
errors.push(Error::Eval(EvalError {
message: "nesting macros is forbidden".to_string(),
line: node.value.line,
}));
continue;
}
inside_macro = true;
} else if id == ".endmacro" {
inside_macro = false;
} else if let Err(err) = self.context.change_context(node) {
// TODO: forbid if inside_macro
errors.push(Error::Context(err));
}
}
NodeType::Value | NodeType::Call => match self.macros.get(&node.value.value) {
Some(m) => {
for node in nodes.get(m.nodes.start..=m.nodes.end).unwrap_or_default() {
match self.evaluate_node(node) {
Ok(bundle) => bundles.push(bundle),
Err(e) => errors.push(Error::Eval(e)),
}
}
}
None => errors.push(Error::Eval(EvalError {
line: node.value.line,
message: format!(
"could not find a macro with the name {}",
node.value.value
),
})),
},
_ => {}
}
}
Expand Down
6 changes: 6 additions & 0 deletions lib/xixanta/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,9 @@ impl Context {
}
}
}

impl Default for Context {
fn default() -> Self {
Self::new()
}
}

0 comments on commit ca58683

Please sign in to comment.