Compare commits

...

5 Commits

Author SHA1 Message Date
Araozu 2830a1befd Refactor Binding 2023-12-16 20:51:12 -05:00
Araozu 3e592392a8 Change syntax from val/var to let (mut) 2023-12-16 20:47:42 -05:00
Araozu 7379c469d3 Small changes 2023-12-16 20:35:24 -05:00
Araozu 498f8fb87f Re-enable compile command 2023-12-13 20:29:04 -05:00
Araozu 3d5c7769e6 Now every cli command handles its own arguments 2023-12-13 20:03:23 -05:00
13 changed files with 132 additions and 141 deletions

View File

@ -22,11 +22,11 @@
## v0.0.9 ## v0.0.9
- [ ] Hand made CLI, remove clap - [x] Hand-make CLI, remove clap
- [ ] Compile a single file - [x] Compile a single file
- [ ] Implement code generation for ast nodes implemented as of now - [ ] Display error messages during compilation instead of panicking
- [ ] Display error messages during compilation
- [ ] Improve errror messages - [ ] Improve errror messages
- [ ] Implement code generation for ast nodes implemented as of now

50
src/cli/compile.rs Normal file
View File

@ -0,0 +1,50 @@
use colored::*;
pub fn compile_command(arguments: Vec<String>) {
if arguments.is_empty() {
println!("{}", compile_help());
println!("{}: {}", "error".on_red(), "No file specified");
return;
}
if arguments.len() > 1 {
println!("{}", compile_help());
println!(
"{}: {}",
"error".on_red(),
"Only a single file can be compiled at a time"
);
return;
}
let argument = &arguments[0];
if argument.starts_with("-") {
let opt_str = argument.as_str();
println!("{}", compile_help());
if opt_str != "-h" && opt_str != "--help" {
println!(
"{}: {}",
"error".on_red(),
"Invalid option. The compile command only accepts the `-h` or `--help` option"
);
}
return;
}
crate::file::compile_file(argument);
}
fn compile_help() -> String {
format!(
r#"Compile a single file in place. If the file to compile
references other THP files, they will be (typechecked?) as well.
Usage:
`thp compile {0}` Compile {0} and output in the same directory
`thp compile -h` Print this message & exit
"#,
"_file_".green()
)
}

View File

@ -7,11 +7,11 @@ enum EmptyOptions {
Version, Version,
} }
pub fn empty_command(options: &Vec<String>) { pub fn empty_command(arguments: Vec<String>) {
// Add all options to a set // Add all options to a set
let mut options_set = std::collections::HashSet::new(); let mut options_set = std::collections::HashSet::new();
for option in options { for option in arguments {
match expand_option(option) { match expand_option(&option) {
Ok(o) => { Ok(o) => {
options_set.insert(o); options_set.insert(o);
} }

View File

@ -1,14 +1,14 @@
use crate::cli::get_help_text; use crate::cli::get_help_text;
use colored::*; use colored::*;
pub fn help_command(options: &Vec<String>) { pub fn help_command(arguments: Vec<String>) {
println!("{}", get_help_text()); println!("{}", get_help_text());
if options.len() > 0 { if arguments.len() > 0 {
println!( println!(
"{}: {}", "{}: {}",
"warning".yellow(), "warning".yellow(),
"The help command doesn't take any options." "The help command doesn't take any argument."
); );
} }
} }

View File

@ -1,8 +1,9 @@
mod compile;
mod empty; mod empty;
mod help; mod help;
mod types; mod types;
use types::{Command, CommandType}; use types::CommandType;
use colored::*; use colored::*;
@ -39,7 +40,7 @@ fn get_version() -> String {
} }
pub fn run_cli() { pub fn run_cli() {
let command = match parse_args() { let (command, args) = match parse_args() {
Ok(c) => c, Ok(c) => c,
Err(reason) => { Err(reason) => {
println!("{}", get_help_text()); println!("{}", get_help_text());
@ -48,38 +49,17 @@ pub fn run_cli() {
} }
}; };
command.run(); command.run(args);
} }
fn parse_args() -> Result<Command, String> { fn parse_args() -> Result<(CommandType, Vec<String>), String> {
let mut args = std::env::args().collect::<Vec<String>>(); let mut args = std::env::args().collect::<Vec<String>>();
// Remove the first argument, which is the path to the executable
args.remove(0); args.remove(0);
let mut args = args.into_iter(); let command = match args.get(0) {
let mut options = Vec::new(); Some(command) if !command.starts_with('-') => match command.as_str() {
let command = match args.next() {
Some(command) if !command.starts_with('-') => Some(command),
Some(option) => {
options.push(option);
None
}
_ => None,
};
for arg in args {
if arg.starts_with('-') {
options.push(arg);
} else {
return Err(format!(
"Unexpected command `{}`. There can only be one command",
arg
));
}
}
let command = match command {
Some(command) => match command.as_str() {
"c" | "compile" => CommandType::Compile, "c" | "compile" => CommandType::Compile,
"f" | "format" => CommandType::Format, "f" | "format" => CommandType::Format,
"r" | "repl" => CommandType::Repl, "r" | "repl" => CommandType::Repl,
@ -90,8 +70,12 @@ fn parse_args() -> Result<Command, String> {
"help" | "h" => CommandType::Help, "help" | "h" => CommandType::Help,
_ => return Err(format!("Unknown command `{}`", command)), _ => return Err(format!("Unknown command `{}`", command)),
}, },
None => CommandType::None, _ => CommandType::None,
}; };
Ok(Command { command, options }) if command != CommandType::None {
args.remove(0);
}
Ok((command, args))
} }

View File

@ -1,10 +1,4 @@
#[derive(Debug)] #[derive(Debug, PartialEq)]
pub struct Command {
pub command: CommandType,
pub options: Vec<String>,
}
#[derive(Debug)]
pub enum CommandType { pub enum CommandType {
Compile, Compile,
Format, Format,
@ -17,16 +11,11 @@ pub enum CommandType {
None, None,
} }
impl Command {
pub fn run(&self) {
self.command.run(&self.options);
}
}
impl CommandType { impl CommandType {
pub fn run(&self, options: &Vec<String>) { pub fn run(&self, options: Vec<String>) {
match self { match self {
CommandType::Help => super::help::help_command(options), CommandType::Help => super::help::help_command(options),
CommandType::Compile => super::compile::compile_command(options),
CommandType::None => super::empty::empty_command(options), CommandType::None => super::empty::empty_command(options),
_ => { _ => {
println!("Not implemented yet! {:?} {:?}", self, options); println!("Not implemented yet! {:?} {:?}", self, options);

View File

@ -4,38 +4,27 @@ use crate::syntax::ast::var_binding::Binding;
impl Transpilable for Binding { impl Transpilable for Binding {
/// Transpiles val and var bindings into PHP. /// Transpiles val and var bindings into PHP.
fn transpile(&self) -> String { fn transpile(&self) -> String {
match self { let expression_str = self.expression.transpile();
Binding::Val(val_binding) => {
let expression_str = val_binding.expression.transpile();
format!("${} = {};", val_binding.identifier, expression_str) format!("${} = {};", self.identifier, expression_str)
}
Binding::Var(var_binding) => {
let expression_str = var_binding.expression.transpile();
format!("${} = {};", var_binding.identifier, expression_str)
}
}
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::syntax::ast::{ use crate::syntax::ast::{var_binding::Binding, Expression};
var_binding::{Binding, ValBinding},
Expression,
};
#[test] #[test]
fn binding_should_transpile() { fn binding_should_transpile() {
let id = String::from("identifier"); let id = String::from("identifier");
let value = String::from("322"); let value = String::from("322");
let binding = Binding::Val(ValBinding { let binding = Binding {
datatype: None, datatype: None,
identifier: Box::new(id), identifier: Box::new(id),
expression: Expression::Number(Box::new(value)), expression: Expression::Number(Box::new(value)),
}); is_mutable: false,
};
let result = binding.transpile(); let result = binding.transpile();

View File

@ -18,20 +18,18 @@ impl Transpilable for ModuleAST {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::syntax::ast::{ use crate::syntax::ast::{var_binding::Binding, Expression, TopLevelDeclaration};
var_binding::{Binding, ValBinding},
Expression, TopLevelDeclaration,
};
#[test] #[test]
fn module_ast_should_transpile() { fn module_ast_should_transpile() {
let id = String::from("identifier"); let id = String::from("identifier");
let value = String::from("322"); let value = String::from("322");
let binding = Binding::Val(ValBinding { let binding = Binding {
datatype: None, datatype: None,
identifier: Box::new(id), identifier: Box::new(id),
expression: Expression::Number(Box::new(value)), expression: Expression::Number(Box::new(value)),
}); is_mutable: false,
};
let module = ModuleAST { let module = ModuleAST {
declarations: vec![TopLevelDeclaration::Binding(binding)], declarations: vec![TopLevelDeclaration::Binding(binding)],

View File

@ -4,8 +4,8 @@ use crate::lexic::{token::Token, utils, LexResult};
/// Checks if a String is a keyword, and returns its TokenType /// Checks if a String is a keyword, and returns its TokenType
fn str_is_keyword(s: &String) -> Option<TokenType> { fn str_is_keyword(s: &String) -> Option<TokenType> {
match s.as_str() { match s.as_str() {
"var" => Some(TokenType::VAR), "let" => Some(TokenType::LET),
"val" => Some(TokenType::VAL), "mut" => Some(TokenType::MUT),
"fun" => Some(TokenType::FUN), "fun" => Some(TokenType::FUN),
_ => None, _ => None,
} }
@ -141,23 +141,23 @@ mod tests {
// Should scan keywords // Should scan keywords
#[test] #[test]
fn test_4() { fn test_4() {
let input = str_to_vec("var"); let input = str_to_vec("mut");
let start_pos = 0; let start_pos = 0;
if let LexResult::Some(token, next) = scan(*input.get(0).unwrap(), &input, start_pos) { if let LexResult::Some(token, next) = scan(*input.get(0).unwrap(), &input, start_pos) {
assert_eq!(3, next); assert_eq!(3, next);
assert_eq!(TokenType::VAR, token.token_type); assert_eq!(TokenType::MUT, token.token_type);
assert_eq!("var", token.value); assert_eq!("mut", token.value);
assert_eq!(0, token.position); assert_eq!(0, token.position);
} else { } else {
panic!() panic!()
} }
let input = str_to_vec("val"); let input = str_to_vec("let");
let start_pos = 0; let start_pos = 0;
if let LexResult::Some(token, next) = scan(*input.get(0).unwrap(), &input, start_pos) { if let LexResult::Some(token, next) = scan(*input.get(0).unwrap(), &input, start_pos) {
assert_eq!(3, next); assert_eq!(3, next);
assert_eq!(TokenType::VAL, token.token_type); assert_eq!(TokenType::LET, token.token_type);
assert_eq!("val", token.value); assert_eq!("let", token.value);
assert_eq!(0, token.position); assert_eq!(0, token.position);
} else { } else {
panic!() panic!()

View File

@ -15,8 +15,8 @@ pub enum TokenType {
Comment, Comment,
INDENT, INDENT,
DEDENT, DEDENT,
VAR, LET,
VAL, MUT,
EOF, EOF,
FUN, FUN,
} }

View File

@ -1,21 +1,9 @@
use super::Expression; use super::Expression;
#[derive(Debug)] #[derive(Debug)]
pub enum Binding { pub struct Binding {
Val(ValBinding),
Var(VarBinding),
}
#[derive(Debug)]
pub struct ValBinding {
pub datatype: Option<String>,
pub identifier: Box<String>,
pub expression: Expression,
}
#[derive(Debug)]
pub struct VarBinding {
pub datatype: Option<String>, pub datatype: Option<String>,
pub identifier: Box<String>, pub identifier: Box<String>,
pub expression: Expression, pub expression: Expression,
pub is_mutable: bool,
} }

View File

@ -1,4 +1,4 @@
use super::ast::var_binding::{Binding, ValBinding, VarBinding}; use super::ast::var_binding::Binding;
use super::utils::{parse_token_type, try_operator}; use super::utils::{parse_token_type, try_operator};
use super::{expression, ParseResult}; use super::{expression, ParseResult};
use crate::error_handling::SyntaxError; use crate::error_handling::SyntaxError;
@ -8,23 +8,23 @@ use crate::utils::Result3;
pub fn try_parse<'a>(tokens: &'a Vec<Token>, pos: usize) -> ParseResult<Binding, ()> { pub fn try_parse<'a>(tokens: &'a Vec<Token>, pos: usize) -> ParseResult<Binding, ()> {
let mut current_pos = pos; let mut current_pos = pos;
// TODO: Detect if the binding starts with a datatype
/* /*
* val/var keyword * let keyword
*/ */
let (is_val, binding_token, next_pos) = { let (is_mutable, binding_token, next_pos) = {
let res1 = parse_token_type(tokens, current_pos, TokenType::VAL); let let_token = parse_token_type(tokens, current_pos, TokenType::LET);
match res1 { match let_token {
ParseResult::Ok(val_token, next) => (true, val_token, next), ParseResult::Ok(let_token, next_let) => {
_ => { let mut_token = parse_token_type(tokens, next_let, TokenType::MUT);
let res2 = parse_token_type(tokens, current_pos, TokenType::VAR); match mut_token {
match res2 { ParseResult::Ok(_mut_token, next_mut) => (true, let_token, next_mut),
ParseResult::Ok(var_token, next) => (false, var_token, next), _ => (false, let_token, next_let),
// Neither VAL nor VAR were matched, the caller should try }
// other constructs }
_ => return ParseResult::Unmatched, _ => return ParseResult::Unmatched,
} }
}
}
}; };
current_pos = next_pos; current_pos = next_pos;
@ -50,7 +50,7 @@ pub fn try_parse<'a>(tokens: &'a Vec<Token>, pos: usize) -> ParseResult<Binding,
return ParseResult::Err(SyntaxError { return ParseResult::Err(SyntaxError {
reason: format!( reason: format!(
"There should be an identifier after a `{}` token", "There should be an identifier after a `{}` token",
if is_val { "val" } else { "var" } if is_mutable { "val" } else { "var" }
), ),
error_start: binding_token.position, error_start: binding_token.position,
error_end: binding_token.get_end_position(), error_end: binding_token.get_end_position(),
@ -95,18 +95,11 @@ pub fn try_parse<'a>(tokens: &'a Vec<Token>, pos: usize) -> ParseResult<Binding,
}; };
current_pos = next_pos; current_pos = next_pos;
let binding = if is_val { let binding = Binding {
Binding::Val(ValBinding {
datatype: None, datatype: None,
identifier: Box::new(identifier.value.clone()), identifier: Box::new(identifier.value.clone()),
expression, expression,
}) is_mutable,
} else {
Binding::Var(VarBinding {
datatype: None,
identifier: Box::new(identifier.value.clone()),
expression,
})
}; };
ParseResult::Ok(binding, current_pos) ParseResult::Ok(binding, current_pos)
@ -119,8 +112,8 @@ mod tests {
#[test] #[test]
fn should_parse_val_binding() { fn should_parse_val_binding() {
let tokens = get_tokens(&String::from("val identifier = 20")).unwrap(); let tokens = get_tokens(&String::from("let identifier = 20")).unwrap();
let ParseResult::Ok(Binding::Val(binding), _) = try_parse(&tokens, 0) else { let ParseResult::Ok(binding, _) = try_parse(&tokens, 0) else {
panic!() panic!()
}; };
@ -129,11 +122,11 @@ mod tests {
#[test] #[test]
fn should_parse_val() { fn should_parse_val() {
let tokens = get_tokens(&String::from("val")).unwrap(); let tokens = get_tokens(&String::from("let")).unwrap();
let token = *try_token_type(&tokens, 0, TokenType::VAL).unwrap(); let token = *try_token_type(&tokens, 0, TokenType::LET).unwrap();
assert_eq!(TokenType::VAL, token.token_type); assert_eq!(TokenType::LET, token.token_type);
assert_eq!("val", token.value); assert_eq!("let", token.value);
} }
#[test] #[test]
@ -175,8 +168,8 @@ mod tests {
#[test] #[test]
fn should_return_correct_error() { fn should_return_correct_error() {
let tokens = get_tokens(&String::from("val")).unwrap(); let tokens = get_tokens(&String::from("let")).unwrap();
assert_eq!(TokenType::VAL, tokens[0].token_type); assert_eq!(TokenType::LET, tokens[0].token_type);
assert_eq!(0, tokens[0].position); assert_eq!(0, tokens[0].position);
let binding = try_parse(&tokens, 0); let binding = try_parse(&tokens, 0);
@ -191,8 +184,8 @@ mod tests {
#[test] #[test]
fn should_return_error_when_identifier_is_wrong() { fn should_return_error_when_identifier_is_wrong() {
let tokens = get_tokens(&String::from("val 322")).unwrap(); let tokens = get_tokens(&String::from("let 322")).unwrap();
assert_eq!(TokenType::VAL, tokens[0].token_type); assert_eq!(TokenType::LET, tokens[0].token_type);
assert_eq!(0, tokens[0].position); assert_eq!(0, tokens[0].position);
let binding = try_parse(&tokens, 0); let binding = try_parse(&tokens, 0);
@ -204,7 +197,7 @@ mod tests {
_ => panic!("Error expected"), _ => panic!("Error expected"),
} }
let tokens = get_tokens(&String::from("val \"hello\"")).unwrap(); let tokens = get_tokens(&String::from("let \"hello\"")).unwrap();
let binding = try_parse(&tokens, 0); let binding = try_parse(&tokens, 0);
match binding { match binding {
@ -218,7 +211,7 @@ mod tests {
#[test] #[test]
fn should_return_error_when_equal_op_is_wrong() { fn should_return_error_when_equal_op_is_wrong() {
let tokens = get_tokens(&String::from("val id \"error\"")).unwrap(); let tokens = get_tokens(&String::from("let id \"error\"")).unwrap();
let binding = try_parse(&tokens, 0); let binding = try_parse(&tokens, 0);
match binding { match binding {

View File

@ -39,7 +39,7 @@ mod tests {
#[test] #[test]
fn should_parse_binding() { fn should_parse_binding() {
let input = String::from("val identifier = 20"); let input = String::from("let identifier = 20");
let tokens = crate::lexic::get_tokens(&input).unwrap(); let tokens = crate::lexic::get_tokens(&input).unwrap();
let statement = try_parse(&tokens, 0); let statement = try_parse(&tokens, 0);