From 35f65101adafaaba1d4c7fc0afc648d4364c66b6 Mon Sep 17 00:00:00 2001 From: 0x4261756D <–38735823+0x4261756D@users.noreply.github.com> Date: Thu, 15 Dec 2022 21:53:01 +0100 Subject: [PATCH] Add testing functionality and add error reporting --- src/main.rs | 166 +++++++++++++--------- tests/invalid_function_name_intrinsic.qbl | 2 +- tests/missing_function_name.qbl | 2 +- tests/recursion.qbl | 11 +- tests/req_impl.qbl | 9 +- tests/unknown_function_basic.qbl | 2 +- tests/unknown_function_hard.qbl | 2 +- tests/unknown_function_in_function.qbl | 2 +- tests/while.qbl | 2 +- 9 files changed, 112 insertions(+), 86 deletions(-) diff --git a/src/main.rs b/src/main.rs index 89e3088..b6c0a41 100644 --- a/src/main.rs +++ b/src/main.rs @@ -100,29 +100,53 @@ fn main() } match args[1].as_str() { + "-t" | "--test" => + { + for f in fs::read_dir(&args[2]).unwrap() + { + let f = f.unwrap(); + let file_content = fs::read_to_string(f.path()).unwrap(); + println!("========NOW TESTING '{:?}'========", f.path()); + match compile(file_content, &intrinsics, interpret, debug) + { + Ok(()) => println!("\n\n\n---Successfully parsed '{:?}'---", f.path()), + Err(msg) => println!("ERROR: {}", msg), + } + } + } "-c" | "--compile" => { let file_content = fs::read_to_string(&args[2]).expect("Could not read the source file"); - let mut tokens: Vec = tokenize(&file_content); - println!("---Done tokenizing, got {} tokens---", tokens.len()); - let functions: Vec = extract_functions(&mut tokens, &intrinsics, debug); - println!("---Done extracting functions, got {} functions and reduced the token count to {}---", functions.len(), tokens.len()); - let operations = parse_until_delimiter(&mut tokens.iter().peekable(), &intrinsics, None, debug); - println!("---Done parsing tokens into {} operations---", operations.len()); - validate_function_calls(&operations, &functions, debug); - println!("---Done validating function calls---"); - typecheck(&operations, &functions, &intrinsics, debug); - println!("---Done typechecking---"); - if interpret + match compile(file_content, &intrinsics, interpret, debug) { - println!("---Starting to interpret the program---\n\n"); - interpret_program(&operations, &mut Vec::new(), &functions, &intrinsics, debug); + Ok(()) => println!("\n\n\n---Successfully parsed '{}'---", args[2]), + Err(msg) => println!("ERROR: {}", msg), } } _ => panic!("Unknown option {}", args[1]) } } +fn compile(file_content: String, intrinsics: &HashMap<&str, (Vec, Vec)>, interpret: bool, debug: bool) -> Result<(), String> +{ + let mut tokens: Vec = tokenize(&file_content)?; + println!("---Done tokenizing, got {} tokens---", tokens.len()); + let functions: Vec = extract_functions(&mut tokens, &intrinsics, debug)?; + println!("---Done extracting functions, got {} functions and reduced the token count to {}---", functions.len(), tokens.len()); + let operations = parse_until_delimiter(&mut tokens.iter().peekable(), &intrinsics, None, debug)?; + println!("---Done parsing tokens into {} operations---", operations.len()); + validate_function_calls(&operations, &functions, debug)?; + println!("---Done validating function calls---"); + typecheck(&operations, &functions, &intrinsics, debug)?; + println!("---Done typechecking---"); + if interpret + { + println!("---Starting to interpret the program---\n\n"); + interpret_program(&operations, &mut Vec::new(), &functions, &intrinsics, debug); + } + return Ok(()); +} + fn interpret_program(operations: &Vec, queue: &mut Vec, functions: &Vec, intrinsics: &HashMap<&str, (Vec, Vec)>, debug: bool) { for operation in operations @@ -273,7 +297,7 @@ fn interpret_program(operations: &Vec, queue: &mut Vec, funct } } -fn typecheck(operations: &Vec, functions: &Vec, intrinsics: &HashMap<&str, (Vec, Vec)>, debug: bool) +fn typecheck(operations: &Vec, functions: &Vec, intrinsics: &HashMap<&str, (Vec, Vec)>, debug: bool) -> Result<(), String> { for function in functions { @@ -281,7 +305,7 @@ fn typecheck(operations: &Vec, functions: &Vec, intrinsics: { println!("Now typechecking function '{}'", function.name); } - typecheck_block(&function.content, &function.ins, &function.outs, functions, intrinsics, debug); + typecheck_block(&function.content, &function.ins, &function.outs, functions, intrinsics, debug)?; if debug { println!("Successfully typechecked function '{}'", function.name); @@ -291,16 +315,17 @@ fn typecheck(operations: &Vec, functions: &Vec, intrinsics: { println!("Now typechecking main operations"); } - typecheck_block(operations, &Vec::new(), &Vec::new(), functions, intrinsics, debug); + typecheck_block(operations, &Vec::new(), &Vec::new(), functions, intrinsics, debug)?; if debug { println!("Successfully typechecked main operations"); } + return Ok(()); } -fn typecheck_block(operations: &Vec, ins: &Vec, outs: &Vec, functions: &Vec, intrinsics: &HashMap<&str, (Vec, Vec)>, debug: bool) +fn typecheck_block(operations: &Vec, ins: &Vec, outs: &Vec, functions: &Vec, intrinsics: &HashMap<&str, (Vec, Vec)>, debug: bool) -> Result<(), String> { - let actual_outs = get_return_type(operations, ins, functions, intrinsics, debug); + let actual_outs = get_return_type(operations, ins, functions, intrinsics, debug)?; if &actual_outs != outs { let (line, col) = match operations.last() @@ -324,11 +349,12 @@ fn typecheck_block(operations: &Vec, ins: &Vec, outs: &Vec< } None => (-1, -1) }; - panic!("Wrong queue state at the end of a block, expected {:?} but got {:?} at {}:{}", outs, actual_outs, line, col); + return Err(format!("Wrong queue state at the end of a block, expected {:?} but got {:?} at {}:{}", outs, actual_outs, line, col)); } + return Ok(()); } -fn get_return_type(operations: &Vec, ins: &Vec, functions: &Vec, intrinsics: &HashMap<&str, (Vec, Vec)>, debug: bool) -> Vec +fn get_return_type(operations: &Vec, ins: &Vec, functions: &Vec, intrinsics: &HashMap<&str, (Vec, Vec)>, debug: bool) -> Result, String> { let type_queue: &mut Vec = &mut Vec::new(); type_queue.extend_from_slice(ins); @@ -345,7 +371,7 @@ fn get_return_type(operations: &Vec, ins: &Vec, functions: { if type_queue.is_empty() { - panic!("Attempted to dequeue an element while the queue was empty at {}:{}", line, col); + return Err(format!("Attempted to dequeue an element while the queue was empty at {}:{}", line, col)); } type_queue.remove(0); } @@ -361,14 +387,14 @@ fn get_return_type(operations: &Vec, ins: &Vec, functions: } else { - panic!("Attempted to dup an element while the queue was empty at {}:{}", line, col); + return Err(format!("Attempted to dup an element while the queue was empty at {}:{}", line, col)); } } Operation::Requeue(line, col) => { if type_queue.is_empty() { - panic!("Attempted to requeue an element while the queue was empty at {}:{}", line, col); + return Err(format!("Attempted to requeue an element while the queue was empty at {}:{}", line, col)); } let typ = type_queue.remove(0); type_queue.push(typ); @@ -393,14 +419,14 @@ fn get_return_type(operations: &Vec, ins: &Vec, functions: let function = functions.iter().find(|x| &x.name == function_name).unwrap(); if function.ins.len() > type_queue.len() { - panic!("Attempted to call function '{}' at {}:{}, with insufficient elements in the queue, expected {:?} but got {:?}", function.name, line, col, function.ins, type_queue); + return Err(format!("Attempted to call function '{}' at {}:{}, with insufficient elements in the queue, expected {:?} but got {:?}", function.name, line, col, function.ins, type_queue)); } for in_type in &function.ins { let actual_type = type_queue.remove(0); if in_type != &actual_type { - panic!("Attempted to call function '{}' at {}:{} with a wrong parameter, expected {:?} but got {:?}", function.name, line, col, in_type, actual_type); + return Err(format!("Attempted to call function '{}' at {}:{} with a wrong parameter, expected {:?} but got {:?}", function.name, line, col, in_type, actual_type)); } } type_queue.extend_from_slice(&function.outs); @@ -409,18 +435,18 @@ fn get_return_type(operations: &Vec, ins: &Vec, functions: { if type_queue.is_empty() { - panic!("Encountered if block with an empty queue at {}:{}", line, col); + return Err(format!("Encountered if block with an empty queue at {}:{}", line, col)); } let comparison_type = type_queue.remove(0); if comparison_type != Datatype::Bool { - panic!("Expected a Bool as an if condition but got {:?} instead at {}:{}", comparison_type, line, col); + return Err(format!("Expected a Bool as an if condition but got {:?} instead at {}:{}", comparison_type, line, col)); } if debug { println!("Starting to typecheck if block"); } - let if_ret = get_return_type(if_block, &type_queue, functions, intrinsics, debug); + let if_ret = get_return_type(if_block, &type_queue, functions, intrinsics, debug)?; let else_ret = if let Some(else_block) = maybe_else_block { @@ -428,7 +454,7 @@ fn get_return_type(operations: &Vec, ins: &Vec, functions: { println!("Starting to typecheck else block"); } - get_return_type(else_block, &type_queue, functions, intrinsics, debug) + get_return_type(else_block, &type_queue, functions, intrinsics, debug)? } else { @@ -436,7 +462,7 @@ fn get_return_type(operations: &Vec, ins: &Vec, functions: }; if if_ret != else_ret { - panic!("Incompatible queue states after if/else construction, expected {:?} but got {:?}", if_ret, else_ret); + return Err(format!("Incompatible queue states after if/else construction, expected {:?} but got {:?}", if_ret, else_ret)); } type_queue.clear(); type_queue.extend_from_slice(&if_ret); @@ -446,14 +472,14 @@ fn get_return_type(operations: &Vec, ins: &Vec, functions: let io = intrinsics.get(intrinsic_name.as_str()).unwrap(); if io.0.len() > type_queue.len() { - panic!("Attempted to call intrinsic '{}' at {}:{}, with insufficient elements in the queue, expected {:?} but got {:?}", intrinsic_name, line, col, io.0, type_queue); + return Err(format!("Attempted to call intrinsic '{}' at {}:{}, with insufficient elements in the queue, expected {:?} but got {:?}", intrinsic_name, line, col, io.0, type_queue)); } for in_type in &io.0 { let actual_type = type_queue.remove(0); if in_type != &actual_type { - panic!("Attempted to call intrinsic '{}' at {}:{} with a wrong parameter, expected {:?} but got {:?}", intrinsic_name, line, col, in_type, actual_type); + return Err(format!("Attempted to call intrinsic '{}' at {}:{} with a wrong parameter, expected {:?} but got {:?}", intrinsic_name, line, col, in_type, actual_type)); } } type_queue.extend_from_slice(&io.1); @@ -462,7 +488,7 @@ fn get_return_type(operations: &Vec, ins: &Vec, functions: { if type_queue.is_empty() { - panic!("Encountered while block with an empty queue at {}:{}", line, col); + return Err(format!("Encountered while block with an empty queue at {}:{}", line, col)); } let comparison_type = type_queue.remove(0); if comparison_type != Datatype::Bool @@ -475,7 +501,7 @@ fn get_return_type(operations: &Vec, ins: &Vec, functions: } let mut outs = type_queue.clone(); outs.insert(0, Datatype::Bool); - typecheck_block(while_block, type_queue, &outs, functions, intrinsics, debug); + typecheck_block(while_block, type_queue, &outs, functions, intrinsics, debug)?; } Operation::Depth(_, _) => { @@ -491,27 +517,28 @@ fn get_return_type(operations: &Vec, ins: &Vec, functions: println!("{} => {:?}", debug_string, type_queue); } } - return type_queue.clone(); + return Ok(type_queue.clone()); } -fn validate_function_calls(operations: &Vec, functions: &Vec, debug: bool) +fn validate_function_calls(operations: &Vec, functions: &Vec, debug: bool) -> Result<(), String> { for function in functions { - validate_function_calls_in_block(&function.content, functions, debug); + validate_function_calls_in_block(&function.content, functions, debug)?; if debug { println!("Successfully validated function calls in function '{}'", function.name); } } - validate_function_calls_in_block(operations, functions, debug); + validate_function_calls_in_block(operations, functions, debug)?; if debug { println!("Successfully validated function calls in main operations"); } + return Ok(()); } -fn validate_function_calls_in_block(block: &Vec, functions: &Vec, debug: bool) +fn validate_function_calls_in_block(block: &Vec, functions: &Vec, debug: bool) -> Result<(), String> { for operation in block { @@ -523,26 +550,27 @@ fn validate_function_calls_in_block(block: &Vec, functions: &Vec { - validate_function_calls_in_block(if_block, functions, debug); + validate_function_calls_in_block(if_block, functions, debug)?; if let Some(else_block) = maybe_else_block { - validate_function_calls_in_block(else_block, functions, debug); + validate_function_calls_in_block(else_block, functions, debug)?; } } Operation::While(while_block, _, _) => { - validate_function_calls_in_block(while_block, functions, debug); + validate_function_calls_in_block(while_block, functions, debug)?; } } } + return Ok(()); } -fn extract_functions(tokens: &mut Vec, intrinsics: &HashMap<&str, (Vec, Vec)>, debug: bool) -> Vec +fn extract_functions(tokens: &mut Vec, intrinsics: &HashMap<&str, (Vec, Vec)>, debug: bool) -> Result, String> { let mut tokens_iter = tokens.iter().peekable(); let mut functions: Vec = Vec::new(); @@ -569,7 +597,7 @@ fn extract_functions(tokens: &mut Vec, intrinsics: &HashMap<&str, (Vec { - panic!("Expected input parameters for a function but got {:?} instead at {}:{}", token, line, col); + return Err(format!("Expected input parameters for a function but got {:?} instead at {}:{}", token, line, col)); } Token::Keyword(word, line, col) => { @@ -583,12 +611,12 @@ fn extract_functions(tokens: &mut Vec, intrinsics: &HashMap<&str, (Vec ins.push(Datatype::String), "int" => ins.push(Datatype::Int), "bool" => ins.push(Datatype::Bool), - _ => panic!("Expected input parameters for a function but got {} instead at {}:{}", word, line, col) + _ => return Err(format!("Expected input parameters for a function but got {} instead at {}:{}", word, line, col)) } } } } - None => panic!("Unexpected end of file while extracting a function") + None => return Err(format!("Unexpected end of file while extracting a function")) } } if debug @@ -607,7 +635,7 @@ fn extract_functions(tokens: &mut Vec, intrinsics: &HashMap<&str, (Vec { - panic!("Expected input parameters for a function but got {:?} instead at {}:{}", token, line, col); + return Err(format!("Expected input parameters for a function but got {:?} instead at {}:{}", token, line, col)); } Token::Keyword(word, line, col) => { @@ -617,22 +645,22 @@ fn extract_functions(tokens: &mut Vec, intrinsics: &HashMap<&str, (Vec outs.push(Datatype::String), "int" => outs.push(Datatype::Int), "bool" => outs.push(Datatype::Bool), - "{" | "}" | "deq" | "req" | "dup" | "swp" | "true" | "false" | "depth" | "???" => panic!("Expected function name but got {} at {}:{}", word, line, col), + "{" | "}" | "deq" | "req" | "dup" | "swp" | "true" | "false" | "depth" | "???" => return Err(format!("Expected function name but got {} at {}:{}", word, line, col)), _ => { if functions.iter().any(|x| &x.name == word) { - panic!("Redeclaration of function '{}' at {}:{}", word, line, col); + return Err(format!("Redeclaration of function '{}' at {}:{}", word, line, col)); } if intrinsics.contains_key(word.as_str()) { - panic!("Function name {} at {}:{} is already an intrinsic", word, line, col); + return Err(format!("Function name {} at {}:{} is already an intrinsic", word, line, col)); } if debug { println!("outs: {:?}", outs); } - let block = parse_block(&mut tokens_iter, intrinsics, debug); + let block = parse_block(&mut tokens_iter, intrinsics, debug)?; functions.push(Function {name: word.clone(), ins, outs, content: block}); break; } @@ -640,7 +668,7 @@ fn extract_functions(tokens: &mut Vec, intrinsics: &HashMap<&str, (Vec panic!("Unexpected end of file while extracting a function") + None => return Err(format!("Unexpected end of file while extracting a function")) } } } @@ -656,26 +684,26 @@ fn extract_functions(tokens: &mut Vec, intrinsics: &HashMap<&str, (Vec>, intrinsics: &HashMap<&str, (Vec, Vec)>, debug: bool) -> Vec +fn parse_block(tokens_iter: &mut Peekable>, intrinsics: &HashMap<&str, (Vec, Vec)>, debug: bool) -> Result, String> { if let Some(Token::Keyword(word, line, col)) = tokens_iter.next() { if word != "{" { - panic!("Expected '{{' to open a block but got {} at {}:{}", word, line, col); + return Err(format!("Expected '{{' to open a block but got {} at {}:{}", word, line, col)); } } else { - panic!("Expected '{{' to open a block"); + return Err(format!("Expected '{{' to open a block")); } return parse_until_delimiter(tokens_iter, intrinsics, Some("}"), debug); } -fn parse_until_delimiter(tokens_iter: &mut Peekable>, intrinsics: &HashMap<&str, (Vec, Vec)>, delimiter: Option<&str>, debug: bool) -> Vec +fn parse_until_delimiter(tokens_iter: &mut Peekable>, intrinsics: &HashMap<&str, (Vec, Vec)>, delimiter: Option<&str>, debug: bool) -> Result, String> { let mut operations: Vec = Vec::new(); loop @@ -707,14 +735,14 @@ fn parse_until_delimiter(tokens_iter: &mut Peekable>, in } else if word == "if" { - let block = parse_block(tokens_iter, intrinsics, debug); + let block = parse_block(tokens_iter, intrinsics, debug)?; let else_block = if let Some(Token::Keyword(maybe_else, _, _)) = tokens_iter.peek() { if maybe_else == "else" { tokens_iter.next(); - Some(parse_block(tokens_iter, intrinsics, debug)) + Some(parse_block(tokens_iter, intrinsics, debug)?) } else { @@ -729,7 +757,7 @@ fn parse_until_delimiter(tokens_iter: &mut Peekable>, in } else if word == "while" { - operations.push(Operation::While(parse_block(tokens_iter, intrinsics, debug), *line, *col)); + operations.push(Operation::While(parse_block(tokens_iter, intrinsics, debug)?, *line, *col)); } else if word == "deq" { @@ -757,11 +785,11 @@ fn parse_until_delimiter(tokens_iter: &mut Peekable>, in } else if Some(word.as_str()) == delimiter { - return operations; + return Ok(operations); } else if word == "{" || word == "function" { - panic!("Unexpected keyword {} at {}:{}", word, line, col); + return Err(format!("Unexpected keyword {} at {}:{}", word, line, col)); } else { @@ -774,11 +802,11 @@ fn parse_until_delimiter(tokens_iter: &mut Peekable>, in { if delimiter.is_some() { - panic!("Reached the end of the file while parsing a block") + return Err(format!("Reached the end of the file while parsing a block")); } else { - return operations; + return Ok(operations); } } } @@ -791,7 +819,7 @@ fn usage() exit(0); } -fn tokenize(text: &str) -> Vec +fn tokenize(text: &str) -> Result, String> { let mut tokens: Vec = Vec::new(); let mut line = 1; @@ -869,7 +897,7 @@ fn tokenize(text: &str) -> Vec { match ch { - '"' => panic!("Having '\"' in the middle of a word is not allowed"), + '"' => return Err(format!("Having '\"' in the middle of a word is not allowed")), _ => { word.push(ch); @@ -889,7 +917,7 @@ fn tokenize(text: &str) -> Vec { TokenizerState::Quote => { - panic!("Encountered EOF before closing string"); + return Err(format!("Encountered EOF before closing string")); } TokenizerState::Whitespace | TokenizerState::Comment => {}, TokenizerState::Keyword => @@ -897,5 +925,5 @@ fn tokenize(text: &str) -> Vec tokens.push(Token::Keyword(word.clone(), line, col)); } } - tokens + Ok(tokens) } diff --git a/tests/invalid_function_name_intrinsic.qbl b/tests/invalid_function_name_intrinsic.qbl index a6e5833..a40633e 100644 --- a/tests/invalid_function_name_intrinsic.qbl +++ b/tests/invalid_function_name_intrinsic.qbl @@ -1,4 +1,4 @@ -//invalid,Function name print at 1:22 is already an intrinsic +//invalid,Function name print at 3:22 is already an intrinsic function int => print { diff --git a/tests/missing_function_name.qbl b/tests/missing_function_name.qbl index dd7c237..ef8b34e 100644 --- a/tests/missing_function_name.qbl +++ b/tests/missing_function_name.qbl @@ -1,4 +1,4 @@ -//invalid,Expected function name but got { at 2:2 +//invalid,Expected function name but got { at 4:2 function int => { diff --git a/tests/recursion.qbl b/tests/recursion.qbl index c3ee8a3..46f7b9e 100644 --- a/tests/recursion.qbl +++ b/tests/recursion.qbl @@ -1,13 +1,14 @@ -function int int int => int fibonacci +function int int int int => int fibonacci { - dup if + dup 0 == req req req req + if { - req deq deq + req deq deq deq } else { - 1 dup + - swp fibonacci + dup + req 1 req swp - req req req fibonacci } } -20 1 0 fibonacci print \ No newline at end of file +20 0 1 0 fibonacci print \ No newline at end of file diff --git a/tests/req_impl.qbl b/tests/req_impl.qbl index 2788564..8f7cb8c 100644 --- a/tests/req_impl.qbl +++ b/tests/req_impl.qbl @@ -1,7 +1,4 @@ -function int int => int int req_impl -{ - dup deq -} +function int => int req_impl { } -1 2 3 req_impl print print print -1 2 3 req print print print \ No newline at end of file +1 2 3 req_impl print print println +1 2 3 req print print println \ No newline at end of file diff --git a/tests/unknown_function_basic.qbl b/tests/unknown_function_basic.qbl index 6ea98f6..fdace0f 100644 --- a/tests/unknown_function_basic.qbl +++ b/tests/unknown_function_basic.qbl @@ -1,2 +1,2 @@ -//invalid,Call to unknown function foo at 1:4 +//invalid,Call to unknown function foo at 2:4 foo \ No newline at end of file diff --git a/tests/unknown_function_hard.qbl b/tests/unknown_function_hard.qbl index c30c165..a7169a3 100644 --- a/tests/unknown_function_hard.qbl +++ b/tests/unknown_function_hard.qbl @@ -1,4 +1,4 @@ -//invalid,Call to unknown function bar at 7:7 +//invalid,Call to unknown function bar at 9:7 function int => foo { diff --git a/tests/unknown_function_in_function.qbl b/tests/unknown_function_in_function.qbl index cbb7e94..9ba2efa 100644 --- a/tests/unknown_function_in_function.qbl +++ b/tests/unknown_function_in_function.qbl @@ -1,4 +1,4 @@ -//invalid,Call to unknown function bar at 3:5 +//invalid,Call to unknown function bar at 5:5 function => foo { diff --git a/tests/while.qbl b/tests/while.qbl index cb203dd..e9584b5 100644 --- a/tests/while.qbl +++ b/tests/while.qbl @@ -1,4 +1,4 @@ -//valid,output:10987654321 +//valid,output:10987654321falsefalse true while {