Compare commits

..

No commits in common. "f97b8e2e07853c375e63b39144149d0d41653083" and "e1a0afba36444e634760487de0e541d783c14f25" have entirely different histories.

16 changed files with 160 additions and 274 deletions

View File

@ -2,11 +2,6 @@
## TODO ## TODO
- Implement AST transformation before codegen:
Create a new AST to represent PHP source code
and a THP ast -> PHP ast process, so that the
codegen section can focus only in codegen, not in
translation of thp->php.
- Parse __more__ binary operators - Parse __more__ binary operators
- Parse `Type name = value` bindings - Parse `Type name = value` bindings
- Parse more complex bindings - Parse more complex bindings
@ -31,9 +26,8 @@
- [x] Begin work on semantic analysis - [x] Begin work on semantic analysis
- [x] Minimal symbol table - [x] Minimal symbol table
- [x] Check duplicate function declarations - [x] Check duplicate function declarations
- [x] Improve REPL/File compilation code
- [ ] Typecheck bindings - [ ] Typecheck bindings
- [x] Typecheck functions - [ ] Typecheck functions
- [ ] Transform simple THP expression into PHP statements - [ ] Transform simple THP expression into PHP statements
## v0.0.9 ## v0.0.9

View File

@ -1,19 +1,19 @@
use colored::*; use colored::*;
pub fn compile_command(arguments: Vec<String>) -> Result<(), ()> { pub fn compile_command(arguments: Vec<String>) {
if arguments.is_empty() { if arguments.is_empty() {
eprintln!("{}", compile_help()); println!("{}", compile_help());
eprintln!("{}: {}", "error".on_red(), "No file specified"); println!("{}: {}", "error".on_red(), "No file specified");
return Err(()); return;
} }
if arguments.len() > 1 { if arguments.len() > 1 {
eprintln!("{}", compile_help()); println!("{}", compile_help());
eprintln!( println!(
"{}: {}", "{}: {}",
"error".on_red(), "error".on_red(),
"Only a single file can be compiled at a time" "Only a single file can be compiled at a time"
); );
return Err(()); return;
} }
let argument = &arguments[0]; let argument = &arguments[0];
@ -23,16 +23,16 @@ pub fn compile_command(arguments: Vec<String>) -> Result<(), ()> {
println!("{}", compile_help()); println!("{}", compile_help());
if opt_str != "-h" && opt_str != "--help" { if opt_str != "-h" && opt_str != "--help" {
eprintln!( println!(
"{}: {}", "{}: {}",
"error".on_red(), "error".on_red(),
"Invalid option. The compile command only accepts the `-h` or `--help` options" "Invalid option. The compile command only accepts the `-h` or `--help` option"
); );
} }
return Err(()); return;
} }
crate::file::compile_file(argument) crate::file::compile_file(argument);
} }
fn compile_help() -> String { fn compile_help() -> String {

View File

@ -7,7 +7,7 @@ enum EmptyOptions {
Version, Version,
} }
pub fn empty_command(arguments: Vec<String>) -> Result<(), ()> { 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 arguments { for option in arguments {
@ -16,9 +16,9 @@ pub fn empty_command(arguments: Vec<String>) -> Result<(), ()> {
options_set.insert(o); options_set.insert(o);
} }
Err(invalid_option) => { Err(invalid_option) => {
eprintln!("{}", get_help_text()); println!("{}", get_help_text());
eprintln!("{}: invalid option: `{}`", "error".on_red(), invalid_option); println!("{}: invalid option: `{}`", "error".on_red(), invalid_option);
return Err(()); return;
} }
}; };
} }
@ -35,8 +35,6 @@ pub fn empty_command(arguments: Vec<String>) -> Result<(), ()> {
println!("{}", get_help_text()); println!("{}", get_help_text());
} }
} }
Ok(())
} }
fn expand_option(option: &String) -> Result<EmptyOptions, String> { fn expand_option(option: &String) -> Result<EmptyOptions, String> {

View File

@ -1,7 +1,7 @@
use crate::cli::get_help_text; use crate::cli::get_help_text;
use colored::*; use colored::*;
pub fn help_command(arguments: Vec<String>) -> Result<(), ()> { pub fn help_command(arguments: Vec<String>) {
println!("{}", get_help_text()); println!("{}", get_help_text());
if arguments.len() > 0 { if arguments.len() > 0 {
@ -11,6 +11,4 @@ pub fn help_command(arguments: Vec<String>) -> Result<(), ()> {
"The help command doesn't take any argument." "The help command doesn't take any argument."
); );
} }
Ok(())
} }

View File

@ -40,17 +40,17 @@ fn get_version() -> String {
format!("The THP compiler, linter & formatter, v{}", crate_version) format!("The THP compiler, linter & formatter, v{}", crate_version)
} }
pub fn run_cli() -> Result<(), ()> { pub fn run_cli() {
let (command, args) = match parse_args() { let (command, args) = match parse_args() {
Ok(c) => c, Ok(c) => c,
Err(reason) => { Err(reason) => {
eprintln!("{}", get_help_text()); println!("{}", get_help_text());
eprintln!("{}: {}", "error".on_red(), reason); println!("{}: {}", "error".on_red(), reason);
return Err(()); return;
} }
}; };
command.run(args) command.run(args);
} }
fn parse_args() -> Result<(CommandType, Vec<String>), String> { fn parse_args() -> Result<(CommandType, Vec<String>), String> {

View File

@ -1,13 +1,4 @@
use colored::Colorize; pub fn repl_command(_arguments: Vec<String>) {
pub fn repl_command(_arguments: Vec<String>) -> Result<(), ()> {
println!("{}", super::get_version()); println!("{}", super::get_version());
let result = crate::repl::run(); let _ = crate::repl::run();
if let Err(e) = result {
eprintln!("{}: {}", "error".on_red(), e);
return Err(());
}
Ok(())
} }

View File

@ -12,15 +12,14 @@ pub enum CommandType {
} }
impl CommandType { impl CommandType {
pub fn run(&self, options: Vec<String>) -> Result<(), ()> { 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::Compile => super::compile::compile_command(options),
CommandType::Repl => super::repl::repl_command(options), CommandType::Repl => super::repl::repl_command(options),
CommandType::None => super::empty::empty_command(options), CommandType::None => super::empty::empty_command(options),
_ => { _ => {
eprintln!("Not implemented yet! {:?} {:?}", self, options); println!("Not implemented yet! {:?} {:?}", self, options);
Err(())
} }
} }
} }

View File

@ -1,9 +1,5 @@
use self::semantic_error::SemanticError;
mod lex_error; mod lex_error;
pub mod semantic_error;
mod syntax_error; mod syntax_error;
mod utils;
pub trait PrintableError { pub trait PrintableError {
fn get_error_str(&self, chars: &Vec<char>) -> String; fn get_error_str(&self, chars: &Vec<char>) -> String;
@ -13,7 +9,6 @@ pub trait PrintableError {
pub enum MistiError { pub enum MistiError {
Lex(LexError), Lex(LexError),
Syntax(SyntaxError), Syntax(SyntaxError),
Semantic(SemanticError),
} }
#[derive(Debug)] #[derive(Debug)]
@ -34,7 +29,6 @@ impl PrintableError for MistiError {
match self { match self {
Self::Lex(err) => err.get_error_str(chars), Self::Lex(err) => err.get_error_str(chars),
Self::Syntax(err) => err.get_error_str(chars), Self::Syntax(err) => err.get_error_str(chars),
Self::Semantic(err) => err.get_error_str(chars),
} }
} }
} }

View File

@ -1,31 +0,0 @@
use super::utils::{get_line, get_line_number};
use super::PrintableError;
#[derive(Debug)]
pub struct SemanticError {
pub error_start: usize,
pub error_end: usize,
pub reason: String,
}
impl PrintableError for SemanticError {
fn get_error_str(&self, chars: &Vec<char>) -> String {
let (line, before, length) = get_line(chars, self.error_start, self.error_end);
let line_number = get_line_number(chars, self.error_start);
let line_number_whitespace = " ".repeat(line_number.to_string().len());
let whitespace = vec![' '; before].iter().collect::<String>();
let indicator = vec!['^'; length].iter().collect::<String>();
let reason = &self.reason;
format!(
r#"
{line_number_whitespace} |
{line_number } | {line}
{line_number_whitespace} | {whitespace}{indicator}
{reason} at line {line_number}:{before}"#,
)
}
}

View File

@ -1,5 +1,5 @@
use super::utils::{get_line, get_line_number};
use super::{PrintableError, SyntaxError}; use super::{PrintableError, SyntaxError};
use std::collections::VecDeque;
impl PrintableError for SyntaxError { impl PrintableError for SyntaxError {
fn get_error_str(&self, chars: &Vec<char>) -> String { fn get_error_str(&self, chars: &Vec<char>) -> String {
@ -23,6 +23,96 @@ impl PrintableError for SyntaxError {
} }
} }
/// Extracts a line of code
///
/// - `chars`: Input where to extract the line from
/// - `start_position`: Position where the erroneous code starts
/// - `end_position`: Position where the erroneous code ends
///
/// Returns a tuple of:
///
/// - `String`: The faulty line
/// - `usize`: The amount of chars *before* the faulty code
/// - `usize`: The lenght of the faulty code
///
/// ## Example
///
/// ```
/// let input = String::from("\n\nval number == 50\n\n").chars().into_iter().collect();
/// let start_position = 13;
/// let end_position = 15;
///
/// let (line, before, length) = get_line(&input, start_position, end_position);
///
/// assert_eq!("val number == 50", line);
/// assert_eq!(11, before);
/// assert_eq!(2, length);
/// ```
fn get_line(
chars: &Vec<char>,
start_position: usize,
end_position: usize,
) -> (String, usize, usize) {
let mut result_chars = VecDeque::<char>::new();
// Push chars to the front until a new line is found
let mut before_pos = start_position;
loop {
let current_char = chars[before_pos];
if current_char == '\n' {
// This is important because before_pos will be used to calculate
// the number of chars before start_position
before_pos += 1;
break;
}
result_chars.push_front(current_char);
if before_pos == 0 {
break;
}
before_pos -= 1;
}
// Push chars to the end until a new line is found
let mut after_pos = start_position + 1;
let char_count = chars.len();
while after_pos < char_count {
let current_char = chars[after_pos];
if current_char == '\n' {
break;
}
result_chars.push_back(current_char);
after_pos += 1;
}
(
result_chars.iter().collect::<String>(),
start_position - before_pos,
end_position - start_position,
)
}
fn get_line_number(chars: &Vec<char>, target_pos: usize) -> usize {
let mut count = 1;
for (pos, char) in chars.iter().enumerate() {
if pos >= target_pos {
break;
}
if *char == '\n' {
count += 1;
}
}
count
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -1,91 +0,0 @@
use std::collections::VecDeque;
/// Extracts a line of code
///
/// - `chars`: Input where to extract the line from
/// - `start_position`: Position where the erroneous code starts
/// - `end_position`: Position where the erroneous code ends
///
/// Returns a tuple of:
///
/// - `String`: The faulty line
/// - `usize`: The amount of chars *before* the faulty code
/// - `usize`: The lenght of the faulty code
///
/// ## Example
///
/// ```
/// let input = String::from("\n\nval number == 50\n\n").chars().into_iter().collect();
/// let start_position = 13;
/// let end_position = 15;
///
/// let (line, before, length) = get_line(&input, start_position, end_position);
///
/// assert_eq!("val number == 50", line);
/// assert_eq!(11, before);
/// assert_eq!(2, length);
/// ```
pub fn get_line(
chars: &Vec<char>,
start_position: usize,
end_position: usize,
) -> (String, usize, usize) {
let mut result_chars = VecDeque::<char>::new();
// Push chars to the front until a new line is found
let mut before_pos = start_position;
loop {
let current_char = chars[before_pos];
if current_char == '\n' {
// This is important because before_pos will be used to calculate
// the number of chars before start_position
before_pos += 1;
break;
}
result_chars.push_front(current_char);
if before_pos == 0 {
break;
}
before_pos -= 1;
}
// Push chars to the end until a new line is found
let mut after_pos = start_position + 1;
let char_count = chars.len();
while after_pos < char_count {
let current_char = chars[after_pos];
if current_char == '\n' {
break;
}
result_chars.push_back(current_char);
after_pos += 1;
}
(
result_chars.iter().collect::<String>(),
start_position - before_pos,
end_position - start_position,
)
}
pub fn get_line_number(chars: &Vec<char>, target_pos: usize) -> usize {
let mut count = 1;
for (pos, char) in chars.iter().enumerate() {
if pos >= target_pos {
break;
}
if *char == '\n' {
count += 1;
}
}
count
}

View File

@ -4,102 +4,77 @@ use std::{fs, path::Path};
use crate::lexic::token::Token; use crate::lexic::token::Token;
use crate::{codegen, error_handling::PrintableError, lexic, syntax}; use crate::{codegen, error_handling::PrintableError, lexic, syntax};
pub fn compile_file(input: &String) -> Result<(), ()> { pub fn compile_file(input: &String) {
let input_path = Path::new(input); let input_path = Path::new(input);
if !input_path.is_file() { if !input_path.is_file() {
eprintln!( println!(
"{}: {} {}", "{}: {} {}",
"error".on_red(), "error".on_red(),
"Input path is not a valid file:".red(), "Input path is not a valid file:".red(),
input input
); );
return Err(()); return;
} }
let bytes = match fs::read(input_path) { let bytes = fs::read(input_path).expect("INPUT_PATH should be valid");
Ok(bytes) => bytes,
Err(error) => {
eprintln!("{}: Error reading input file", "error".on_red());
eprintln!("{}", error);
return Err(());
}
};
let contents = match String::from_utf8(bytes) { let contents = match String::from_utf8(bytes) {
Ok(str) => str, Ok(str) => str,
Err(error) => { Err(_) => {
eprintln!("{}: Input file contains invalid UTF-8", "error".on_red()); println!("{}: Input file contains invalid UTF-8", "error".on_red());
eprintln!("{}", error); return;
return Err(());
} }
}; };
let out_code = match compile(&contents) { let Some(out_code) = compile(&contents) else {
Ok(out_code) => out_code, return;
Err(error) => {
eprintln!("{}", error);
return Err(());
}
}; };
let mut output_path = Path::new(input) let mut output_path = Path::new(input).canonicalize().unwrap();
.canonicalize()
.expect("Invalid input path: Cannot be canonicalized");
output_path.set_extension("php"); output_path.set_extension("php");
match fs::write(output_path, out_code) { fs::write(output_path, out_code).expect("Error writing to output path");
Ok(_) => Ok(()),
Err(error) => {
eprintln!("{}: Error writing output file", "error".on_red());
eprintln!("{}", error);
Err(())
}
}
} }
/// THP source code goes in, PHP code or an error comes out /// Executes Lexical analysis, handles errors and calls build_ast for the next phase
fn compile(input: &String) -> Result<String, String> { fn compile(input: &String) -> Option<String> {
let tokens = lexic::get_tokens(input); let tokens = lexic::get_tokens(input);
let tokens = match tokens { match tokens {
Ok(tokens) => tokens, Ok(tokens) => Some(build_ast(input, tokens)),
Err(error) => { Err(error) => {
let chars: Vec<char> = input.chars().into_iter().collect(); let chars: Vec<char> = input.chars().into_iter().collect();
return Err(format!( println!(
"{}:\n{}", "{}:\n{}",
"syntax error".on_red(), "syntax error".on_red(),
error.get_error_str(&chars) error.get_error_str(&chars)
)); );
None
}
} }
};
build_ast(input, tokens)
} }
/// Executes Syntax analysis, and for now, Semantic analysis and Code generation. /// Executes Syntax analysis, and for now, Semantic analysis and Code generation.
/// ///
/// Prints the generated code in stdin /// Prints the generated code in stdin
fn build_ast(input: &String, tokens: Vec<Token>) -> Result<String, String> { fn build_ast(input: &String, tokens: Vec<Token>) -> String {
let ast = syntax::construct_ast(&tokens); let ast = syntax::construct_ast(&tokens);
let ast = match ast { match ast {
Ok(ast) => ast, Ok(ast) => {
Err(reason) => {
let chars: Vec<char> = input.chars().into_iter().collect();
let error = format!("{}: {}", "error".on_red(), reason.get_error_str(&chars));
return Err(error);
}
};
match crate::semantic::check_semantics(&ast) { match crate::semantic::check_semantics(&ast) {
Ok(_) => {} Ok(_) => {}
Err(reason) => { Err(reason) => {
let chars: Vec<char> = input.chars().into_iter().collect(); panic!("{}", reason)
let error = format!("{}: {}", "error".on_red(), reason.get_error_str(&chars));
return Err(error);
} }
}; };
Ok(codegen::codegen(&ast)) codegen::codegen(&ast)
}
Err(reason) => {
let chars: Vec<char> = input.chars().into_iter().collect();
panic!("{}", reason.get_error_str(&chars))
}
}
} }

View File

@ -18,8 +18,5 @@ mod utils;
mod error_handling; mod error_handling;
fn main() { fn main() {
match cli::run_cli() { cli::run_cli();
Ok(_) => (),
Err(_) => std::process::exit(1),
}
} }

View File

@ -1,7 +1,5 @@
use std::io::{self, Write}; use std::io::{self, Write};
use colored::Colorize;
use crate::error_handling::PrintableError; use crate::error_handling::PrintableError;
use crate::lexic::token::Token; use crate::lexic::token::Token;
@ -36,9 +34,7 @@ fn build_ast(input: &String, tokens: Vec<Token>) {
match res1 { match res1 {
Ok(_) => {} Ok(_) => {}
Err(reason) => { Err(reason) => {
let chars: Vec<char> = input.chars().into_iter().collect(); eprintln!("{}", reason);
let error = format!("{}: {}", "error".on_red(), reason.get_error_str(&chars));
eprintln!("{}", error);
return; return;
} }
} }

View File

@ -1,18 +1,13 @@
use crate::{ use crate::syntax::ast::{ModuleAST, TopLevelDeclaration};
error_handling::semantic_error::SemanticError,
error_handling::MistiError,
syntax::ast::{ModuleAST, TopLevelDeclaration},
};
use super::symbol_table::SymbolTable; use super::symbol_table::SymbolTable;
pub trait SemanticCheck { pub trait SemanticCheck {
fn check_semantics(&self, scope: &SymbolTable) -> Result<(), MistiError>; fn check_semantics(&self, scope: &SymbolTable) -> Result<(), String>;
} }
impl SemanticCheck for ModuleAST { impl SemanticCheck for ModuleAST {
/// Checks that this AST is semantically correct, given a symbol table fn check_semantics(&self, scope: &SymbolTable) -> Result<(), String> {
fn check_semantics(&self, scope: &SymbolTable) -> Result<(), MistiError> {
for declaration in &self.declarations { for declaration in &self.declarations {
declaration.check_semantics(scope)?; declaration.check_semantics(scope)?;
} }
@ -22,30 +17,14 @@ impl SemanticCheck for ModuleAST {
} }
impl SemanticCheck for TopLevelDeclaration { impl SemanticCheck for TopLevelDeclaration {
fn check_semantics(&self, scope: &SymbolTable) -> Result<(), MistiError> { fn check_semantics(&self, scope: &SymbolTable) -> Result<(), String> {
match self { match self {
TopLevelDeclaration::Binding(_) => { TopLevelDeclaration::Binding(_) => Err("Binding not implemented".into()),
let error = SemanticError {
error_start: 0,
error_end: 0,
reason: "Binding typechecking: Not implemented".into(),
};
Err(MistiError::Semantic(error))
}
TopLevelDeclaration::FunctionDeclaration(function) => { TopLevelDeclaration::FunctionDeclaration(function) => {
let function_name = function.identifier.as_ref().clone(); let function_name = function.identifier.as_ref().clone();
if scope.test(&function_name) { if scope.test(&function_name) {
let error = SemanticError { return Err(format!("Function {} already defined", function_name));
// TODO: Get the position of the function name. For this, these structs
// should store the token instead of just the string
error_start: 0,
error_end: 0,
reason: format!("Function {} already defined", function_name),
};
return Err(MistiError::Semantic(error));
} }
scope.insert( scope.insert(

View File

@ -1,4 +1,4 @@
use crate::{error_handling::MistiError, syntax::ast::ModuleAST}; use crate::syntax::ast::ModuleAST;
mod impls; mod impls;
mod symbol_table; mod symbol_table;
@ -11,11 +11,8 @@ use impls::SemanticCheck;
// 3. Add the symbols declared to the symbol table, annotating them with their type // 3. Add the symbols declared to the symbol table, annotating them with their type
// 4. Check if the symbols used are declared // 4. Check if the symbols used are declared
/// Checks that the AST is semantically correct pub fn check_semantics(ast: &ModuleAST) -> Result<(), String> {
pub fn check_semantics(ast: &ModuleAST) -> Result<(), MistiError> {
// For now there's only support for a single file // For now there's only support for a single file
// TODO: Receive a symbol table as a reference and work on it.
// this way we can implement a unique symbol table for REPL session
let global_scope = symbol_table::SymbolTable::new(); let global_scope = symbol_table::SymbolTable::new();
ast.check_semantics(&global_scope) ast.check_semantics(&global_scope)