Skip to content

Commit

Permalink
Merge pull request #95 from hexaredecimal/inline_functions
Browse files Browse the repository at this point in the history
Inline functions redo
  • Loading branch information
garritfra authored Apr 5, 2024
2 parents 34803d5 + b3d4b2f commit eafd354
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 15 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

## Unreleased

xxx
**Features**

- Support for shorthand function bodies ([#94](https://github.com/antimony-lang/antimony/pull/94))

## v0.7.0 (2022-06-15)

Expand Down
30 changes: 30 additions & 0 deletions docs/concepts/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,33 @@ fn add_one(x: int): int {
$ sb run main.sb
2
```

# Simplified Function Syntax for Single Statements

Antimony supports a more concise syntax for functions that perform a single operation. This syntax is particularly useful for simple tasks, such as arithmetic operations, printing to the console, or returning a single expression. Instead of wrapping the function's body in curly braces, you can define the function using an equals sign (`=`) followed by the expression that constitutes the function's body.

## Syntax

The syntax for this simplified function declaration is as follows:

```
fn function_name(parameters): return_type = expression
```

This syntax removes the need for curly braces and the `return` keyword for single-statement functions, making the code cleaner and more readable.

## Examples

Below are examples demonstrating how to use this syntax:

**Defining a function that adds two numbers**:

```
fn add(x: int, y: int): int = x + y
```

**Defining a function that concatenates two strings**:

```
fn concat(a: string, b: string): string = a + b
```
6 changes: 2 additions & 4 deletions examples/greeter.sb
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
fn greet(name: string) = "Hello " + name

fn main() {
println(greet("World"))
}

fn greet(name: string): string {
return "Hello " + name
}
37 changes: 37 additions & 0 deletions src/command/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
use crate::command::build;
use crate::generator::Target;
use std::fs::OpenOptions;
use std::io::Read;
use std::io::Write;
use std::path::PathBuf;
Expand Down Expand Up @@ -50,6 +51,42 @@ pub fn run(target: Target, in_file: PathBuf) -> Result<(), String> {
.write_all(&s)
.map_err(|e| format!("Could not write to stdout: {}", e))?;
}
Target::Qbe => {
let dir_path = "./"; // TODO: Use this for changind build directory
let filename = in_file.file_stem().unwrap().to_str().unwrap();
let ssa_path = format!("{dir_path}{}.ssa", filename);
let asm_path = format!("{dir_path}{}.s", filename);
let exe_path = format!("{dir_path}{}.exe", filename);

let mut ssa_file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(&ssa_path)
.unwrap();
let buff = *buf;
ssa_file.write_all(&buff).unwrap();

// SSA to ASM
Command::new("qbe")
.arg(&ssa_path)
.arg("-o")
.arg(&asm_path)
.spawn()
.unwrap();

// ASM to EXE
Command::new("gcc")
.arg(&asm_path)
.arg("-o")
.arg(&exe_path)
.spawn()
.unwrap();

// Run the EXE
Command::new(exe_path).spawn().unwrap();
}
_ => todo!(),
}
Ok(())
Expand Down
5 changes: 5 additions & 0 deletions src/parser/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,11 @@ impl Parser {
}
}

pub(super) fn make_hint_msg(&mut self, msg: String) -> String {
let new_lines = "\n".repeat(3);
format!("{new_lines}Hint: {}\n", msg)
}

pub(super) fn prev(&mut self) -> Option<Token> {
self.prev.clone()
}
Expand Down
46 changes: 36 additions & 10 deletions src/parser/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,23 @@ impl Parser {
_ => None,
};

let body = self.parse_block()?;
let peeked_kind = self.peek()?.kind;
let body = match peeked_kind {
TokenKind::CurlyBracesOpen => self.parse_block()?,
TokenKind::Assign => self.parse_inline_function()?,
_ => {
let token = self.peek()?;
let mut error = self.make_error_msg(
token.pos,
format!("Expected `{{` or `=`, got {}", token.raw),
);
let hint = self.make_hint_msg(format!(
"Try the following:\nfn {name}(...) = expression\nOr\nfn {name}(...) {{ ... }}"
));
error.push_str(&hint);
return Err(error);
}
};

Ok(Function {
name,
Expand All @@ -169,6 +185,15 @@ impl Parser {
})
}

fn parse_inline_function(&mut self) -> Result<Statement, String> {
self.next()?;
let expr = self.parse_expression()?;
let return_statment = Statement::Return(Some(expr));
let statements = vec![return_statment];
let scope = vec![];
Ok(Statement::Block { statements, scope })
}

fn parse_import(&mut self) -> Result<String, String> {
self.match_keyword(Keyword::Import)?;
let token = self.next()?;
Expand Down Expand Up @@ -380,16 +405,17 @@ impl Parser {
};

// Check if the parsed expression continues
if self.peek_token(TokenKind::Dot).is_ok() {
// foo.bar
self.parse_field_access(expr)
} else if BinOp::try_from(self.peek()?.kind).is_ok() {
// 1 + 2
self.parse_bin_op(Some(expr))
} else {
// Nope, the expression was fully parsed
Ok(expr)
if let Ok(next) = self.peek() {
if next.kind == TokenKind::Dot {
// foo.bar
return self.parse_field_access(expr);
} else if BinOp::try_from(next.kind).is_ok() {
// 1 + 2
return self.parse_bin_op(Some(expr));
}
}
// Nope, the expression was fully parsed
Ok(expr)
}

fn parse_field_access(&mut self, lhs: Expression) -> Result<Expression, String> {
Expand Down
30 changes: 30 additions & 0 deletions src/parser/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,36 @@ fn test_parse_function_with_return() {
assert!(tree.is_ok())
}

#[test]
fn test_parse_inline_function() {
let raw = "
fn greet(name: string) = \"Hello \" + name
fn main() {
println(greet(\"World\"))
}
";
let tokens = tokenize(raw).unwrap();
let tree = parse(tokens, Some(raw.to_string()), "".into());
assert!(tree.is_ok())
}

#[test]
#[ignore]
// I don't know how this fails yet. It seems to have something to do with how
// `parse_expression` peeks tokens. It tries to peek a token after the
// expression body but it's empty, so it errors out.
fn test_parse_inline_function_as_last_statement() {
let raw = "
fn main() {
println(greet(\"World\"))
}
fn greet(name: string) = \"Hello \" + name
";
let tokens = tokenize(raw).unwrap();
let tree = parse(tokens, Some(raw.to_string()), "".into());
assert!(tree.is_ok())
}

#[test]
fn test_parse_redundant_semicolon() {
let raw = "
Expand Down

0 comments on commit eafd354

Please sign in to comment.