summaryrefslogblamecommitdiff
path: root/src/main.rs
blob: e7e52d9665bc54b3710d18364c90975d8fe26a3e (plain) (tree)





















































































                                                                               
 







                                                                                  




                                                                         
                                                               
                                                     
















                                                                                       

                                                                                      

                                                                                   
                                                                             





                                                                                                        
                                                                            










                                                                                                        
                                                               

                                                                                 
                                                              

                                                                             
                                                    

                                                                             
                                                               






                                                                              














































































                                                                                                  
mod compiler;
mod environment;
mod parsers;
mod tokens;

pub use compiler::*;
pub use environment::*;
pub use parsers::*;
pub use tokens::*;

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

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


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 print_tree = args.named("tree").as_bool();
    let dry_run = args.named("dry-run").short('n').as_bool();
    let only_resolve = args.named("resolve").as_bool();
    let _export_symbols = args.named("symbols").as_bool();

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

    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 only_resolve && !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 parser = SyntacticParser::from_source_code(&merged_source, path);
    let syntactic_tokens: Vec<_> = parser.collect();
    report_syntactic_errors(&syntactic_tokens, &merged_source);

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


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);
}


fn report_syntactic_errors(syntactic_tokens: &[syntactic::Token], source_code: &str) {
    use syntactic::*;

    for token in syntactic_tokens {
        let context = Context { source_code: &source_code, source: &token.source };
        match &token.variant {
            TokenVariant::ConstantExpression(expr) => for t in &expr.tokens {
                let context = Context { source_code: &source_code, source: &t.source };
                if let ConstantExpressionTokenVariant::Error(err) = &t.variant {
                    let ConstantExpressionParseError::InvalidHexadecimalLiteral(hex) = err;
                    let message = format!("Invalid hexadecimal literal {hex:?} in constant expression");
                    report_source_issue(LogLevel::Error, &context, &message);
                }
            }
            TokenVariant::PackedBinaryLiteral(pbl) => for e in &pbl.errors {
                let context = Context { source_code: &source_code, source: &e.source };
                match e.variant {
                    PackedBinaryLiteralParseErrorVariant::DuplicateFieldName(name) => {
                        let message = format!("Duplicate field name {name:?} in packed binary literal");
                        report_source_issue(LogLevel::Error, &context, &message);
                    }
                    PackedBinaryLiteralParseErrorVariant::InvalidCharacter(c) => {
                        let message = format!("Invalid character {c:?} in packed binary literal");
                        report_source_issue(LogLevel::Error, &context, &message);
                    }
                }
            }
            TokenVariant::Error(err) => match err {
                ParseError::InvalidHexadecimalLiteral(hex) => {
                    let message = format!("Invalid hexadecimal literal {hex:?}");
                    report_source_issue(LogLevel::Error, &context, &message);
                }
                ParseError::InvalidSymbolIdentifier(name) => {
                    let message = format!("Invalid identifier {name:?}");
                    report_source_issue(LogLevel::Error, &context, &message);
                }
                ParseError::UnterminatedComment => {
                    let message = format!("Unterminated comment");
                    report_source_issue(LogLevel::Error, &context, &message);
                }
                ParseError::UnterminatedConstantExpression => {
                    let message = format!("Unterminated constant expression");
                    report_source_issue(LogLevel::Error, &context, &message);
                }
            }
            _ => (),
        }
    }
}


fn report_semantic_errors(program: &semantic::Program, source_code: &str) {
    for error in &program.errors {
        report_parse_error(error, source_code);
    }
    for definition in &program.definitions {
        report_definition_errors(&definition.variant, source_code);
    }
    for invocation in &program.invocations {
        report_invocation_errors(invocation, source_code);
    }
}

fn report_parse_error(error: &semantic::ParseError, source_code: &str) {
    use semantic::*;
    let message = match &error.variant {
        ParseErrorVariant::UnterminatedMacroDefinition(name) =>
            format!("The macro definition '{name}' is missing a terminating ';' character"),
        ParseErrorVariant::UnterminatedBlockDefinition =>
            format!("Block literal is missing a terminating '}}' character"),
        ParseErrorVariant::InvalidArgumentDefinition(name) =>
            format!("The macro definition '{name}' has an invalid argument definition"),
        ParseErrorVariant::InvalidToken =>
            format!("Invalid token"),
    };
    let context = Context { source: &error.source, source_code};
    report_source_issue(LogLevel::Error, &context, &message);
}

fn report_definition_errors(definition: &semantic::DefinitionVariant, source_code: &str) {
    use semantic::*;
    match definition {
        DefinitionVariant::Integer(integer) => match &integer.variant {
            IntegerDefinitionVariant::Constant(expr) => for token in &expr.tokens {
                if let ConstantExpressionTokenVariant::Error(error) = &token.variant {
                    let message = match error {
                        ConstantExpressionParseError::InvalidHexadecimalLiteral(hex) =>
                            format!("Invalid hexadecimal literal '{hex}' in constant expression"),
                    };
                    let context = Context { source: &token.source, source_code};
                    report_source_issue(LogLevel::Error, &context, &message);
                }
            }
            _ => (),
        }
        DefinitionVariant::Block(block) => {
            for error in &block.errors {
                report_parse_error(&error, source_code);
            }
            for token in &block.tokens {
                match &token.variant {
                    BlockTokenVariant::Word(pbl) => for error in &pbl.errors {
                        let message = match error.variant {
                            PackedBinaryLiteralParseErrorVariant::DuplicateFieldName(name) =>
                                format!("Duplicate field name '{name}' in packed binary literal"),
                            PackedBinaryLiteralParseErrorVariant::InvalidCharacter(c) =>
                                format!("Invalid character '{c}' in packed binary literal"),
                        };
                        let context = Context { source: &error.source, source_code };
                        report_source_issue(LogLevel::Error, &context, &message);
                    }
                    BlockTokenVariant::Invocation(invocation) =>
                        report_invocation_errors(invocation, source_code),
                    BlockTokenVariant::Comment(_) => (),
                }
            }
        }
        DefinitionVariant::Reference(_) => (),
    }
}

fn report_invocation_errors(invocation: &semantic::Invocation, source_code: &str) {
    for error in &invocation.errors {
        report_parse_error(&error, source_code);
    }
    for argument in &invocation.arguments {
        report_definition_errors(argument, source_code);
    }
}