Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support RETURN keyword #234

Merged
merged 11 commits into from
Aug 12, 2021
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
/target
**/*.rs.bk
**/*.un~
**/*.toml~
**/*.toml~
*.ir
*.o
11 changes: 11 additions & 0 deletions examples/function_with_return.st
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FUNCTION smaller_than_ten: BOOL
VAR_INPUT
n : INT;
END_VAR

IF n < 10 THEN
smaller_than_ten := TRUE;
RETURN;
END_IF;
smaller_than_ten := FALSE;
END_FUNCTION
5 changes: 5 additions & 0 deletions src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,9 @@ pub enum Statement {
CaseCondition {
condition: Box<Statement>,
},
ReturnStatement {
location: SourceRange,
},
}

impl Debug for Statement {
Expand Down Expand Up @@ -729,6 +732,7 @@ impl Debug for Statement {
.debug_struct("CaseCondition")
.field("condition", condition)
.finish(),
Statement::ReturnStatement { .. } => f.debug_struct("ReturnStatement").finish(),
}
}
}
Expand Down Expand Up @@ -807,6 +811,7 @@ impl Statement {
}
Statement::MultipliedStatement { location, .. } => location.clone(),
Statement::CaseCondition { condition } => condition.get_location(),
Statement::ReturnStatement { location } => location.clone(),
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/codegen/generators/pou_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ impl<'ink, 'cg> PouGenerator<'ink, 'cg> {
let statement_gen = StatementCodeGenerator::new(
&self.llvm,
self.index,
self,
implementation.pou_type,
&local_index,
&function_context,
);
Expand Down Expand Up @@ -254,7 +256,7 @@ impl<'ink, 'cg> PouGenerator<'ink, 'cg> {
/// generates the function's return statement only if the given pou_type is a `PouType::Function`
///
/// a function returns the value of the local variable that has the function's name
fn generate_return_statement(
pub fn generate_return_statement(
&self,
function_context: &FunctionContext<'ink>,
local_index: &LlvmTypedIndex<'ink>,
Expand Down
16 changes: 16 additions & 0 deletions src/codegen/generators/statement_generator.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// Copyright (c) 2020 Ghaith Hachem and Mathias Rieder
use std::ops::Range;

use super::pou_generator::PouGenerator;
use super::{expression_generator::ExpressionCodeGenerator, llvm::Llvm};
use crate::ast::PouType;
use crate::codegen::LlvmTypedIndex;
use crate::typesystem::{RANGE_CHECK_LS_FN, RANGE_CHECK_LU_FN, RANGE_CHECK_S_FN, RANGE_CHECK_U_FN};
use crate::{ast::SourceRange, codegen::llvm_typesystem::cast_if_needed};
Expand Down Expand Up @@ -31,6 +33,8 @@ pub struct FunctionContext<'a> {
pub struct StatementCodeGenerator<'a, 'b> {
llvm: &'b Llvm<'a>,
index: &'b Index,
pou_generator: &'b PouGenerator<'a, 'b>,
pou_type: PouType,
llvm_index: &'b LlvmTypedIndex<'a>,
function_context: &'b FunctionContext<'a>,

Expand All @@ -43,12 +47,16 @@ impl<'a, 'b> StatementCodeGenerator<'a, 'b> {
pub fn new(
llvm: &'b Llvm<'a>,
index: &'b Index,
pou_generator: &'b PouGenerator<'a, 'b>,
pou_type: PouType,
llvm_index: &'b LlvmTypedIndex<'a>,
linking_context: &'b FunctionContext<'a>,
) -> StatementCodeGenerator<'a, 'b> {
StatementCodeGenerator {
llvm,
index,
pou_generator,
pou_type,
llvm_index,
function_context: linking_context,
load_prefix: "load_".to_string(),
Expand Down Expand Up @@ -119,6 +127,14 @@ impl<'a, 'b> StatementCodeGenerator<'a, 'b> {
} => {
self.generate_case_statement(selector, case_blocks, else_block)?;
}
Statement::ReturnStatement { location } => {
self.pou_generator.generate_return_statement(
self.function_context,
self.llvm_index,
self.pou_type,
Some(location.clone()),
)?;
}
_ => {
self.create_expr_generator()
.generate_expression(statement)?;
Expand Down
79 changes: 79 additions & 0 deletions src/codegen/tests/code_gen_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3797,6 +3797,85 @@ fn nested_array_access() {
assert_eq!(result, expected);
}

#[test]
fn returning_early_in_function() {
let result = codegen!(
"
FUNCTION smaller_than_ten: INT
VAR_INPUT n : SINT; END_VAR
IF n < 10 THEN
RETURN;
END_IF;
END_FUNCTION
"
);

let expected = r#"; ModuleID = 'main'
source_filename = "main"

%smaller_than_ten_interface = type { i8 }

define i16 @smaller_than_ten(%smaller_than_ten_interface* %0) {
entry:
%n = getelementptr inbounds %smaller_than_ten_interface, %smaller_than_ten_interface* %0, i32 0, i32 0
%smaller_than_ten = alloca i16, align 2
%load_n = load i8, i8* %n, align 1
%1 = sext i8 %load_n to i32
%tmpVar = icmp slt i32 %1, 10
br i1 %tmpVar, label %condition_body, label %continue

condition_body: ; preds = %entry
%smaller_than_ten_ret = load i16, i16* %smaller_than_ten, align 2
ret i16 %smaller_than_ten_ret
br label %continue

continue: ; preds = %condition_body, %entry
%smaller_than_ten_ret1 = load i16, i16* %smaller_than_ten, align 2
ret i16 %smaller_than_ten_ret1
}
"#;

assert_eq!(result, expected);
}

#[test]
fn returning_early_in_function_block() {
let result = codegen!(
"
FUNCTION_BLOCK abcdef
VAR_INPUT n : SINT; END_VAR
IF n < 10 THEN
RETURN;
END_IF;
END_FUNCTION_BLOCK
"
);

let expected = r#"; ModuleID = 'main'
source_filename = "main"

%abcdef_interface = type { i8 }

define void @abcdef(%abcdef_interface* %0) {
entry:
%n = getelementptr inbounds %abcdef_interface, %abcdef_interface* %0, i32 0, i32 0
%load_n = load i8, i8* %n, align 1
%1 = sext i8 %load_n to i32
%tmpVar = icmp slt i32 %1, 10
br i1 %tmpVar, label %condition_body, label %continue

condition_body: ; preds = %entry
ret void
br label %continue

continue: ; preds = %condition_body, %entry
ret void
}
"#;

assert_eq!(result, expected);
}

#[test]
fn accessing_nested_array_in_struct() {
let result = codegen!(
Expand Down
3 changes: 3 additions & 0 deletions src/lexer/tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,9 @@ pub enum Token {
#[token("CASE")]
KeywordCase,

#[token("RETURN")]
KeywordReturn,

#[token("ARRAY")]
KeywordArray,

Expand Down
7 changes: 7 additions & 0 deletions src/parser/control_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,17 @@ pub fn parse_control_statement(lexer: &mut ParseSession) -> Statement {
KeywordWhile => parse_while_statement(lexer),
KeywordRepeat => parse_repeat_statement(lexer),
KeywordCase => parse_case_statement(lexer),
KeywordReturn => parse_return_statement(lexer),
_ => parse_statement(lexer),
}
}

fn parse_return_statement(lexer: &mut ParseSession) -> Statement {
let location = lexer.location();
lexer.advance();
Statement::ReturnStatement { location }
}

fn parse_if_statement(lexer: &mut ParseSession) -> Statement {
let start = lexer.range().start;
lexer.advance(); //If
Expand Down
10 changes: 10 additions & 0 deletions src/parser/tests/control_parser_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ fn if_statement() {
assert_eq!(ast_string, expected_ast);
}

#[test]
fn test_return_statement() {
let lexer = super::lex("PROGRAM ret RETURN END_PROGRAM");
let result = parse(lexer).0;
let prg = &result.implementations[0];
let stmt = &prg.statements[0];

assert_eq!(format!("{:?}", stmt), "ReturnStatement");
}

#[test]
fn if_else_statement_with_expressions() {
let lexer = super::lex(
Expand Down
24 changes: 24 additions & 0 deletions tests/correctness/control_flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,30 @@ fn adding_through_conditions_to_function_return() {
assert_eq!(res, 100);
}

#[test]
fn early_return_test() {
#[allow(dead_code)]
#[repr(C)]
struct MainType {
ret: i32,
}

let function = r#"
FUNCTION main : DINT
main := 100;
// Windows does not like multiple returns in a
// row. That's why we wrap it inside a dummy IF.
IF TRUE THEN
RETURN
END_IF;
main := 200;
END_FUNCTION
"#;

let (res, _) = compile_and_run(function.to_string(), &mut MainType { ret: 0 });
assert_eq!(res, 100);
}

#[test]
fn for_loop_and_increment_10_times() {
#[allow(dead_code)]
Expand Down