aboutsummaryrefslogtreecommitdiff
path: root/lib/xixanta/src/context.rs
diff options
context:
space:
mode:
Diffstat (limited to 'lib/xixanta/src/context.rs')
-rw-r--r--lib/xixanta/src/context.rs180
1 files changed, 131 insertions, 49 deletions
diff --git a/lib/xixanta/src/context.rs b/lib/xixanta/src/context.rs
index 2ac6da8..8e77094 100644
--- a/lib/xixanta/src/context.rs
+++ b/lib/xixanta/src/context.rs
@@ -1,29 +1,22 @@
-use crate::instruction::Bundle;
-use crate::parser::PNode;
+use crate::assembler::Bundle;
+use crate::errors::{ContextError, ContextErrorReason};
+use crate::node::{PNode, PString};
+use crate::opcodes::CONTROL_FUNCTIONS;
use std::collections::HashMap;
+// The name of the global context as used internally..
const GLOBAL_CONTEXT: &str = "Global";
-#[derive(Debug)]
-pub struct PValue {
- pub node: PNode,
- pub value: Bundle,
- pub label: bool,
-}
-
+/// Context holds information about the different scopes being defined, the
+/// current scope, and has a map of all the variables defined for each scope.
#[derive(Debug)]
pub struct Context {
stack: Vec<String>,
- map: HashMap<String, HashMap<String, PValue>>,
-}
-
-impl Default for Context {
- fn default() -> Self {
- Context::new()
- }
+ map: HashMap<String, HashMap<String, Bundle>>,
}
impl Context {
+ /// Returns a new empty context.
pub fn new() -> Self {
Context {
stack: vec![],
@@ -31,60 +24,149 @@ impl Context {
}
}
- pub fn find(&self, name: &str) -> Option<&HashMap<String, PValue>> {
- self.map.get(name)
- }
+ /// Returns the value of the variable represented by the given `id`. Note
+ /// that this `id` can be scoped or not, and this function will try to pick
+ /// the variable from the right scope.
+ pub fn get_variable(&self, id: &PString) -> Result<Bundle, ContextError> {
+ // First of all, figure out the name of the scope and the real name of
+ // the variable. If this was not scoped at all (None case when trying to
+ // rsplit by the "::" operator), then we assume it's a global variable.
+ let (scope_name, var_name) = match id.value.rsplit_once("::") {
+ Some((scope, name)) => (scope, name),
+ None => (self.name(), id.value.as_str()),
+ };
- pub fn current(&self) -> Option<&HashMap<String, PValue>> {
- match self.stack.last() {
- Some(name) => self.map.get(name),
- None => self.map.get(GLOBAL_CONTEXT),
+ // And with that, the only thing left is to find the scope and the
+ // variable in it.
+ match self.map.get(scope_name) {
+ Some(scope) => match scope.get(var_name) {
+ Some(var) => Ok(var.clone()),
+ None => Err(ContextError {
+ message: format!(
+ "could not find variable '{}' in {}",
+ var_name,
+ self.to_human_with(scope_name)
+ ),
+ line: id.line,
+ reason: ContextErrorReason::UnknownVariable,
+ }),
+ },
+ None => Err(ContextError {
+ message: format!("did not find scope '{}'", scope_name),
+ line: id.line,
+ reason: ContextErrorReason::BadScope,
+ }),
}
}
- pub fn current_mut(&mut self) -> Option<&mut HashMap<String, PValue>> {
- match self.stack.last() {
- Some(name) => self.map.get_mut(name),
- None => self.map.get_mut(GLOBAL_CONTEXT),
+ /// Sets a value for a new variable defined in the assignment `node`.
+ pub fn set_variable(&mut self, id: &PString, bundle: &Bundle) -> Result<(), ContextError> {
+ let scope_name = self.name().to_string();
+ let scope = self.map.get_mut(&scope_name).unwrap();
+
+ match scope.get_mut(&id.value) {
+ Some(_) => {
+ return Err(ContextError {
+ message: format!(
+ "'{}' already defined in {}: you cannot re-assign variables",
+ id.value,
+ self.to_human()
+ ),
+ line: id.line,
+ reason: ContextErrorReason::Redefinition,
+ })
+ }
+ None => {
+ self.map.insert(
+ scope_name,
+ HashMap::from([(id.value.clone(), bundle.to_owned())]),
+ );
+ }
}
- }
- pub fn is_global(&self) -> bool {
- self.stack.is_empty()
+ Ok(())
}
- pub fn name(&self) -> &str {
- match self.stack.last() {
- Some(name) => name,
- None => GLOBAL_CONTEXT,
+ /// Change the current context given a `node`.
+ pub fn change_context(&mut self, node: &PNode) -> Result<(), ContextError> {
+ // The parser already guarantees that the control node is
+ // from a function that we already know, so calling `unwrap`
+ // is not dangerous.
+ let control = CONTROL_FUNCTIONS
+ .get(&node.value.value.to_lowercase())
+ .unwrap();
+
+ // If the control function does not touch the context, leave early.
+ if !control.touches_context {
+ return Ok(());
+ }
+
+ // And push/pop the context depending on the control being used.
+ match node.value.value.as_str() {
+ ".macro" | ".proc" | ".scope" => self.context_push(&node.left.clone().unwrap()),
+ ".endmacro" | ".endproc" | ".endscope" => self.context_pop(&node.value)?,
+ _ => {}
}
+
+ Ok(())
}
- pub fn push(&mut self, identifier: &String) {
+ // Pushes a new context given a `node`, which holds the identifier of the
+ // new scope.
+ fn context_push(&mut self, id: &PNode) {
let name = match self.stack.last() {
- Some(n) => n.to_owned() + &String::from("::") + identifier,
- None => identifier.to_string(),
+ Some(n) => format!("{}::{}", n, id.value.value),
+ None => id.value.value.clone(),
};
+ // Actually push the name to the stack and initialize it on the variable
+ // map.
self.stack.push(name.clone());
self.map.entry(name).or_default();
}
- // pub fn push_stack(&mut self, identifier: &String) {
- // let name = match self.stack.last() {
- // Some(n) => n.to_owned() + &String::from("::") + identifier,
- // None => identifier.to_string(),
- // };
-
- // self.stack.push(name.clone());
- // }
-
- pub fn pop(&mut self) -> bool {
+ // Pops out the latest context that was pushed.
+ fn context_pop(&mut self, id: &PString) -> Result<(), ContextError> {
if self.stack.is_empty() {
- return false;
+ return Err(ContextError {
+ message: format!("missplaced '{}' statement", id.value),
+ reason: ContextErrorReason::BadScope,
+ line: id.line,
+ });
}
self.stack.truncate(self.stack.len() - 1);
- true
+ Ok(())
+ }
+
+ // Returns the name of the current context.
+ fn name(&self) -> &str {
+ match self.stack.last() {
+ Some(name) => name,
+ None => GLOBAL_CONTEXT,
+ }
+ }
+
+ // Returns a human-readable string representing the current context.
+ fn to_human(&self) -> String {
+ match self.stack.last() {
+ Some(n) => format!("'{}'", n),
+ None => "the global scope".to_string(),
+ }
+ }
+
+ // Returns a human-readable string representing the given context.
+ fn to_human_with(&self, name: &str) -> String {
+ if name == GLOBAL_CONTEXT {
+ "the global scope".to_string()
+ } else {
+ format!("'{}'", name)
+ }
+ }
+}
+
+impl Default for Context {
+ fn default() -> Self {
+ Self::new()
}
}