use crate::ast::{ExprType, VarRef};
use crate::bytecode::{InvalidExitCodeError, RegisterScope};
use crate::callable::CallableMetadata;
use crate::image::Image;
use crate::reader::LineCol;
use crate::{Callable, parser};
use std::collections::HashMap;
use std::io;
use std::rc::Rc;
mod args;
mod codegen;
mod exprs;
mod ids;
mod syms;
pub use syms::SymbolKey;
use syms::{GlobalSymtable, LocalSymtable, LocalSymtableSnapshot};
mod top;
use top::{Context, prepare_globals};
pub use top::{GlobalDef, GlobalDefKind, only_metadata};
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("{0}: Cannot redefine {1}")]
AlreadyDefined(LineCol, VarRef),
#[error("{0}: {1} is an array and requires subscripts")]
ArrayUsedAsScalar(LineCol, VarRef),
#[error("{0}: Cannot {1} {2} and {3}")]
BinaryOpType(LineCol, &'static str, ExprType, ExprType),
#[error("{0}: {} expected {}", .1.name(), .1.syntax())]
CallableSyntax(LineCol, CallableMetadata),
#[error("{0}: Cannot nest FUNCTION or SUB declarations nor definitions")]
CannotNestUserCallables(LineCol),
#[error("{0}: Duplicate label {1}")]
DuplicateLabel(LineCol, String),
#[error("{0}: Incompatible type annotation in {1} reference")]
IncompatibleTypeAnnotationInReference(LineCol, VarRef),
#[error("{0}: Cannot assign value of type {1} to variable of type {2}")]
IncompatibleTypesInAssignment(LineCol, ExprType, ExprType),
#[error("{0}: {1}")]
InvalidEndCode(LineCol, String),
#[error("{0}: I/O error during compilation: {1}")]
Io(LineCol, io::Error),
#[error("{0}: {1} is not an array nor a function")]
NotAFunction(LineCol, VarRef),
#[error("{0}: EXIT {1} outside of {1}")]
MisplacedExit(LineCol, &'static str),
#[error("{0}: {1} is not an array")]
NotAnArray(LineCol, VarRef),
#[error("{0}: {1} is not a number")]
NotANumber(LineCol, ExprType),
#[error("{0}: Out of constants")]
OutOfConstants(LineCol),
#[error("{0}: Out of {1} registers")]
OutOfRegisters(LineCol, RegisterScope),
#[error("{0}: Out of upcalls")]
OutOfUpcalls(LineCol),
#[error("{0}: {1}")]
Parse(LineCol, String),
#[error("{0}: Jump/call target is {1} which is too far")]
TargetTooFar(LineCol, usize),
#[error("{0}: Array cannot have {1} dimensions")]
TooManyArrayDimensions(LineCol, usize),
#[error("{0}: Expected {2} but found {1}")]
TypeMismatch(LineCol, ExprType, ExprType),
#[error("{0}: Undefined symbol {1}")]
UndefinedSymbol(LineCol, VarRef),
#[error("{0}: Unknown label {1}")]
UnknownLabel(LineCol, String),
#[error("{0}: Array requires {1} subscripts but got {2}")]
WrongNumberOfSubscripts(LineCol, usize, usize),
}
impl Error {
fn split_display_message(&self) -> (LineCol, String) {
let display = self.to_string();
let mut parts = display.splitn(3, ':');
let line = parts
.next()
.expect("CompilerError display always has line")
.parse()
.expect("CompilerError line is always numeric");
let col = parts
.next()
.expect("CompilerError display always has column")
.parse()
.expect("CompilerError column is always numeric");
let message =
parts.next().expect("CompilerError display always has message").trim_start().to_owned();
(LineCol { line, col }, message)
}
pub fn pos(&self) -> LineCol {
self.split_display_message().0
}
pub fn message_without_pos(&self) -> String {
self.split_display_message().1
}
fn from_bytecode_invalid_exit_code(value: InvalidExitCodeError, pos: LineCol) -> Self {
Self::InvalidEndCode(pos, value.to_string())
}
fn from_syms(value: syms::Error, pos: LineCol) -> Self {
match value {
syms::Error::AlreadyDefined(vref) => Error::AlreadyDefined(pos, vref),
syms::Error::IncompatibleTypeAnnotationInReference(vref) => {
Error::IncompatibleTypeAnnotationInReference(pos, vref)
}
syms::Error::OutOfRegisters(scope) => Error::OutOfRegisters(pos, scope),
syms::Error::UndefinedSymbol(vref, _scope) => Error::UndefinedSymbol(pos, vref),
}
}
}
impl From<parser::Error> for Error {
fn from(value: parser::Error) -> Self {
match value {
parser::Error::Bad(pos, message) => Self::Parse(pos, message),
parser::Error::Io(pos, e) => Self::Io(pos, e),
}
}
}
pub type Result<T> = std::result::Result<T, Error>;
pub struct Compiler {
context: Context,
symtable: GlobalSymtable,
program_scope: LocalSymtableSnapshot,
}
impl Compiler {
pub fn new(
upcalls: &HashMap<SymbolKey, Rc<dyn Callable>>,
global_defs: &[GlobalDef],
) -> Result<Self> {
let mut upcalls_metadata = HashMap::with_capacity(upcalls.len());
for (k, v) in upcalls.iter() {
upcalls_metadata.insert(k.clone(), v.metadata());
}
let mut context = Context::default();
let mut symtable = GlobalSymtable::new(upcalls_metadata);
prepare_globals(&mut context, &mut symtable, global_defs)?;
Ok(Self { context, symtable, program_scope: LocalSymtableSnapshot::default() })
}
pub fn compile(mut self, input: &mut dyn io::Read) -> Result<Image> {
let mut image = Image::default();
self.compile_more(&mut image, input)?;
Ok(image)
}
pub fn compile_more(&mut self, image: &mut Image, input: &mut dyn io::Read) -> Result<()> {
let mut new_context = self.context.clone();
let mut new_symtable = self.symtable.clone();
let program_scope = LocalSymtable::restore(&mut new_symtable, self.program_scope.clone());
let (delta, snapshot) = top::compile(input, image, &mut new_context, program_scope)?;
image.append(delta);
self.context = new_context;
self.symtable = new_symtable;
self.program_scope = snapshot;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_pos_and_message_without_pos() {
let err = Error::Parse(LineCol { line: 3, col: 15 }, "Invalid token".to_owned());
assert_eq!(LineCol { line: 3, col: 15 }, err.pos());
assert_eq!("Invalid token", err.message_without_pos());
}
#[test]
fn test_error_message_without_pos_preserves_colons() {
let err =
Error::Parse(LineCol { line: 4, col: 9 }, "Expected INTEGER: got STRING".to_owned());
assert_eq!(LineCol { line: 4, col: 9 }, err.pos());
assert_eq!("Expected INTEGER: got STRING", err.message_without_pos());
}
}