Skip to content

Commit

Permalink
feat: implement exported procedures
Browse files Browse the repository at this point in the history
  • Loading branch information
bobbinth committed Feb 22, 2022
1 parent de0ede6 commit cad755c
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 55 deletions.
15 changes: 13 additions & 2 deletions assembly/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,9 +242,20 @@ impl AssemblyError {
}
}

pub fn circular_module_dependency(token: &Token, module_stack: &[String]) -> Self {
pub fn circular_module_dependency(token: &Token, module_chain: &[String]) -> Self {
AssemblyError {
message: format!("circular module dependency in the following chain: {:?}", module_stack),
message: format!(
"circular module dependency in the following chain: {:?}",
module_chain
),
step: token.pos(),
op: token.to_string(),
}
}

pub fn invalid_module_path(token: &Token, module_path: &str) -> Self {
AssemblyError {
message: format!("invalid module import path: {}", module_path),
step: token.pos(),
op: token.to_string(),
}
Expand Down
36 changes: 17 additions & 19 deletions assembly/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use vm_core::program::{blocks::CodeBlock, Library, Script};
use vm_stdlib::StdLibrary;
use winter_utils::collections::{BTreeMap, Vec};
use winter_utils::collections::BTreeMap;

mod context;
use context::AssemblyContext;
Expand Down Expand Up @@ -67,13 +67,11 @@ impl Assembler {
// parse locally defined procedures (if any), and add these procedures to the current
// context
while let Some(token) = tokens.read() {
match token.parts()[0] {
Token::PROC => {
let proc = Procedure::parse(&mut tokens, &context)?;
context.add_local_proc(proc);
}
let proc = match token.parts()[0] {
Token::PROC => Procedure::parse(&mut tokens, &context)?,
_ => break,
}
};
context.add_local_proc(proc);
}

// make sure script body is present
Expand Down Expand Up @@ -120,7 +118,7 @@ impl Assembler {

// check if a module with the same path is currently being parsed somewhere up
// the chain; if it is, then we have a circular dependency.
if let Some(_) = dep_chain.iter().position(|v| v == module_path) {
if dep_chain.iter().any(|v| v == module_path) {
dep_chain.push(module_path.clone());
return Err(AssemblyError::circular_module_dependency(token, dep_chain));
}
Expand Down Expand Up @@ -185,13 +183,11 @@ impl Assembler {
// parse procedures defined in the module, and add these procedures to the current
// context
while let Some(token) = tokens.read() {
match token.parts()[0] {
Token::PROC => {
let proc = Procedure::parse(&mut tokens, &context)?;
context.add_local_proc(proc);
}
let proc = match token.parts()[0] {
Token::PROC | Token::EXPORT => Procedure::parse(&mut tokens, &context)?,
_ => break,
}
};
context.add_local_proc(proc);
}

// make sure there are no dangling instructions after all procedures have been read
Expand All @@ -200,12 +196,14 @@ impl Assembler {
return Err(AssemblyError::dangling_ops_after_module(token, path));
}

// extract only the exported local procedures from the context, and insert them
// into `self.parsed_procedures`
let path = path.to_string();
let module_procs = context.into_local_procs();
// extract the exported local procedures from the context
let mut module_procs = context.into_local_procs();
module_procs.retain(|_, p| p.is_export());

// insert exported procedures into `self.parsed_procedures`
// TODO: figure out how to do this using interior mutability
unsafe {
// TODO: figure out how to do this using interior mutability
let path = path.to_string();
let mutable_self = &mut *(self as *const _ as *mut Assembler);
mutable_self.parsed_modules.insert(path, module_procs);
}
Expand Down
2 changes: 1 addition & 1 deletion assembly/src/parsers/blocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ impl BlockParser {
token.validate_end()?;
None
}
Token::BEGIN | Token::PROC => None,
Token::USE | Token::EXPORT | Token::PROC | Token::BEGIN => None,
_ => Some(Self::Span),
},
};
Expand Down
9 changes: 8 additions & 1 deletion assembly/src/procedures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use super::{parse_proc_blocks, AssemblyContext, AssemblyError, CodeBlock, Token,
/// TODO: add docs
pub struct Procedure {
label: String,
is_export: bool,
#[allow(dead_code)]
num_locals: u32,
code_root: CodeBlock,
Expand All @@ -25,6 +26,11 @@ impl Procedure {
&self.label
}

/// Returns `true` if this is an exported procedure.
pub fn is_export(&self) -> bool {
self.is_export
}

// PARSER
// --------------------------------------------------------------------------------------------

Expand All @@ -37,7 +43,7 @@ impl Procedure {

// read procedure name and consume the procedure header token
let header = tokens.read().expect("missing procedure header");
let (label, num_locals) = header.parse_proc()?;
let (label, num_locals, is_export) = header.parse_proc()?;
if context.contains_proc(&label) {
return Err(AssemblyError::duplicate_proc_label(header, &label));
}
Expand All @@ -63,6 +69,7 @@ impl Procedure {
// build and return the procedure
Ok(Self {
label,
is_export,
num_locals,
code_root,
})
Expand Down
79 changes: 63 additions & 16 deletions assembly/src/tokens/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ impl<'a> Token<'a> {
// --------------------------------------------------------------------------------------------

pub const USE: &'static str = "use";
pub const PROC: &'static str = "proc";
pub const EXPORT: &'static str = "export";

pub const BEGIN: &'static str = "begin";
pub const PROC: &'static str = "proc";
pub const IF: &'static str = "if";
pub const ELSE: &'static str = "else";
pub const WHILE: &'static str = "while";
Expand Down Expand Up @@ -64,8 +65,10 @@ impl<'a> Token<'a> {
pub fn is_control_token(&self) -> bool {
matches!(
self.parts()[0],
Self::BEGIN
Self::USE
| Self::PROC
| Self::EXPORT
| Self::BEGIN
| Self::IF
| Self::ELSE
| Self::WHILE
Expand Down Expand Up @@ -95,10 +98,7 @@ impl<'a> Token<'a> {
assert_eq!(Self::USE, self.parts[0], "not a use");
match self.num_parts() {
1 => Err(AssemblyError::missing_param(self)),
2 => {
let ns_path = self.parts[1]; // TODO: validate
Ok(ns_path.to_string())
}
2 => validate_import_path(self.parts[1], self),
_ => Err(AssemblyError::extra_param(self)),
}
}
Expand All @@ -112,18 +112,22 @@ impl<'a> Token<'a> {
}
}

pub fn parse_proc(&self) -> Result<(String, u32), AssemblyError> {
assert_eq!(Self::PROC, self.parts[0], "invalid procedure declaration");
pub fn parse_proc(&self) -> Result<(String, u32, bool), AssemblyError> {
assert!(
self.parts[0] == Self::PROC || self.parts[0] == Self::EXPORT,
"invalid procedure declaration"
);
let is_export = self.parts[0] == Self::EXPORT;
match self.num_parts() {
1 => Err(AssemblyError::missing_param(self)),
2 => {
let label = validate_proc_label(self.parts[1], self)?;
Ok((label, 0))
let label = validate_proc_declaration_label(self.parts[1], self)?;
Ok((label, 0, is_export))
}
3 => {
let label = validate_proc_label(self.parts[1], self)?;
let label = validate_proc_declaration_label(self.parts[1], self)?;
let num_locals = validate_proc_locals(self.parts[2], self)?;
Ok((label, num_locals))
Ok((label, num_locals, is_export))
}
_ => Err(AssemblyError::extra_param(self)),
}
Expand Down Expand Up @@ -183,7 +187,7 @@ impl<'a> Token<'a> {
assert_eq!(Self::EXEC, self.parts[0], "not an exec");
match self.num_parts() {
1 => Err(AssemblyError::missing_param(self)),
2 => validate_proc_label(self.parts[1], self),
2 => validate_proc_invocation_label(self.parts[1], self),
_ => Err(AssemblyError::extra_param(self)),
}
}
Expand All @@ -207,13 +211,36 @@ impl<'a> fmt::Display for Token<'a> {
// HELPER FUNCTIONS
// ================================================================================================

fn validate_proc_label(label: &str, token: &Token) -> Result<String, AssemblyError> {
// a label must start with an alphanumeric character
/// Label of a declared procedure must comply with the following rules:
/// - It must start with an ascii letter.
/// - It can contain only ascii letters, numbers, or underscores.
fn validate_proc_declaration_label(label: &str, token: &Token) -> Result<String, AssemblyError> {
// a label must start with a letter
if label.is_empty() || !label.chars().next().unwrap().is_ascii_alphabetic() {
return Err(AssemblyError::invalid_proc_label(token, label));
}

// a label can contain only number, letters, underscores, and colons
// a declaration label can contain only letters, numbers, or underscores
if !label.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
return Err(AssemblyError::invalid_proc_label(token, label));
}

Ok(label.to_string())
}

/// A label of an invoked procedure must comply with the following rules:
/// - It must start with an ascii letter.
/// - It can contain only ascii letters, numbers, underscores, or colons.
///
/// As compared to procedure declaration label, colons are allowed here to support invocation
/// of imported procedures.
fn validate_proc_invocation_label(label: &str, token: &Token) -> Result<String, AssemblyError> {
// a label must start with a letter
if label.is_empty() || !label.chars().next().unwrap().is_ascii_alphabetic() {
return Err(AssemblyError::invalid_proc_label(token, label));
}

// a label can contain only letters, numbers, underscores, or colons
if !label
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == ':')
Expand All @@ -235,3 +262,23 @@ fn validate_proc_locals(locals: &str, token: &Token) -> Result<u32, AssemblyErro
Err(_) => Err(AssemblyError::invalid_proc_locals(token, locals)),
}
}

/// A module import path must comply with the following rules:
/// - It must start with an ascii letter.
/// - It can contain only ascii letters, numbers, underscores, or colons.
fn validate_import_path(path: &str, token: &Token) -> Result<String, AssemblyError> {
// a path must start with a letter
if path.is_empty() || !path.chars().next().unwrap().is_ascii_alphabetic() {
return Err(AssemblyError::invalid_module_path(token, path));
}

// a path can contain only letters, numbers, underscores, or colons
if !path
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == ':')
{
return Err(AssemblyError::invalid_module_path(token, path));
}

Ok(path.to_string())
}
14 changes: 7 additions & 7 deletions stdlib/asm/math/u256.masm
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
proc.add_unsafe
export.add_unsafe
swapw.3
movup.3
movup.7
Expand Down Expand Up @@ -30,7 +30,7 @@ proc.add_unsafe
drop
end

proc.sub_unsafe
export.sub_unsafe
swapw.3
movup.3
movup.7
Expand Down Expand Up @@ -89,7 +89,7 @@ proc.sub_unsafe
drop
end

proc.and
export.and
swapw.3
movup.3
movup.7
Expand Down Expand Up @@ -118,7 +118,7 @@ proc.and
u32and
end

proc.or
export.or
swapw.3
movup.3
movup.7
Expand Down Expand Up @@ -147,7 +147,7 @@ proc.or
u32or
end

proc.u256xor
export.u256xor
swapw.3
movup.3
movup.7
Expand Down Expand Up @@ -176,7 +176,7 @@ proc.u256xor
u32xor
end

proc.iszero_unsafe
export.iszero_unsafe
eq.0
repeat.7
swap
Expand All @@ -185,7 +185,7 @@ proc.iszero_unsafe
end
end

proc.eq_unsafe
export.eq_unsafe
swapw.3
eqw
movdn.8
Expand Down
2 changes: 1 addition & 1 deletion stdlib/asm/math/u64.masm
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
proc.add_unsafe
export.add_unsafe
swap
movup.3
u32add.unsafe
Expand Down

0 comments on commit cad755c

Please sign in to comment.