For example, `-37:i32` will add -37 as a signed 32-bit integer onto the stack.
Similarly, `45.5:f64` will add 45.5 as a 64-bit float, and `123:u8` will add 123 as an unsigned 8-bit integer.
Numbers respect the system endianness (little-endian).
+These same suffixes are used in struct definitions (see below) with the addition of the `ptr` suffix.
Values themselves don't have types.
Operations have types.
These can be retrieved for a given function by prefixing it with an apostrophe `'`.
For eample, to put a pointer to `foo` onto the stack, do `'foo`.
+## Structs
+
+It's often useful to interact with structured data, particularly in code that interacts in some way with other langauges.
+Sorel provides some syntactic sugar for dealing with structs as they would be defined in C.
+You can define a struct with the `::` word, followed by the struct name and fields, indicated as words of the form `field_name:type`, where the type is one of Sorel's core types like `u8`, `f32`, or `ptr`.
+Close out the struct with the `;` word, much like in word definitions.
+For easy interop with other native code, structs use the same memory layout as in C.
+
+Then, when you want to refer to a field in the struct, concatenate the struct name with the field name with a `.` to get the offset of that field.
+
+You can get the size of a struct in memory by prepending the struct name with `sizeof.`.
+
+For example:
+
+```
+:: my_struct
+ field1:u8
+ field2:u16
+;
+
+sizeof.my_struct
+\ `4` is now on the top of the stack
+
+\ now assume `instance` is a pointer to a my_struct
+
+instance my_struct.field2
+\ a pointer to `field2` of `instance` is now on top of the stack
+```
+
+Remember that structs are syntactic sugar, meaning they can't be imported or exported on their own.
+Only their generated words can (e.g. the specific field helper words or sizeof).
+An advantage of this is you can have effectively "private" fields by not exporting their generated words.
+Do _not_ rely on this for security though, since it's all just raw memory and offsets can be easily computed.
+
## Standard Library
Built-in words are insufficient to create most programs.
#[derive(Debug)]
pub struct WordDefinition<'a> {
- pub name: &'a str,
+ pub name: String,
pub instructions: Vec<Token<'a>>,
}
None,
}
+struct Struct<'a> {
+ name: &'a str,
+ fields: Vec<(&'a str, usize)>,
+}
+
+fn size_str_to_usize(size_str: &str) -> usize {
+ match size_str {
+ "u8" => 1,
+ "i8" => 1,
+ "u16" => 2,
+ "i16" => 2,
+ "u32" => 4,
+ "i32" => 4,
+ "u64" => 8,
+ "i64" => 8,
+ "ptr" => size_of::<usize>(),
+ _ => 0, // TODO should this error out instead?
+ }
+}
+
+impl<'a> Struct<'a> {
+ fn to_words(&'a self, module: &mut Module) {
+ let mut total_size = 0;
+
+ for (name, size) in self.fields.clone() {
+ let remainder = total_size % size;
+ let offset = if remainder == 0 {
+ total_size
+ } else {
+ total_size + (size - remainder)
+ };
+ module.words.push(WordDefinition {
+ name: format!("{}.{}", self.name, name),
+ instructions: vec![Token::NumU64(offset as u64), Token::Word("+")],
+ });
+ total_size = offset + size;
+ }
+
+ module.words.push(WordDefinition {
+ name: format!("sizeof.{}", self.name),
+ instructions: vec![Token::NumU64(total_size as u64)]
+ });
+ }
+
+ fn add_field(&mut self, token: Token<'a>) -> Result<()> {
+ if let Token::Word(token) = token {
+ let splat: Vec<_> = token.split(":").collect();
+ let name = splat[0];
+ let size_str = splat[1];
+ self.fields.push((name, size_str_to_usize(size_str)));
+ Ok(())
+ } else {
+ bail!("Improper struct definition!");
+ }
+ }
+}
+
impl<'a> Module<'a> {
pub fn parse(input: Vec<Token<'a>>, is_entrypoint: bool) -> Result<Self> {
let mut module = Module::default();
let mut main = vec![];
let mut current_word: Option<WordDefinition> = None;
+ let mut current_struct: Option<Struct> = None;
let mut last_was_colon = false;
+ let mut last_was_double_colon = false;
let mut last_meta = LastMeta::None;
for token in input {
// We're about to start defining a word, and the current token
// is the word's name.
current_word = Some(WordDefinition {
- name: token.as_word()?,
+ name: token.as_word()?.to_string(),
instructions: vec![],
});
last_was_colon = false;
+
+ continue;
+ }
+ if last_was_double_colon {
+ // We're about to start defining a struct, and the current token
+ // is the struct's name.
+ current_struct = Some(Struct {
+ name: token.as_word()?,
+ fields: vec![],
+ });
+ last_was_double_colon = false;
+
continue;
}
if let Token::Word(word) = token {
if word == ":" {
- if current_word.is_some() {
- bail!("can't define words inside word definitions!");
+ if current_word.is_some() || current_struct.is_some() {
+ bail!("can't start definitions inside definitions!");
}
last_was_colon = true;
continue;
}
+ if word == "::" {
+ if current_word.is_some() || current_struct.is_some() {
+ bail!("can't start definitions inside definitions!");
+ }
+ last_was_double_colon = true;
+ continue;
+ }
if word == ";" {
- let word = current_word
- .take()
- .expect("`;` must be at the end of a word definition");
- module.words.push(word);
+ if let Some(word) = current_word {
+ current_word = None;
+ module.words.push(word);
+ } else if let Some(strukt) = current_struct {
+ current_struct = None;
+ strukt.to_words(&mut module);
+ } else {
+ bail!("`;` must be at the end of a word or struct definition");
+ }
continue;
}
}
continue;
}
+ if let Some(ref mut current_struct) = current_struct {
+ current_struct.add_field(token)?;
+ continue;
+ }
+
// From here on out, we're at the top level in the module, which
// means either a "meta" (import, export, or extern) or raw code
// to be added to the `main` word if we're in the entrypoint, or
if is_entrypoint {
module.words.push(WordDefinition {
- name: "main",
+ name: "main".to_string(),
instructions: main,
});
}