From: Bryan English Date: Wed, 26 Aug 2026 02:19:29 +0000 (-0400) Subject: structs X-Git-Url: https://rethought.computer/gitweb//gitweb//git?a=commitdiff_plain;ds=sidebyside;p=sorel-lang.git structs --- diff --git a/docs/language_overview.md b/docs/language_overview.md index 1e3475a..8228116 100644 --- a/docs/language_overview.md +++ b/docs/language_overview.md @@ -27,6 +27,7 @@ Number literals can provide type information after a colon. 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. @@ -103,6 +104,40 @@ In order to use `call`, you'll need to get a function pointer. 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. diff --git a/sorel-parser/src/lib.rs b/sorel-parser/src/lib.rs index e269e06..757d1b6 100644 --- a/sorel-parser/src/lib.rs +++ b/sorel-parser/src/lib.rs @@ -3,7 +3,7 @@ use sorel_tokenizer::Token; #[derive(Debug)] pub struct WordDefinition<'a> { - pub name: &'a str, + pub name: String, pub instructions: Vec>, } @@ -22,12 +22,71 @@ enum LastMeta { 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::(), + _ => 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>, is_entrypoint: bool) -> Result { let mut module = Module::default(); let mut main = vec![]; let mut current_word: Option = None; + let mut current_struct: Option = None; let mut last_was_colon = false; + let mut last_was_double_colon = false; let mut last_meta = LastMeta::None; for token in input { @@ -35,26 +94,50 @@ impl<'a> Module<'a> { // 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; } } @@ -64,6 +147,11 @@ impl<'a> Module<'a> { 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 @@ -105,7 +193,7 @@ impl<'a> Module<'a> { if is_entrypoint { module.words.push(WordDefinition { - name: "main", + name: "main".to_string(), instructions: main, }); } diff --git a/tests/test.sh b/tests/test.sh index e239f87..9979347 100644 --- a/tests/test.sh +++ b/tests/test.sh @@ -4,12 +4,20 @@ AS="${CMD_PREFIX}as" LD="${CMD_PREFIX}ld" CC="${CMD_PREFIX}cc" +echo "### test 1 ###" ../target/debug/sorelc test1.sorel $AS -g -o test1.o test1.asm $LD -o test1.out test1.o ./test1.out +echo "### test 2 ###" ../target/debug/sorelc test2.sorel $AS -g -o test2.o test2.asm $LD -o test2.out test2.o ./test2.out + +echo "### test 3 ###" +../target/debug/sorelc test3.sorel +$AS -g -o test3.o test3.asm +$LD -o test3.out test3.o +./test3.out diff --git a/tests/test2.sorel b/tests/test2.sorel index 0ff5b41..c536d06 100644 --- a/tests/test2.sorel +++ b/tests/test2.sorel @@ -15,3 +15,4 @@ loop \ ( argv argc ) 1 - \ ( argv+8 argc-1 ) dup \ ( argv+8 argc-1 argc-1 ) endloop +"\n" puts diff --git a/tests/test3.sorel b/tests/test3.sorel new file mode 100644 index 0000000..549c4a8 --- /dev/null +++ b/tests/test3.sorel @@ -0,0 +1,29 @@ +\ vim: filetype=forth + +import "std:mem" +import "std:out" + +:: test_struct + first:u8 + second:u32 + third:u64 +; + +var instance + +\ allocate some memory for an instance and store the pointer to it in `instance` +sizeof.test_struct alloc instance ! + +\ put 40, 41, and 42 into the struct at the `instance` pointer +40 instance test_struct.first !:8 +41 instance test_struct.second !:32 +42 instance test_struct.third ! + +\ extract the properties back onto the stack +instance test_struct.first @:8 +instance test_struct.second @:32 +instance test_struct.third @ + +sizeof.test_struct + +putstack