use super::{Function, Link, Opcode, Program, Stack, Val};
use crate::error;
use crate::lang::ast::{self, AcceptVisitor};
use crate::lang::{Column, Error, LineNumber};
use std::convert::TryFrom;
use std::rc::Rc;
type Result<T> = std::result::Result<T, Error>;
pub fn compile(program: &mut Program, ast: &[ast::Statement]) {
Visitor::compile(program, ast)
}
struct Visitor<'a> {
link: &'a mut Program,
comp: Compiler,
}
impl<'a> Visitor<'a> {
fn compile(program: &mut Program, ast: &[ast::Statement]) {
let mut this = Visitor {
link: program,
comp: Compiler::new(),
};
for statement in ast {
statement.accept(&mut this);
}
for (_col, frag) in this.comp.stmt.drain(..) {
if let Some(error) = this.link.append(frag).err() {
this.link.error(error);
break;
}
}
debug_assert_eq!(0, this.comp.var.len());
debug_assert_eq!(0, this.comp.expr.len());
debug_assert_eq!(0, this.comp.stmt.len());
}
}
impl<'a> ast::Visitor for Visitor<'a> {
fn visit_statement(&mut self, statement: &ast::Statement) {
let mut link = Link::default();
let col = match self.comp.statement(&mut link, statement) {
Ok(col) => col,
Err(e) => {
self.link.error(e);
0..0
}
};
if let Some(error) = self.comp.stmt.push((col.clone(), link)).err() {
self.link.error(error.in_column(&col))
}
}
fn visit_variable(&mut self, var: &ast::Variable) {
let mut link = Link::default();
let (col, name, len) = match self.comp.variable(&mut link, var) {
Ok((col, name, len)) => (col, name, len),
Err(e) => {
self.link.error(e);
(0..0, "".into(), 0)
}
};
let var_item = VarItem::new(col.clone(), name, link, len);
if let Some(error) = self.comp.var.push(var_item).err() {
self.link.error(error.in_column(&col))
}
}
fn visit_expression(&mut self, expression: &ast::Expression) {
let mut link = Link::default();
let col = match self.comp.expression(&mut link, expression) {
Ok(col) => col,
Err(e) => {
self.link.error(e);
0..0
}
};
if let Some(error) = self.comp.expr.push((col.clone(), link)).err() {
self.link.error(error.in_column(&col))
}
}
}
struct VarItem {
col: Column,
name: Rc<str>,
link: Link,
len: usize,
}
impl VarItem {
fn new(col: Column, name: Rc<str>, link: Link, len: usize) -> VarItem {
VarItem {
col,
name,
link,
len,
}
}
fn push_as_dim(self, link: &mut Link) -> Result<Column> {
if self.len == 0 {
Err(error!(SyntaxError, ..&self.col; "NOT AN ARRAY"))
} else {
link.append(self.link)?;
link.push(Opcode::Literal(Val::try_from(self.len)?))?;
link.push(Opcode::DimArr(self.name))?;
Ok(self.col.clone())
}
}
fn push_as_pop_unary(self, link: &mut Link) -> Result<Column> {
debug_assert!(self.len == 0);
debug_assert!(self.link.is_empty());
link.push(Opcode::Pop(self.name))?;
Ok(self.col.clone())
}
fn push_as_pop(self, link: &mut Link) -> Result<Column> {
if self.len == 0 {
debug_assert!(self.link.is_empty());
link.push(Opcode::Pop(self.name))?
} else {
link.append(self.link)?;
link.push(Opcode::Literal(Val::try_from(self.len)?))?;
link.push(Opcode::PopArr(self.name))?
}
Ok(self.col.clone())
}
fn push_as_expression(self, link: &mut Link) -> Result<Column> {
link.append(self.link)?;
if let Some((opcode, arity)) = Function::opcode_and_arity(&self.name) {
if arity.contains(&self.len) {
if arity.start() != arity.end() {
link.push(Opcode::Literal(Val::try_from(self.len)?))?;
}
link.push(opcode)?;
return Ok(self.col.clone());
}
return Err(error!(IllegalFunctionCall, ..&self.col; "WRONG NUMBER OF ARGUMENTS"));
}
if self.len == 0 {
link.push(Opcode::Push(self.name))?;
} else if self.name.starts_with("FN") {
link.push(Opcode::Literal(Val::try_from(self.len)?))?;
link.push(Opcode::Fn(self.name))?;
} else {
link.push(Opcode::Literal(Val::try_from(self.len)?))?;
link.push(Opcode::PushArr(self.name))?;
}
Ok(self.col.clone())
}
}
struct Compiler {
var: Stack<VarItem>,
expr: Stack<(Column, Link)>,
stmt: Stack<(Column, Link)>,
}
impl Compiler {
fn new() -> Compiler {
Compiler {
var: Stack::new("COMPILER VARIABLE OVERFLOW"),
expr: Stack::new("COMPILER EXPRESSION OVERFLOW"),
stmt: Stack::new("COMPILER STATEMENT OVERFLOW"),
}
}
fn variable(
&mut self,
link: &mut Link,
var: &ast::Variable,
) -> Result<(Column, Rc<str>, usize)> {
use ast::Variable;
let (col, ident, len) = match var {
Variable::Unary(col, ident) => (col, ident, 0),
Variable::Array(col, ident, vec_expr) => {
let len = vec_expr.len();
let vec_expr = self.expr.pop_n(len)?;
for (_col, ops) in vec_expr {
link.append(ops)?
}
(col, ident, len)
}
};
let s = match ident {
ast::Ident::Plain(s) => s,
ast::Ident::String(s) => s,
ast::Ident::Single(s) => s,
ast::Ident::Double(s) => s,
ast::Ident::Integer(s) => s,
};
Ok((col.clone(), s.clone(), len))
}
fn expression(&mut self, link: &mut Link, expr: &ast::Expression) -> Result<Column> {
fn unary_expression(
this: &mut Compiler,
link: &mut Link,
op: Opcode,
col: &Column,
) -> Result<Column> {
let (expr_col, ops) = this.expr.pop()?;
link.append(ops)?;
link.push(op)?;
Ok(col.start..expr_col.end)
}
fn binary_expression(this: &mut Compiler, link: &mut Link, op: Opcode) -> Result<Column> {
let (col_rhs, rhs) = this.expr.pop()?;
let (col_lhs, lhs) = this.expr.pop()?;
link.append(lhs)?;
link.append(rhs)?;
link.push(op)?;
Ok(col_lhs.start..col_rhs.end)
}
fn literal(link: &mut Link, col: &Column, val: Val) -> Result<Column> {
link.push(Opcode::Literal(val))?;
Ok(col.clone())
}
use ast::Expression;
match expr {
Expression::Single(col, val) => literal(link, col, Val::Single(*val)),
Expression::Double(col, val) => literal(link, col, Val::Double(*val)),
Expression::Integer(col, val) => literal(link, col, Val::Integer(*val)),
Expression::String(col, val) => literal(link, col, Val::String(val.clone())),
Expression::Variable(_) => self.var.pop()?.push_as_expression(link),
Expression::Negation(col, ..) => unary_expression(self, link, Opcode::Neg, col),
Expression::Power(..) => binary_expression(self, link, Opcode::Pow),
Expression::Multiply(..) => binary_expression(self, link, Opcode::Mul),
Expression::Divide(..) => binary_expression(self, link, Opcode::Div),
Expression::DivideInt(..) => binary_expression(self, link, Opcode::DivInt),
Expression::Modulus(..) => binary_expression(self, link, Opcode::Mod),
Expression::Add(..) => binary_expression(self, link, Opcode::Add),
Expression::Subtract(..) => binary_expression(self, link, Opcode::Sub),
Expression::Equal(..) => binary_expression(self, link, Opcode::Eq),
Expression::NotEqual(..) => binary_expression(self, link, Opcode::NotEq),
Expression::Less(..) => binary_expression(self, link, Opcode::Lt),
Expression::LessEqual(..) => binary_expression(self, link, Opcode::LtEq),
Expression::Greater(..) => binary_expression(self, link, Opcode::Gt),
Expression::GreaterEqual(..) => binary_expression(self, link, Opcode::GtEq),
Expression::Not(col, ..) => unary_expression(self, link, Opcode::Not, col),
Expression::And(..) => binary_expression(self, link, Opcode::And),
Expression::Or(..) => binary_expression(self, link, Opcode::Or),
Expression::Xor(..) => binary_expression(self, link, Opcode::Xor),
Expression::Imp(..) => binary_expression(self, link, Opcode::Imp),
Expression::Eqv(..) => binary_expression(self, link, Opcode::Eqv),
}
}
fn statement(&mut self, link: &mut Link, statement: &ast::Statement) -> Result<Column> {
use ast::Statement;
match statement {
Statement::Clear(col, ..) => self.r#clear(link, col),
Statement::Cont(col, ..) => self.r#cont(link, col),
Statement::Data(col, v) => self.r#data(link, col, v.len()),
Statement::Def(col, _, v, _) => self.r#def(link, col, v.len()),
Statement::Dim(col, ..) => self.r#dim(link, col),
Statement::End(col, ..) => self.r#end(link, col),
Statement::For(col, ..) => self.r#for(link, col),
Statement::Gosub(col, ..) => self.r#gosub(link, col),
Statement::Goto(col, ..) => self.r#goto(link, col),
Statement::If(col, _, th, el) => self.r#if(link, col, th.len(), el.len()),
Statement::Input(col, _, _, v) => self.r#input(link, col, v.len()),
Statement::Let(col, ..) => self.r#let(link, col),
Statement::List(col, ..) => self.r#list(link, col),
Statement::Load(col, ..) => self.r#load(link, col),
Statement::New(col, ..) => self.r#new_(link, col),
Statement::Next(col, ..) => self.r#next(link, col),
Statement::OnGoto(col, _, v) => self.r#on(link, col, v.len(), false),
Statement::OnGosub(col, _, v) => self.r#on(link, col, v.len(), true),
Statement::Print(col, ..) => self.r#print(link, col),
Statement::Read(col, v) => self.r#read(link, col, v.len()),
Statement::Restore(col, ..) => self.r#restore(link, col),
Statement::Return(col, ..) => self.r#return(link, col),
Statement::Run(col, ..) => self.r#run(link, col),
Statement::Save(col, ..) => self.r#save(link, col),
Statement::Stop(col, ..) => self.r#stop(link, col),
}
}
fn expr_pop_line_number(&mut self) -> Result<(Column, LineNumber)> {
let (sub_col, ops) = self.expr.pop()?;
match LineNumber::try_from(ops) {
Ok(ln) => Ok((sub_col, ln)),
Err(e) => Err(e.in_column(&sub_col)),
}
}
fn r#clear(&mut self, link: &mut Link, col: &Column) -> Result<Column> {
link.push(Opcode::Clear)?;
Ok(col.clone())
}
fn r#cont(&mut self, link: &mut Link, col: &Column) -> Result<Column> {
link.push(Opcode::Cont)?;
Ok(col.clone())
}
fn r#data(&mut self, link: &mut Link, col: &Column, len: usize) -> Result<Column> {
let exprs = self.expr.pop_n(len)?;
for (expr_col, mut expr_link) in exprs {
expr_link.transform_to_data(&expr_col)?;
link.append(expr_link)?;
}
Ok(col.clone())
}
fn r#def(&mut self, link: &mut Link, col: &Column, len: usize) -> Result<Column> {
let mut vars = self.var.pop_n(len)?;
let fn_name = self.var.pop()?;
debug_assert_eq!(0, fn_name.len);
let (_expr_col, expr_ops) = self.expr.pop()?;
let fn_vars: Vec<Rc<str>> = vars
.drain(..)
.map(|var_item| {
debug_assert_eq!(0, var_item.len);
var_item.name
})
.collect();
link.push_def_fn(col.clone(), fn_name.name, fn_vars, expr_ops)?;
Ok(col.clone())
}
fn r#dim(&mut self, link: &mut Link, col: &Column) -> Result<Column> {
let var = self.var.pop()?;
let sub_col = var.push_as_dim(link)?;
Ok(col.start..sub_col.end)
}
fn r#end(&mut self, link: &mut Link, col: &Column) -> Result<Column> {
link.push(Opcode::End)?;
Ok(col.clone())
}
fn r#for(&mut self, link: &mut Link, col: &Column) -> Result<Column> {
let (step_col, step_ops) = self.expr.pop()?;
let (_to_col, to_ops) = self.expr.pop()?;
let (_from_col, from_ops) = self.expr.pop()?;
let var = self.var.pop()?;
let var_name = var.name.clone();
link.append(from_ops)?;
var.push_as_pop_unary(link)?;
link.append(to_ops)?;
link.append(step_ops)?;
link.push(Opcode::Literal(Val::String(var_name)))?;
link.push_for(col.start..step_col.end)?;
Ok(col.start..step_col.end)
}
fn r#gosub(&mut self, link: &mut Link, col: &Column) -> Result<Column> {
let (sub_col, line_number) = self.expr_pop_line_number()?;
let full_col = col.start..sub_col.end;
link.push_gosub(sub_col, line_number)?;
Ok(full_col)
}
fn r#goto(&mut self, link: &mut Link, col: &Column) -> Result<Column> {
let (sub_col, line_number) = self.expr_pop_line_number()?;
let full_col = col.start..sub_col.end;
link.push_goto(sub_col, line_number)?;
Ok(full_col)
}
fn r#if(
&mut self,
link: &mut Link,
col: &Column,
then_len: usize,
else_len: usize,
) -> Result<Column> {
let (_predicate_col, predicate) = self.expr.pop()?;
link.append(predicate)?;
let else_sym = link.next_symbol();
link.push_ifnot(col.clone(), else_sym)?;
let elses = self.stmt.pop_n(else_len)?;
for (_col, stmt_ops) in self.stmt.pop_n(then_len)? {
link.append(stmt_ops)?;
}
if else_len == 0 {
link.push_symbol(else_sym);
} else {
let finished_sym = link.next_symbol();
link.push_jump(col.clone(), finished_sym)?;
link.push_symbol(else_sym);
for (_col, stmt_ops) in elses {
link.append(stmt_ops)?;
}
link.push_symbol(finished_sym);
}
Ok(col.clone())
}
fn r#input(&mut self, link: &mut Link, col: &Column, len: usize) -> Result<Column> {
let (_prompt_col, prompt) = self.expr.pop()?;
let (_caps_col, caps) = self.expr.pop()?;
link.append(prompt)?;
link.append(caps)?;
link.push(Opcode::Literal(Val::try_from(len)?))?;
for var in self.var.pop_n(len)? {
link.push(Opcode::Input(var.name.clone()))?;
var.push_as_pop(link)?;
}
link.push(Opcode::Input("".into()))?;
Ok(col.clone())
}
fn r#let(&mut self, link: &mut Link, col: &Column) -> Result<Column> {
let (expr_col, expr_ops) = self.expr.pop()?;
link.append(expr_ops)?;
self.var.pop()?.push_as_pop(link)?;
Ok(col.start..expr_col.end)
}
fn r#list(&mut self, link: &mut Link, col: &Column) -> Result<Column> {
let (col_to, ln_to) = self.expr_pop_line_number()?;
let (_col_from, ln_from) = self.expr_pop_line_number()?;
link.push(Opcode::Literal(Val::try_from(ln_from)?))?;
link.push(Opcode::Literal(Val::try_from(ln_to)?))?;
link.push(Opcode::List)?;
Ok(col.start..col_to.end)
}
fn r#load(&mut self, link: &mut Link, col: &Column) -> Result<Column> {
let (sub_col, expr) = self.expr.pop()?;
link.append(expr)?;
link.push(Opcode::Load)?;
Ok(col.start..sub_col.end)
}
fn r#new_(&mut self, link: &mut Link, col: &Column) -> Result<Column> {
link.push(Opcode::New)?;
Ok(col.clone())
}
fn r#next(&mut self, link: &mut Link, col: &Column) -> Result<Column> {
link.push(Opcode::Next(self.var.pop()?.name))?;
Ok(col.clone())
}
fn r#on(
&mut self,
link: &mut Link,
col: &Column,
len: usize,
is_gosub: bool,
) -> Result<Column> {
let line_numbers = self.expr.pop_n(len)?;
let len = Val::try_from(len)?;
let (mut sub_col, var_ops) = self.expr.pop()?;
let ret_symbol = link.next_symbol();
if is_gosub {
link.push_return_val(col.clone(), ret_symbol)?;
}
link.push(Opcode::Literal(len))?;
link.append(var_ops)?;
link.push(Opcode::On)?;
for (column, ops) in line_numbers {
sub_col.end = column.end;
let ln = match LineNumber::try_from(ops) {
Ok(ln) => ln,
Err(e) => return Err(e.in_column(&column)),
};
link.push_goto(column, ln)?;
}
if is_gosub {
link.push_symbol(ret_symbol);
}
Ok(col.start..sub_col.end)
}
fn r#print(&mut self, link: &mut Link, col: &Column) -> Result<Column> {
let (_expr_col, expr) = self.expr.pop()?;
link.append(expr)?;
link.push(Opcode::Print)?;
Ok(col.clone())
}
fn r#read(&mut self, link: &mut Link, col: &Column, len: usize) -> Result<Column> {
for var in self.var.pop_n(len)? {
link.push(Opcode::Read)?;
var.push_as_pop(link)?;
}
Ok(col.clone())
}
fn r#restore(&mut self, link: &mut Link, col: &Column) -> Result<Column> {
let mut line_number: LineNumber = None;
let (sub_col, ops) = self.expr.pop()?;
if let Ok(ln) = LineNumber::try_from(ops) {
line_number = ln;
}
link.push_restore(sub_col, line_number)?;
Ok(col.clone())
}
fn r#return(&mut self, link: &mut Link, col: &Column) -> Result<Column> {
link.push(Opcode::Return)?;
Ok(col.clone())
}
fn r#run(&mut self, link: &mut Link, col: &Column) -> Result<Column> {
let mut line_number: LineNumber = None;
let (sub_col, ops) = self.expr.pop()?;
if let Ok(ln) = LineNumber::try_from(ops) {
line_number = ln;
}
let full_col = col.start..sub_col.end;
link.push_run(sub_col, line_number)?;
Ok(full_col)
}
fn r#save(&mut self, link: &mut Link, col: &Column) -> Result<Column> {
let (sub_col, expr) = self.expr.pop()?;
link.append(expr)?;
link.push(Opcode::Save)?;
Ok(col.start..sub_col.end)
}
fn r#stop(&mut self, link: &mut Link, col: &Column) -> Result<Column> {
link.push(Opcode::Stop)?;
Ok(col.clone())
}
}