summaryrefslogblamecommitdiff
path: root/src/main.rs
blob: f271bcdc655ef5875a0d3f80dd73328cc6506aee (plain) (tree)
1
2
3
4
5
6
7
8
9
             
            
           
           
            
                    
                   
                  
                  
                   




                                            
                      
























                                                                         
                                                                   
                                                             
                                                             
                                                                                                      
      








































                                                                               
 



                                                                               



                                                                                  
                                                      
                                                                              
                                                               
                                                                
                                                     
















                                                                                   


                                                                                   













                                                                                   















                                                                                       

                    
         

           
 



                                                   
                                     


                                         
         
     
mod compiler;
mod parsers;
mod report;
mod tokens;
mod formats;

pub use compiler::*;
pub use parsers::*;
pub use report::*;
pub use tokens::*;
pub use formats::*;

pub use assembler::*;
use log::{info, fatal};
use switchboard::{Switchboard, SwitchQuery};

use std::io::{Read, Write};
use std::str::FromStr;


fn print_version() -> ! {
    let version = env!("CARGO_PKG_VERSION");
    eprintln!("torque assembler, version {version}");
    eprintln!("written by ben bridle");
    std::process::exit(0);
}

fn main() {
    let mut args = Switchboard::from_env();
    if args.named("version").as_bool() {
        print_version();
    }
    if args.named("verbose").short('v').as_bool() {
        log::set_log_level(log::LogLevel::Info);
    }
    let source_path = args.positional("source").as_path_opt().map(
        |p| p.canonicalize().unwrap_or_else(|e| fatal!("{p:?}: {e:?}")));
    let destination_path = args.positional("destination").as_path_opt();
    let extension = args.named("ext").default("tq").as_string();

    let no_libs = args.named("no-libs").as_bool();
    let no_project_libs = args.named("no-project-libs").as_bool();
    let no_environment_libs = args.named("no-env-libs").as_bool();

    let format = args.named("format").default("debug").as_string();
    let print_tree = args.named("tree").as_bool();
    let dry_run = args.named("dry-run").short('n').as_bool();

    let Ok(format) = Format::from_str(format.as_str()) else {
        fatal!("Unknown format '{format}', expected 'debug', 'inhx', 'inhx32', 'raw', or 'source'. ");
    };

    // -----------------------------------------------------------------------

    let mut compiler = if let Some(path) = &source_path {
        info!("Reading program source from {path:?}");
        Compiler::from_path(path).unwrap_or_else(|err| match err {
            FileError::InvalidExtension => fatal!(
                "File {path:?} has invalid extension, must be '.{extension}'"),
            FileError::NotFound => fatal!(
                "File {path:?} was not found"),
            FileError::InvalidUtf8 => fatal!(
                "File {path:?} does not contain valid UTF-8 text"),
            FileError::NotReadable => fatal!(
                "File {path:?} is not readable"),
            FileError::IsADirectory => fatal!(
                "File {path:?} is a directory"),
            FileError::Unknown => fatal!(
                "Unknown error while attempting to read from {path:?}")
        })
    } else {
        let mut source_code = String::new();
        info!("Reading program source from standard input");
        if let Err(err) = std::io::stdin().read_to_string(&mut source_code) {
            fatal!("Could not read from standard input\n{err:?}");
        }
        Compiler::from_string(source_code, "<standard input>")
    };
    if compiler.error().is_some() && !no_libs && !no_project_libs {
        compiler.include_libs_from_parent(&extension);
    }
    if compiler.error().is_some() && !no_libs && !no_environment_libs {
        compiler.include_libs_from_path_variable("TORQUE_LIBS", &extension);
    }

    if print_tree {
        compiler.resolver.hierarchy().report()
    }

    if let Some(error) = compiler.error() {
        error.report();
        std::process::exit(1);
    }

    let merged_source = compiler.get_compiled_source().unwrap_or_else(|error| {
        error.report();
        std::process::exit(1);
    });
    if format == Format::Source && !dry_run {
        write_bytes_and_exit(merged_source.as_bytes(), destination_path.as_ref());
    }

    // -----------------------------------------------------------------------

    // Parse syntactic tokens from merged source code.
    let path = Some("<merged source>");
    let syntactic_tokens = SyntacticParser::new(&merged_source, path).parse();
    report_syntactic_errors(&syntactic_tokens, &merged_source);

    let program = SemanticParser::new(syntactic_tokens).parse();
    report_semantic_errors(&program, &merged_source);

    // program.print_definitions();
    let assembled_tokens = program.assemble();
    report_assembler_errors(&assembled_tokens, &merged_source);

    let bytecode = BytecodeGenerator::new(&assembled_tokens).generate();
    report_bytecode_errors(&bytecode, &merged_source);

    if !dry_run {
        match format {
            Format::Debug => {
                let mut output = String::new();
                for word in &bytecode.words {
                    output.push_str(&word.to_string());
                    output.push('\n');
                }
                write_bytes_and_exit(output.as_bytes(), destination_path.as_ref());
            }
            Format::Inhx => {
                let output = format_inhx(&bytecode.words);
                write_bytes_and_exit(output.as_bytes(), destination_path.as_ref());
            }
            Format::Inhx32 => {
                let output = format_inhx32(&bytecode.words);
                write_bytes_and_exit(output.as_bytes(), destination_path.as_ref());
            }
            Format::Raw => {
                let mut output = Vec::new();
                for word in &bytecode.words {
                    let value = word.value as u16;
                    output.extend(value.to_be_bytes());
                }
                write_bytes_and_exit(&output, destination_path.as_ref());
            }
            Format::Source => unreachable!(),
        }
    }
}


fn write_bytes_and_exit<P: AsRef<Path>>(bytes: &[u8], path: Option<&P>) -> ! {
    match path {
        Some(path) => match std::fs::write(path, bytes) {
            Ok(_) => info!("Wrote output to path {:?}", path.as_ref()),
            Err(err) => fatal!("Could not write to path {:?}\n{err:?}", path.as_ref()),
        }
        None => match std::io::stdout().write_all(bytes) {
            Ok(_) => info!("Wrote output to standard output"),
            Err(err) => fatal!("Could not write to standard output\n{err:?}"),
        }
    }
    std::process::exit(0);
}

#[derive(PartialEq)]
enum Format {
    Debug,
    Inhx,
    Inhx32,
    Raw,
    Source,
}

impl FromStr for Format {
    type Err = ();
    fn from_str(string: &str) -> Result<Self, ()> {
        match string {
            "debug" => Ok(Self::Debug),
            "inhx" => Ok(Self::Inhx),
            "inhx32" => Ok(Self::Inhx32),
            "raw" => Ok(Self::Raw),
            "source" => Ok(Self::Source),
            _ => Err(()),
        }
    }
}