Browse Source

feat: struct definitions (#12)

* feat: struct definitions

* feat: struct initialization

* chore: fix warning

* feat: use `new` keyword to initialize structs

* feat: infer struct type

* feat: allow structs as types

* feat: basic field access

* chore: fix formatting

* feat: assert function

* docs: add structs

* fix: builtin readme

* chore: refactor field access parsing

* chore: fix formatting

* feat: struct field assignments

* feat: struct field access in expressions

* feat: struct field access on function calls

* chore: fix formatting

* feat: nested structs

* chore: fix formatting

* chore: fix clippy warnings

Co-authored-by: Garrit Franke <garrit@slashdev.space>
match-statements
Garrit Franke 8 months ago
committed by GitHub
parent
commit
2c6d81c063
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      builtin/README.md
  2. 5
      builtin/builtin.c
  3. 4
      builtin/builtin.js
  4. 1
      docs/SUMMARY.md
  5. 66
      docs/concepts/structured-data.md
  6. 12
      lib/stdio.sb
  7. 3
      src/command/run.rs
  8. 5
      src/generator/c.rs
  9. 41
      src/generator/js.rs
  10. 1
      src/generator/llvm.rs
  11. 6
      src/generator/x86.rs
  12. 7
      src/lexer/mod.rs
  13. 1
      src/parser/infer.rs
  14. 12
      src/parser/node_type.rs
  15. 223
      src/parser/rules.rs
  16. 22
      src/parser/tests.rs
  17. 1
      src/util/mod.rs
  18. 1
      tests/function_return_type_explicit.sb
  19. 2
      tests/if_statement_basic.sb
  20. 2
      tests/if_statement_non_boolean_condition.sb
  21. 13
      tests/struct_initialization.sb
  22. 73
      tests/structs.sb

3
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)`

5
builtin/builtin.c

@ -7,4 +7,9 @@ void _printf(char *msg)
printf("%s", msg);
}
void _exit(int code)
{
exit(code);
}
/* END builtins */

4
builtin/builtin.js

@ -5,4 +5,8 @@ function _printf(msg) {
process.stdout.write(msg.toString());
}
function _exit(code) {
process.exit(code);
}
/* END builtins */

1
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)

66
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
```

12
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

3
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(())
}

5
src/generator/c.rs

@ -51,6 +51,7 @@ pub(super) fn generate_type(t: Either<Variable, Option<Type>>) -> 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<Expression>) -> 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::<Vec<String>>()
.join(",");

41
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>) -> 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<Expression>) -> 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::<Vec<String>>()
.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, Box<Expression>>,
) -> 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!(
"{} = {}",

1
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();

6
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");

7
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,
}
}

1
src/parser/infer.rs

@ -32,6 +32,7 @@ fn infer_expression(expr: &Expression, table: &SymbolTable) -> Option<Type> {
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,

12
src/parser/node_type.rs

@ -23,6 +23,7 @@ pub type SymbolTable = HashMap<String, Option<Type>>;
#[derive(Debug)]
pub struct Program {
pub func: Vec<Function>,
pub structs: Vec<StructDef>,
pub globals: Vec<String>,
}
@ -51,6 +52,12 @@ pub struct Function {
pub ret_type: Option<Type>,
}
#[derive(Debug, Clone)]
pub struct StructDef {
pub name: String,
pub fields: Vec<Variable>,
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct Variable {
pub name: String,
@ -64,6 +71,7 @@ pub enum Type {
Str,
Bool,
Array(Box<Type>),
Struct(String),
}
impl TryFrom<String> for Type {
@ -74,7 +82,7 @@ impl TryFrom<String> 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<Expression>),
BinOp(Box<Expression>, BinOp, Box<Expression>),
StructInitialization(String, HashMap<String, Box<Expression>>),
FieldAccess(Box<Expression>, String),
}
impl TryFrom<Token> for Expression {

223
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<Program, String> {
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<StructDef, String> {
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<Vec<Variable>, 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<Variable, String> {
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<Statement, String> {
self.match_token(TokenKind::CurlyBracesOpen)?;
@ -68,7 +119,7 @@ impl Parser {
let arguments: Vec<Variable> = 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<Vec<Variable>, 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<Variable, String> {
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<Type, String> {
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<Statement, String> {
@ -228,38 +265,27 @@ impl Parser {
fn parse_expression(&mut self) -> Result<Expression, String> {
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::<bool>().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::<u32>().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::<bool>().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::<u32>().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<Expression, String> {
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<Expression, String> {
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<HashMap<String, Box<Expression>>, 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<Expression>), 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<Expression, String> {

22
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());
}

1
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<L, R> {
Left(L),

1
tests/function_return_type_explicit.sb

@ -1,5 +1,6 @@
fn main() {
let x = add_one(2)
assert(x == 3)
println(x)
}

2
tests/if_statement_basic.sb

@ -5,5 +5,7 @@ fn main() {
println("condition was true")
} else {
println("condition was false")
// Should not be true
}
}

2
tests/if_statement_non_boolean_condition.sb

@ -3,5 +3,7 @@ fn main() {
if number {
println("number was three")
} else {
assert(false)
}
}

13
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'
}
}

73
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()
}
Loading…
Cancel
Save