Skip to content

Commit

Permalink
Merge branch 'master' into feat/std-text
Browse files Browse the repository at this point in the history
  • Loading branch information
MuhamedMagdi committed Jul 30, 2024
2 parents ace421a + 03c7705 commit fa15f13
Show file tree
Hide file tree
Showing 18 changed files with 179 additions and 26 deletions.
2 changes: 1 addition & 1 deletion src/modules/command/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub struct CommandExpr {

impl Typed for CommandExpr {
fn get_type(&self) -> Type {
Type::Text
Type::Failable(Box::new(<Type::Text>));
}
}

Expand Down
8 changes: 8 additions & 0 deletions src/modules/expression/unop/cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ impl SyntaxModule<ParserMetadata> for Cast {
let message = Message::new_warn_at_token(meta, tok)
.message(format!("Casting a value of type '{l_type}' value to a '{r_type}' is not recommended"))
.comment(format!("To suppress this warning, use '{flag_name}' compiler flag"));
let (l_type, r_type) = match (l_type, r_type) {
(Type::Failable(l), Type::Failable(r)) => (*l, *r),
(Type::Failable(_), _) | (_, Type::Failable(_)) => {
meta.add_message(message);
return Ok(());
},
types => types
};
match (l_type, r_type) {
(Type::Array(left), Type::Array(right)) => {
if *left != *right && !matches!(*left, Type::Bool | Type::Num) && !matches!(*right, Type::Bool | Type::Num) {
Expand Down
14 changes: 13 additions & 1 deletion src/modules/function/declaration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,9 @@ impl SyntaxModule<ParserMetadata> for FunctionDeclaration {
self.arg_types.push(Type::Generic);
}
}
if let Type::Failable(_) = arg_type {
return error!(meta, name_token, "Failable types cannot be used as arguments");
}
match token(meta,"=") {
Ok(_) => {
if is_ref {
Expand All @@ -198,14 +201,23 @@ impl SyntaxModule<ParserMetadata> for FunctionDeclaration {
Err(_) => token(meta, ",")?
};
}
let mut returns_tok = None;
// Optionally parse the return type
match token(meta, ":") {
Ok(_) => self.returns = parse_type(meta)?,
Ok(_) => {
returns_tok = meta.get_current_token();
self.returns = parse_type(meta)?
},
Err(_) => self.returns = Type::Generic
}
// Parse the body
token(meta, "{")?;
let (index_begin, index_end, is_failable) = skip_function_body(meta);
if is_failable && !matches!(self.returns, Type::Failable(_) | Type::Generic) {
return error!(meta, returns_tok, "Failable functions must return a Failable type");
} else if !is_failable && matches!(self.returns, Type::Failable(_)) {
return error!(meta, returns_tok, "Non-failable functions cannot return a Failable type");
}
// Create a new context with the function body
let expr = meta.context.expr[index_begin..index_end].to_vec();
let ctx = meta.context.clone().function_invocation(expr);
Expand Down
14 changes: 9 additions & 5 deletions src/modules/function/invocation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,17 @@ impl SyntaxModule<ParserMetadata> for FunctionInvocation {
}
}

let types = self.args.iter().map(|e| e.get_type()).collect::<Vec<Type>>();
let var_names = self.args.iter().map(|e| e.is_var()).collect::<Vec<bool>>();
self.refs.clone_from(&function_unit.arg_refs);
(self.kind, self.variant_id) = handle_function_parameters(meta, self.id, function_unit.clone(), &types, &var_names, tok.clone())?;

self.is_failable = function_unit.is_failable;
if self.is_failable {
match syntax(meta, &mut self.failed) {
Ok(_) => {},
Ok(_) => if let Type::Failable(t) = &self.kind {
self.kind = *t.clone();
},
Err(Failure::Quiet(_)) => return error!(meta, tok => {
message: "This function can fail. Please handle the failure",
comment: "You can use '?' in the end to propagate the failure"
Expand All @@ -113,10 +120,7 @@ impl SyntaxModule<ParserMetadata> for FunctionInvocation {
meta.add_message(message);
}
}
let types = self.args.iter().map(|e| e.get_type()).collect::<Vec<Type>>();
let var_names = self.args.iter().map(|e| e.is_var()).collect::<Vec<bool>>();
self.refs.clone_from(&function_unit.arg_refs);
(self.kind, self.variant_id) = handle_function_parameters(meta, self.id, function_unit, &types, &var_names, tok)?;

Ok(())
})
}
Expand Down
25 changes: 18 additions & 7 deletions src/modules/function/ret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,26 @@ impl SyntaxModule<ParserMetadata> for Return {
});
}
syntax(meta, &mut self.expr)?;
match meta.context.fun_ret_type.as_ref() {
Some(ret_type) => if ret_type != &self.expr.get_type() {
return error!(meta, tok => {
message: "Return type does not match function return type",
comment: format!("Given type: {}, expected type: {}", self.expr.get_type(), ret_type)
});
let ret_type = meta.context.fun_ret_type.as_ref();
let expr_type = &self.expr.get_type();
// Unpacking Failable types
let (ret_type, expr_type) = match (ret_type, expr_type) {
types @ (Some(Type::Failable(_)), Type::Failable(_)) => types,
(Some(Type::Failable(ret_type)), expr_type) => (Some(ret_type.as_ref()), expr_type),
(Some(ret_type), Type::Failable(expr_type)) => (Some(ret_type), expr_type.as_ref()),
types @ _ => types
};
match ret_type {
Some(ret_type) => {
if ret_type != expr_type {
return error!(meta, tok => {
message: "Return type does not match function return type",
comment: format!("Given type: {}, expected type: {}", expr_type, ret_type)
});
}
},
None => {
meta.context.fun_ret_type = Some(self.expr.get_type());
meta.context.fun_ret_type = Some(expr_type.clone());
}
}
Ok(())
Expand Down
12 changes: 10 additions & 2 deletions src/modules/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub enum Type {
Num,
Null,
Array(Box<Type>),
Failable(Box<Type>),
Generic
}

Expand All @@ -21,6 +22,7 @@ impl Display for Type {
Type::Num => write!(f, "Num"),
Type::Null => write!(f, "Null"),
Type::Array(t) => write!(f, "[{}]", t),
Type::Failable(t) => write!(f, "{}?", t),
Type::Generic => write!(f, "Generic")
}
}
Expand All @@ -40,7 +42,7 @@ pub fn parse_type(meta: &mut ParserMetadata) -> Result<Type, Failure> {
// Tries to parse the type - if it fails, it fails quietly
pub fn try_parse_type(meta: &mut ParserMetadata) -> Result<Type, Failure> {
let tok = meta.get_current_token();
match tok.clone() {
let res = match tok.clone() {
Some(matched_token) => {
match matched_token.word.as_ref() {
"Text" => {
Expand Down Expand Up @@ -97,5 +99,11 @@ pub fn try_parse_type(meta: &mut ParserMetadata) -> Result<Type, Failure> {
None => {
Err(Failure::Quiet(PositionInfo::at_eof(meta)))
}
};

if token(meta, "?").is_ok() {
return res.map(|t| Type::Failable(Box::new(t)))
}
}

res
}
2 changes: 1 addition & 1 deletion src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pub fn get_rules() -> Rules {
let symbols = vec![
'+', '-', '*', '/', '%', '\n', ';', ':',
'(', ')', '[', ']', '{', '}', ',', '.',
'<', '>', '=', '!'
'<', '>', '=', '!', '?'
];
let compounds = vec![
('<', '='),
Expand Down
10 changes: 5 additions & 5 deletions src/std/env.ab
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,23 @@ pub fun shell_isset(name: Text): Bool {
return true
}

pub fun shell_constant_set(name: Text, val: Text): Null {
pub fun shell_constant_set(name: Text, val: Text): Null? {
$readonly \${nameof name}="\${nameof val}" 2> /dev/null$?
}

pub fun shell_constant_get(name: Text): Text {
pub fun shell_constant_get(name: Text): Text? {
return $echo \$\{!{nameof name}}$?
}

pub fun shell_var_set(name: Text, val: Text): Null {
pub fun shell_var_set(name: Text, val: Text): Null? {
$export \${nameof name}="\${nameof val}" 2> /dev/null$?
}

pub fun shell_var_get(name: Text): Text {
pub fun shell_var_get(name: Text): Text? {
return $echo \$\{!{nameof name}}$?
}

pub fun shell_unset(name: Text): Null {
pub fun shell_unset(name: Text): Null? {
$unset {name}$?
}

Expand Down
6 changes: 3 additions & 3 deletions src/std/text.ab
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
pub fun replace_once(source, pattern, replacement) {
return unsafe $echo "\$\{source/{pattern}/{replacement}}"$
return "\$\{source/{pattern}/{replacement}}"
}

pub fun replace(source, pattern, replacement) {
return unsafe $echo "\$\{source//{pattern}/{replacement}}"$
return "\$\{source//{pattern}/{replacement}}"
}

pub fun replace_regex(source: Text, pattern: Text, replacement: Text): Text {
Expand Down Expand Up @@ -52,7 +52,7 @@ pub fun upper(text: Text): Text {
}

#[allow_absurd_cast]
pub fun parse(text: Text): Num {
pub fun parse(text: Text): Num? {
$[ -n "{text}" ] && [ "{text}" -eq "{text}" ] 2>/dev/null$?
return text as Num
}
Expand Down
55 changes: 55 additions & 0 deletions src/tests/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use crate::compiler::AmberCompiler;
use crate::Cli;
use crate::test_amber;

#[test]
#[should_panic(expected = "ERROR: Return type does not match function return type")]
fn function_with_wrong_typed_return() {
let code = r#"
pub fun test(): Num {
return "Hello, World!"
}
echo test()
"#;

test_amber!(code, "Hello, World!");
}

#[test]
#[should_panic(expected = "ERROR: Failable functions must return a Failable type")]
fn function_failable_with_typed_nonfailable_return() {
let code = r#"
pub fun test(): Null {
fail 1
}
echo test() failed: echo "Failed"
"#;

test_amber!(code, "Failed");
}

#[test]
#[should_panic(expected = "ERROR: Non-failable functions cannot return a Failable type")]
fn function_nonfailable_with_typed_failable_return() {
let code = r#"
pub fun test(): Null? {
echo "Hello, World!"
}
echo test() failed: echo "Failed"
"#;

test_amber!(code, "Hello, World!");
}

#[test]
#[should_panic(expected = "ERROR: Failable types cannot be used as arguments")]
fn function_with_failable_typed_arg() {
let code = r#"
pub fun test(a: Text?) {
echo a
}
test("Hello, World!")
"#;

test_amber!(code, "Hello, World!");
}
1 change: 1 addition & 0 deletions src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::Cli;

pub mod cli;
pub mod formatter;
pub mod errors;
pub mod stdlib;
pub mod validity;

Expand Down
11 changes: 11 additions & 0 deletions src/tests/validity/failed_block_unwraps_failable_type.ab
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
fun failable(): Num? {
if 0 > 5 {
fail 1
}

return 1
}

let a = failable() failed: echo "Failed"

if a is Num: echo "Succeded"
11 changes: 11 additions & 0 deletions src/tests/validity/failed_unwraps_failable_type.ab
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
fun failable(): Num? {
if 0 > 5 {
fail 1
}

return 1
}

let a = failable() failed: echo "Failed"

if a is Num: echo "Succeded"
9 changes: 9 additions & 0 deletions src/tests/validity/function_failable.ab
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
fun test() {
if 0 < 5 {
fail 1
}

return 42
}

test() failed: echo "Succeded"
9 changes: 9 additions & 0 deletions src/tests/validity/function_failable_with_typed_return.ab
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
pub fun test(): Num? {
if 0 < 5 {
fail 1
}

return 42
}

echo test() failed: echo "Succeded"
5 changes: 5 additions & 0 deletions src/tests/validity/function_with_typed_return.ab
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub fun test(): Text {
return "Succeded"
}

echo test()
2 changes: 1 addition & 1 deletion src/tests/validity/unsafe_function_call.ab
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Output
// 6, 0

fun safe_division(a: Num, b: Num): Num {
fun safe_division(a: Num, b: Num): Num? {
if b == 0:
fail 1
return a / b
Expand Down
9 changes: 9 additions & 0 deletions src/tests/validity/unsafe_unwraps_failable_type.ab
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
fun test(): Num? {
if 0 < 5 {
fail 1
}

return 42
}

if unsafe test() is Num: echo "Succeded"

0 comments on commit fa15f13

Please sign in to comment.