diff --git a/builtin/README.md b/builtin/README.md index 72746e7..a79c87e 100644 --- a/builtin/README.md +++ b/builtin/README.md @@ -1,3 +1,4 @@ The following function signatures need to be implemented by each backend: -\_printf(msg String) +`_printf(msg: string)` +`_exit(code: int)` diff --git a/builtin/builtin.c b/builtin/builtin.c index bd882e5..49f8ac7 100644 --- a/builtin/builtin.c +++ b/builtin/builtin.c @@ -7,4 +7,9 @@ void _printf(char *msg) printf("%s", msg); } +void _exit(int code) +{ + exit(code); +} + /* END builtins */ diff --git a/builtin/builtin.js b/builtin/builtin.js index bf1145c..48938db 100644 --- a/builtin/builtin.js +++ b/builtin/builtin.js @@ -5,4 +5,8 @@ function _printf(msg) { process.stdout.write(msg.toString()); } +function _exit(code) { + process.exit(code); +} + /* END builtins */ diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 361de59..0eca749 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -9,6 +9,7 @@ - [Functions](./concepts/functions.md) - [Comments](./concepts/comments.md) - [Control Flow](./concepts/control-flow.md) + - [Structured data](./concepts/structured-data.md) - [Developer Resources](./developers/SUMMARY.md) - [Contributing to Sabre](./developers/contributing.md) - [Compiler Backends](./developers/backends.md) diff --git a/docs/concepts/structured-data.md b/docs/concepts/structured-data.md new file mode 100644 index 0000000..705ea09 --- /dev/null +++ b/docs/concepts/structured-data.md @@ -0,0 +1,66 @@ +# Structured data + +When working with data, you often find yourself needing to group information together. This is where a `struct` could come into play. A _struct_, or _structure_, is a custom data type that lets you name and package together multiple related values that make up a meaningful group. If you’re familiar with an object-oriented language, a struct is like an object’s data attributes. + +## Defining structs + +To define a struct, we enter the keyword `struct` and name the entire struct. A struct’s name should describe the significance of the pieces of data being grouped together. Then, inside curly brackets, we define the names and types of the pieces of data, which we call fields. The following example shows a struct that stores information about a user account. + +``` +struct User { + username: string, + email: string, + sign_in_count: int, + active: bool, +} +``` + +Structs can be nested as a type inside other structs. For example, we could assign each user an address, which itself is a struct. + +``` +struct Address { + street: string, + number: int + postal_code: string, + city: string +} + +struct User { + username: string, + email: string, + address: Address +} +``` + +## Instantiating structs + +To use a struct after we’ve defined it, we create an _instance_ of that struct by specifying concrete values for each of the fields. We create an instance by stating the name of the struct and then add curly brackets containing `key: value` pairs, where the keys are the names of the fields and the values are the data we want to store in those fields. We don’t have to specify the fields in the same order in which we declared them in the struct. In other words, the struct definition is like a general template for the type, and instances fill in that template with particular data to create values of the type. Let's use our `User` struct from a previous example and create an user called `alice`. + +``` +struct User { + username: string, + email: string, + sign_in_count: int, + active: bool, +} + +let alice = new User { + email: "alice@example.com", + username: "alice", + sign_in_count: 1, + active: true +} +``` + +To get a specific value from a struct, we can use dot notation. If we wanted just alice's email address, we could use `alice.email` wherever we wanted to use this value. Fields of structs can also be reassigned using the dot notation: + +``` +let alice = new User { + email: "alice@example.com", + username: "alice", + sign_in_count: 1, + active: true +} + +alice.sign_in_count = 2 +``` diff --git a/lib/stdio.sb b/lib/stdio.sb index e5cb55f..ce40fc6 100644 --- a/lib/stdio.sb +++ b/lib/stdio.sb @@ -9,6 +9,18 @@ fn println(msg: string) { print(msg + "\n") } +// Exit the program immediately +fn exit(code: int) { + _exit(code) +} + +fn assert(condition: bool) { + if condition == false { + println("Assertion failed") + exit(1) + } +} + // Prints the size of an array fn len(arr: int[]): int { let c: int = 0 diff --git a/src/command/run.rs b/src/command/run.rs index 2bc726c..757a2cd 100644 --- a/src/command/run.rs +++ b/src/command/run.rs @@ -1,6 +1,7 @@ use crate::command::build; use std::io::Write; use std::path::PathBuf; +use std::process; use std::process::Command; use std::process::Stdio; use tempfile::tempdir; @@ -51,6 +52,8 @@ pub fn run(in_file: PathBuf) -> Result<(), String> { std::io::stderr() .write_all(&out.stderr) .expect("Could not write to stderr"); + + process::exit(out.status.code().unwrap()) } Ok(()) } diff --git a/src/generator/c.rs b/src/generator/c.rs index e108280..02da8ea 100644 --- a/src/generator/c.rs +++ b/src/generator/c.rs @@ -51,6 +51,7 @@ pub(super) fn generate_type(t: Either>) -> String { Type::Str => "char *".into(), Type::Any => "void *".into(), Type::Bool => "bool".into(), + Type::Struct(_) => todo!(), Type::Array(_) => match name { Some(n) => format!( "{T} {N}[]", @@ -125,6 +126,8 @@ fn generate_expression(expr: Expression) -> String { Expression::Array(els) => generate_array(els), Expression::ArrayAccess(name, expr) => generate_array_access(name, *expr), Expression::BinOp(left, op, right) => generate_bin_op(*left, op, *right), + Expression::StructInitialization(_, _) => todo!(), + Expression::FieldAccess(_, _) => todo!(), } } @@ -217,6 +220,8 @@ fn generate_function_call(func: String, args: Vec) -> String { Expression::Str(s) | Expression::Variable(s) => s, Expression::Array(_) => todo!(), Expression::BinOp(left, op, right) => generate_bin_op(*left, op, *right), + Expression::StructInitialization(_, _) => todo!(), + Expression::FieldAccess(_, _) => todo!(), }) .collect::>() .join(","); diff --git a/src/generator/js.rs b/src/generator/js.rs index dabb680..6f174bf 100644 --- a/src/generator/js.rs +++ b/src/generator/js.rs @@ -15,6 +15,7 @@ */ use crate::generator::Generator; use crate::parser::node_type::*; +use std::collections::HashMap; pub struct JsGenerator; @@ -26,6 +27,15 @@ impl Generator for JsGenerator { crate::Builtins::get("builtin.js").expect("Could not locate builtin functions"); code += std::str::from_utf8(raw_builtins.as_ref()) .expect("Unable to interpret builtin functions"); + + let structs: String = prog + .structs + .into_iter() + .map(generate_struct_definition) + .collect(); + + code += &structs; + let funcs: String = prog.func.into_iter().map(generate_function).collect(); code += &funcs; @@ -50,6 +60,11 @@ fn generate_function(func: Function) -> String { raw } +fn generate_struct_definition(struct_def: StructDef) -> String { + // JS doesn't care about field declaration + format!("class {} {{}}\n", struct_def.name) +} + /// prepend is used to pass optional statements, that will be put in front of the regular block /// Currently used in for statements, to declare local variables fn generate_block(block: Statement, prepend: Option) -> String { @@ -102,6 +117,10 @@ fn generate_expression(expr: Expression) -> String { Expression::Array(els) => generate_array(els), Expression::ArrayAccess(name, expr) => generate_array_access(name, *expr), Expression::BinOp(left, op, right) => generate_bin_op(*left, op, *right), + Expression::StructInitialization(name, fields) => { + generate_struct_initialization(name, fields) + } + Expression::FieldAccess(expr, field) => generate_field_access(*expr, field), } } @@ -210,6 +229,10 @@ fn generate_function_call(func: String, args: Vec) -> String { Expression::Str(s) | Expression::Variable(s) => s, Expression::Array(elements) => generate_array(elements), Expression::BinOp(left, op, right) => generate_bin_op(*left, op, *right), + Expression::StructInitialization(name, fields) => { + generate_struct_initialization(name, fields) + } + Expression::FieldAccess(expr, field) => generate_field_access(*expr, field), }) .collect::>() .join(","); @@ -251,6 +274,24 @@ fn generate_bin_op(left: Expression, op: BinOp, right: Expression) -> String { ) } +fn generate_struct_initialization( + _name: String, + fields: HashMap>, +) -> String { + let mut out_str = "{".to_string(); + for (key, value) in fields { + out_str += &format!("{}: {},", key, generate_expression(*value)); + } + + out_str += "}"; + + out_str +} + +fn generate_field_access(expr: Expression, field: String) -> String { + format!("{}.{}", generate_expression(expr), field) +} + fn generate_assign(name: Expression, expr: Expression) -> String { format!( "{} = {}", diff --git a/src/generator/llvm.rs b/src/generator/llvm.rs index 0d1deb9..210e6a0 100644 --- a/src/generator/llvm.rs +++ b/src/generator/llvm.rs @@ -31,6 +31,7 @@ impl<'ctx> LLVMGenerator<'ctx> { Some(Type::Any) => todo!(), Some(Type::Str) => todo!(), Some(Type::Array(_)) => todo!(), + Some(Type::Struct(_)) => todo!(), None => panic!("Function argument has no type"), }) .collect(); diff --git a/src/generator/x86.rs b/src/generator/x86.rs index 44c6c5d..c44346c 100644 --- a/src/generator/x86.rs +++ b/src/generator/x86.rs @@ -57,7 +57,11 @@ impl X86Generator { fn gen_program(&mut self, prog: Program) -> Assembly { let mut asm = Assembly::new(); - let Program { func, globals } = prog; + let Program { + func, + globals, + structs: _, + } = prog; asm.add(".intel_syntax noprefix"); asm.add(".text"); diff --git a/src/lexer/mod.rs b/src/lexer/mod.rs index dda678c..14586e4 100644 --- a/src/lexer/mod.rs +++ b/src/lexer/mod.rs @@ -72,6 +72,8 @@ pub enum TokenKind { Colon, /// ";" SemiColon, + /// "." + Dot, /// "!" Exclamation, /// "," @@ -141,6 +143,8 @@ pub enum Keyword { Continue, Function, Boolean, + Struct, + New, Unknown, } @@ -201,6 +205,7 @@ impl Cursor<'_> { c if is_whitespace(c) => self.whitespace(), '0'..='9' => self.number(), '"' | '\'' => self.string(), + '.' => Dot, '+' => match self.first() { '=' => { self.bump(); @@ -361,6 +366,8 @@ impl Cursor<'_> { c if c == "in" => Keyword::In, c if c == "break" => Keyword::Break, c if c == "continue" => Keyword::Continue, + c if c == "struct" => Keyword::Struct, + c if c == "new" => Keyword::New, _ => Keyword::Unknown, } } diff --git a/src/parser/infer.rs b/src/parser/infer.rs index c964ad5..819e167 100644 --- a/src/parser/infer.rs +++ b/src/parser/infer.rs @@ -32,6 +32,7 @@ fn infer_expression(expr: &Expression, table: &SymbolTable) -> Option { Expression::Int(_) => Some(Type::Int), Expression::Bool(_) => Some(Type::Bool), Expression::Str(_) => Some(Type::Str), + Expression::StructInitialization(name, _) => Some(Type::Struct(name.to_string())), Expression::FunctionCall(name, _) => infer_function_call(name, table), Expression::Array(els) => infer_array(els, table), _ => None, diff --git a/src/parser/node_type.rs b/src/parser/node_type.rs index c2c5c33..bc0940f 100644 --- a/src/parser/node_type.rs +++ b/src/parser/node_type.rs @@ -23,6 +23,7 @@ pub type SymbolTable = HashMap>; #[derive(Debug)] pub struct Program { pub func: Vec, + pub structs: Vec, pub globals: Vec, } @@ -51,6 +52,12 @@ pub struct Function { pub ret_type: Option, } +#[derive(Debug, Clone)] +pub struct StructDef { + pub name: String, + pub fields: Vec, +} + #[derive(Debug, Eq, PartialEq, Clone)] pub struct Variable { pub name: String, @@ -64,6 +71,7 @@ pub enum Type { Str, Bool, Array(Box), + Struct(String), } impl TryFrom for Type { @@ -74,7 +82,7 @@ impl TryFrom for Type { "string" => Ok(Self::Str), "any" => Ok(Self::Any), "bool" => Ok(Self::Bool), - _ => Err("Unknown Type".into()), + name => Ok(Self::Struct(name.to_string())), } } } @@ -105,6 +113,8 @@ pub enum Expression { /// (name, index) ArrayAccess(String, Box), BinOp(Box, BinOp, Box), + StructInitialization(String, HashMap>), + FieldAccess(Box, String), } impl TryFrom for Expression { diff --git a/src/parser/rules.rs b/src/parser/rules.rs index 63768ca..4334764 100644 --- a/src/parser/rules.rs +++ b/src/parser/rules.rs @@ -18,23 +18,74 @@ use super::node_type::*; use super::parser::Parser; use crate::lexer::Keyword; use crate::lexer::{TokenKind, Value}; +use std::collections::HashMap; use std::convert::TryFrom; impl Parser { pub fn parse_program(&mut self) -> Result { let mut functions = Vec::new(); + let mut structs = Vec::new(); let globals = Vec::new(); while self.has_more() { - functions.push(self.parse_function()?) + let next = self.peek()?; + match next.kind { + TokenKind::Keyword(Keyword::Function) => functions.push(self.parse_function()?), + TokenKind::Keyword(Keyword::Struct) => { + structs.push(self.parse_struct_definition()?) + } + _ => return Err(format!("Unexpected token: {}", next.raw)), + } } Ok(Program { func: functions, + structs, globals, }) } + fn parse_struct_definition(&mut self) -> Result { + self.match_keyword(Keyword::Struct)?; + let name = self.match_identifier()?; + + self.match_token(TokenKind::CurlyBracesOpen)?; + let fields = self.parse_typed_variable_list()?; + self.match_token(TokenKind::CurlyBracesClose)?; + + Ok(StructDef { name, fields }) + } + + fn parse_typed_variable_list(&mut self) -> Result, String> { + let mut args = Vec::new(); + + // If there is an argument + if let TokenKind::Identifier(_) = self.peek()?.kind { + // Parse first argument + args.push(self.parse_typed_variable()?); + // Then continue to parse arguments + // as long as a comma token is found + while self.peek_token(TokenKind::Comma).is_ok() { + self.match_token(TokenKind::Comma)?; + args.push(self.parse_typed_variable()?); + } + } + + Ok(args) + } + + fn parse_typed_variable(&mut self) -> Result { + let next = self.next()?; + if let TokenKind::Identifier(name) = next.kind { + return Ok(Variable { + name, + ty: Some(self.parse_type()?), + }); + } + + Err(format!("Argument could not be parsed: {}", next.raw)) + } + fn parse_block(&mut self) -> Result { self.match_token(TokenKind::CurlyBracesOpen)?; @@ -68,7 +119,7 @@ impl Parser { let arguments: Vec = match self.peek()? { t if t.kind == TokenKind::BraceClose => Vec::new(), - _ => self.parse_arguments()?, + _ => self.parse_typed_variable_list()?, }; self.match_token(TokenKind::BraceClose)?; @@ -88,36 +139,6 @@ impl Parser { }) } - fn parse_arguments(&mut self) -> Result, String> { - let mut args = Vec::new(); - - // If there is an argument - if let TokenKind::Identifier(_) = self.peek()?.kind { - // Parse first argument - args.push(self.parse_typed_argument_list()?); - // Then continue to parse arguments - // as long as a comma token is found - while self.peek_token(TokenKind::Comma).is_ok() { - self.match_token(TokenKind::Comma)?; - args.push(self.parse_typed_argument_list()?); - } - } - - Ok(args) - } - - fn parse_typed_argument_list(&mut self) -> Result { - let next = self.next()?; - if let TokenKind::Identifier(name) = next.kind { - return Ok(Variable { - name, - ty: Some(self.parse_type()?), - }); - } - - Err(format!("Argument could not be parsed: {}", next.raw)) - } - fn parse_type(&mut self) -> Result { self.match_token(TokenKind::Colon)?; let next = self.peek()?; @@ -146,12 +167,18 @@ impl Parser { TokenKind::Keyword(Keyword::For) => self.parse_for_loop(), TokenKind::Identifier(_) => { let ident = self.match_identifier()?; + let expr = if self.peek_token(TokenKind::Dot).is_ok() { + self.parse_field_access(Expression::Variable(ident.clone()))? + } else { + Expression::Variable(ident.clone()) + }; + // TODO: Use match statement if self.peek_token(TokenKind::BraceOpen).is_ok() { let state = self.parse_function_call(Some(ident))?; Ok(Statement::Exp(state)) } else if self.peek_token(TokenKind::Assign).is_ok() { - let state = self.parse_assignent(Some(Expression::Variable(ident)))?; + let state = self.parse_assignent(Some(expr))?; Ok(state) } else if self.peek_token(TokenKind::SquareBraceOpen).is_ok() { let expr = self.parse_array_access(Some(ident))?; @@ -166,12 +193,18 @@ impl Parser { let expr = Expression::Variable(ident); let state = Statement::Exp(self.parse_bin_op(Some(expr))?); Ok(state) + } else if self.peek_token(TokenKind::Dot).is_ok() { + Ok(Statement::Exp( + self.parse_field_access(Expression::Variable(ident))?, + )) } else { - let state = Statement::Exp(Expression::Variable(ident)); - Ok(state) + Ok(Statement::Exp(expr)) } } TokenKind::Literal(_) => Ok(Statement::Exp(self.parse_expression()?)), + TokenKind::Keyword(Keyword::Struct) => { + Err("Struct definitions inside functions are not allowed".to_string()) + } _ => Err(self.make_error(TokenKind::Unknown, token)), } } @@ -214,7 +247,11 @@ impl Parser { } self.match_token(TokenKind::BraceClose)?; - Ok(Expression::FunctionCall(name, args)) + let expr = Expression::FunctionCall(name, args); + match self.peek()?.kind { + TokenKind::Dot => self.parse_field_access(expr), + _ => Ok(expr), + } } fn parse_return(&mut self) -> Result { @@ -228,38 +265,27 @@ impl Parser { fn parse_expression(&mut self) -> Result { let token = self.next()?; - match token.kind { + let expr = match token.kind { TokenKind::BraceOpen => { let expr = self.parse_expression()?; self.match_token(TokenKind::BraceClose)?; - Ok(expr) - } - TokenKind::Keyword(Keyword::Boolean) => { - let state = match BinOp::try_from(self.peek()?.kind) { - Ok(_) => self.parse_bin_op(None)?, - Err(_) => { - Expression::Bool(token.raw.parse::().map_err(|e| e.to_string())?) - } - }; - Ok(state) - } - TokenKind::Literal(Value::Int) => { - let state = match BinOp::try_from(self.peek()?.kind) { - Ok(_) => self.parse_bin_op(None)?, - Err(_) => Expression::Int(token.raw.parse::().map_err(|e| e.to_string())?), - }; - Ok(state) - } - TokenKind::Literal(Value::Str) => { - let state = match BinOp::try_from(self.peek()?.kind) { - Ok(_) => self.parse_bin_op(None)?, - Err(_) => Expression::Str(token.raw), - }; - Ok(state) + expr } + TokenKind::Keyword(Keyword::Boolean) => match BinOp::try_from(self.peek()?.kind) { + Ok(_) => self.parse_bin_op(None)?, + Err(_) => Expression::Bool(token.raw.parse::().map_err(|e| e.to_string())?), + }, + TokenKind::Literal(Value::Int) => match BinOp::try_from(self.peek()?.kind) { + Ok(_) => self.parse_bin_op(None)?, + Err(_) => Expression::Int(token.raw.parse::().map_err(|e| e.to_string())?), + }, + TokenKind::Literal(Value::Str) => match BinOp::try_from(self.peek()?.kind) { + Ok(_) => self.parse_bin_op(None)?, + Err(_) => Expression::Str(token.raw), + }, TokenKind::Identifier(val) => { let next = self.peek()?; - let state = match &next.kind { + match &next.kind { TokenKind::BraceOpen => { let func_call = self.parse_function_call(Some(val))?; match BinOp::try_from(self.peek()?.kind) { @@ -274,16 +300,81 @@ impl Parser { Err(_) => arr, } } + TokenKind::Dot => { + let lhs = Expression::Variable(val); + let expr = self.parse_field_access(lhs)?; + match BinOp::try_from(self.peek()?.kind) { + Ok(_) => self.parse_bin_op(Some(expr))?, + Err(_) => expr, + } + } _ => match BinOp::try_from(self.peek()?.kind) { Ok(_) => self.parse_bin_op(Some(Expression::Variable(token.raw)))?, Err(_) => Expression::Variable(val), }, - }; - Ok(state) + } + } + TokenKind::SquareBraceOpen => self.parse_array()?, + TokenKind::Keyword(Keyword::New) => self.parse_struct_initialization()?, + other => return Err(format!("Expected Expression, found {:?}", other)), + }; + + if self.peek_token(TokenKind::Dot).is_ok() { + self.parse_field_access(expr) + } else { + Ok(expr) + } + } + + fn parse_field_access(&mut self, lhs: Expression) -> Result { + self.match_token(TokenKind::Dot)?; + let field = self.match_identifier()?; + let expr = Expression::FieldAccess(Box::new(lhs), field); + if self.peek_token(TokenKind::Dot).is_ok() { + self.parse_field_access(expr) + } else { + Ok(expr) + } + } + + /// TODO: Cleanup + fn parse_struct_initialization(&mut self) -> Result { + let name = self.match_identifier()?; + self.match_token(TokenKind::CurlyBracesOpen)?; + let fields = self.parse_struct_fields()?; + self.match_token(TokenKind::CurlyBracesClose)?; + + Ok(Expression::StructInitialization(name, fields)) + } + + fn parse_struct_fields(&mut self) -> Result>, String> { + let mut map = HashMap::new(); + + // If there is a field + if let TokenKind::Identifier(_) = self.peek()?.kind { + // Parse first field + let (name, expr) = self.parse_struct_field()?; + map.insert(name, expr); + // Then continue to parse fields + // as long as a comma token is found + while self.peek_token(TokenKind::Comma).is_ok() { + self.match_token(TokenKind::Comma)?; + let (name, expr) = self.parse_struct_field()?; + map.insert(name, expr); } - TokenKind::SquareBraceOpen => self.parse_array(), - other => Err(format!("Expected Expression, found {:?}", other)), } + + Ok(map) + } + + fn parse_struct_field(&mut self) -> Result<(String, Box), String> { + let next = self.next()?; + if let TokenKind::Identifier(name) = next.kind { + self.match_token(TokenKind::Colon)?; + return Ok((name, Box::new(self.parse_expression()?))); + } + + Err(format!("Struct field could not be parsed: {}", next.raw)) } fn parse_array(&mut self) -> Result { diff --git a/src/parser/tests.rs b/src/parser/tests.rs index 5159f84..30fb9e7 100644 --- a/src/parser/tests.rs +++ b/src/parser/tests.rs @@ -776,3 +776,25 @@ fn test_array_as_argument() { let tree = parse(tokens, Some(raw.to_string())); assert!(tree.is_ok()); } + +#[test] +fn test_struct_initialization() { + let raw = " + struct User { + username: string, + first_name: string, + last_name: string + } + + fn main() { + let foo = new User { + username: 'foobar', + first_name: 'Foo', + last_name: 'Bar' + } + } + "; + let tokens = tokenize(raw); + let tree = parse(tokens, Some(raw.to_string())); + assert!(tree.is_ok()); +} diff --git a/src/util/mod.rs b/src/util/mod.rs index 9a7561a..d2ec59c 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -15,6 +15,7 @@ */ pub mod string_util; +#[cfg(feature = "backend_c")] /// Datatype that holds one of two types pub enum Either { Left(L), diff --git a/tests/function_return_type_explicit.sb b/tests/function_return_type_explicit.sb index 7d6dc27..88a9b1e 100644 --- a/tests/function_return_type_explicit.sb +++ b/tests/function_return_type_explicit.sb @@ -1,5 +1,6 @@ fn main() { let x = add_one(2) + assert(x == 3) println(x) } diff --git a/tests/if_statement_basic.sb b/tests/if_statement_basic.sb index 6569033..2fb39c7 100644 --- a/tests/if_statement_basic.sb +++ b/tests/if_statement_basic.sb @@ -5,5 +5,7 @@ fn main() { println("condition was true") } else { println("condition was false") + // Should not be true + } } \ No newline at end of file diff --git a/tests/if_statement_non_boolean_condition.sb b/tests/if_statement_non_boolean_condition.sb index 8fbd737..92e6cd0 100644 --- a/tests/if_statement_non_boolean_condition.sb +++ b/tests/if_statement_non_boolean_condition.sb @@ -3,5 +3,7 @@ fn main() { if number { println("number was three") + } else { + assert(false) } } \ No newline at end of file diff --git a/tests/struct_initialization.sb b/tests/struct_initialization.sb new file mode 100644 index 0000000..d2588a3 --- /dev/null +++ b/tests/struct_initialization.sb @@ -0,0 +1,13 @@ +struct User { + username: string, + first_name: string, + last_name: string +} + +fn main() { + let foo = new User { + username: 'foobar', + first_name: 'Foo', + last_name: 'Bar' + } +} \ No newline at end of file diff --git a/tests/structs.sb b/tests/structs.sb new file mode 100644 index 0000000..04f6913 --- /dev/null +++ b/tests/structs.sb @@ -0,0 +1,73 @@ +struct User { + username: string, + first_name: string, + last_name: string +} + +// Creates a stub user +fn user_stub() { + let stub = new User { + username: "Foo Bar", + first_name: "Foo", + last_name: "Bar" + } + + assert(stub.first_name) + assert(stub.last_name) + return stub +} + +fn test_initialization() { + let foo = new User { + username: "Foo Bar", + first_name: "Bar", + last_name: "Bar" + } + + assert(foo) +} + +fn test_simple_field_access() { + let user: User = user_stub() + user.username = "Foo Bar" +} + +fn test_field_access_in_function_call() { + let user: User = user_stub() + user.username = "Bar" + assert(user.username == "Bar") +} + +fn test_field_access_on_function() { + assert(user_stub().first_name == "Foo") +} + +struct Foo { + x: int, + bar: Bar +} + +struct Bar { + y: string +} + +fn test_nested_structs() { + let foo = new Foo { + x: 5, + bar: new Bar { + y: "Nested field" + } + } + + assert(foo.x == 5) + println(foo.bar.y) + assert(foo.bar.y == "Nested field") +} + +fn main() { + test_initialization() + test_simple_field_access() + test_field_access_in_function_call() + test_field_access_on_function() + test_nested_structs() +} \ No newline at end of file