separate interpreter and compiler
This commit is contained in:
12
crates/compiler/Cargo.toml
Normal file
12
crates/compiler/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "compiler"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
byteorder = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
integer-encoding = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
128
crates/compiler/src/ast/mod.rs
Normal file
128
crates/compiler/src/ast/mod.rs
Normal file
@@ -0,0 +1,128 @@
|
||||
pub mod optimization;
|
||||
pub mod typed;
|
||||
pub mod untyped;
|
||||
|
||||
use core::fmt;
|
||||
use std::ops::{Add, Div, Mul, Sub};
|
||||
|
||||
pub use untyped::UntypedExpr;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Span {
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
}
|
||||
|
||||
impl Span {
|
||||
pub fn new(start: usize, end: usize) -> Self {
|
||||
Self { start, end }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum BinOp {
|
||||
Add,
|
||||
Sub,
|
||||
Mul,
|
||||
Div,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum OpCommutative {
|
||||
Yes(BinOp),
|
||||
No,
|
||||
}
|
||||
|
||||
impl BinOp {
|
||||
pub fn precedence(&self) -> u8 {
|
||||
match self {
|
||||
BinOp::Add => 1,
|
||||
BinOp::Sub => 1,
|
||||
BinOp::Mul => 2,
|
||||
BinOp::Div => 2,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn commutative(&self, other: &Self) -> bool {
|
||||
matches!(self.commutative_expr(other), OpCommutative::Yes(_))
|
||||
}
|
||||
|
||||
pub fn swappable(&self, other: &Self) -> bool {
|
||||
if let OpCommutative::Yes(new_op) = self.commutative_expr(other) {
|
||||
other == self && new_op == *other
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn commutative_expr(&self, other: &Self) -> OpCommutative {
|
||||
match (self, other) {
|
||||
(BinOp::Add, BinOp::Add) => OpCommutative::Yes(BinOp::Add),
|
||||
(BinOp::Add, BinOp::Sub) => OpCommutative::Yes(BinOp::Sub),
|
||||
(BinOp::Sub, BinOp::Add) => OpCommutative::Yes(BinOp::Sub),
|
||||
(BinOp::Sub, BinOp::Sub) => OpCommutative::Yes(BinOp::Add),
|
||||
(BinOp::Mul, BinOp::Mul) => OpCommutative::Yes(BinOp::Mul),
|
||||
(BinOp::Mul, BinOp::Div) => OpCommutative::Yes(BinOp::Div),
|
||||
(BinOp::Div, BinOp::Mul) => OpCommutative::Yes(BinOp::Div),
|
||||
(BinOp::Div, BinOp::Div) => OpCommutative::Yes(BinOp::Mul),
|
||||
_ => OpCommutative::No,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn evaluate<T>(&self, lhs: T, rhs: T) -> T
|
||||
where
|
||||
T: Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Div<Output = T>,
|
||||
{
|
||||
match self {
|
||||
BinOp::Add => lhs + rhs,
|
||||
BinOp::Sub => lhs - rhs,
|
||||
BinOp::Mul => lhs * rhs,
|
||||
BinOp::Div => lhs / rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&BinOp> for u8 {
|
||||
fn from(value: &BinOp) -> Self {
|
||||
match value {
|
||||
BinOp::Add => 1,
|
||||
BinOp::Sub => 2,
|
||||
BinOp::Mul => 3,
|
||||
BinOp::Div => 4,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for BinOp {
|
||||
type Error = u8;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
1 => Ok(BinOp::Add),
|
||||
2 => Ok(BinOp::Sub),
|
||||
3 => Ok(BinOp::Mul),
|
||||
4 => Ok(BinOp::Div),
|
||||
b => Err(b),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for BinOp {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if f.alternate() {
|
||||
match self {
|
||||
BinOp::Add => f.write_str("add"),
|
||||
BinOp::Sub => f.write_str("sub"),
|
||||
BinOp::Mul => f.write_str("mul"),
|
||||
BinOp::Div => f.write_str("div"),
|
||||
}
|
||||
} else {
|
||||
match self {
|
||||
BinOp::Add => f.write_str("+"),
|
||||
BinOp::Sub => f.write_str("-"),
|
||||
BinOp::Mul => f.write_str("*"),
|
||||
BinOp::Div => f.write_str("/"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
241
crates/compiler/src/ast/optimization.rs
Normal file
241
crates/compiler/src/ast/optimization.rs
Normal file
@@ -0,0 +1,241 @@
|
||||
use std::fmt;
|
||||
|
||||
use crate::ast::typed::{Type, TypedExpr};
|
||||
use crate::ast::{BinOp, OpCommutative};
|
||||
use crate::symbols::SymbolsTable;
|
||||
|
||||
#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, clap::ValueEnum)]
|
||||
pub enum OLevel {
|
||||
None = 0,
|
||||
O1 = 1,
|
||||
O2 = 2,
|
||||
O3 = 3,
|
||||
}
|
||||
|
||||
pub fn optimize_expr(expr: TypedExpr) -> TypedExpr {
|
||||
match expr {
|
||||
TypedExpr::BinOp { lhs, op, rhs } => optimize_binop(*lhs, op, *rhs),
|
||||
TypedExpr::IntToFloat { value } => optimize_int_to_float(*value),
|
||||
expr => expr,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bubble_binop_vars(expr: TypedExpr) -> TypedExpr {
|
||||
if expr.is_const() {
|
||||
return expr;
|
||||
}
|
||||
|
||||
let expr = reorder_commutative_expr(expr);
|
||||
|
||||
let expr = match expr {
|
||||
TypedExpr::BinOp { lhs, op, rhs } => {
|
||||
let lhs = *lhs;
|
||||
let rhs = *rhs;
|
||||
|
||||
let lhs = bubble_binop_vars(lhs);
|
||||
let rhs = bubble_binop_vars(rhs);
|
||||
|
||||
let (lhs, rhs, op, rebubble) = match (lhs, rhs) {
|
||||
(
|
||||
TypedExpr::BinOp {
|
||||
lhs: lhs1,
|
||||
op: op1,
|
||||
rhs: rhs1,
|
||||
},
|
||||
rhs,
|
||||
) if rhs.is_const() && op.commutative(&op1) => (
|
||||
TypedExpr::BinOp {
|
||||
lhs: lhs1,
|
||||
op,
|
||||
rhs: Box::new(rhs),
|
||||
},
|
||||
*rhs1,
|
||||
op1,
|
||||
true,
|
||||
),
|
||||
(lhs, rhs) if !lhs.is_const() && rhs.is_const() && op.swappable(&op) => {
|
||||
(rhs, lhs, op, false)
|
||||
},
|
||||
(lhs, rhs) if op.precedence() < lhs.precedence() && op.swappable(&op) => {
|
||||
(rhs, lhs, op, true)
|
||||
},
|
||||
(lhs, rhs) => (lhs, rhs, op, false),
|
||||
};
|
||||
|
||||
let lhs = if rebubble {
|
||||
bubble_binop_vars(lhs)
|
||||
} else {
|
||||
lhs
|
||||
};
|
||||
|
||||
TypedExpr::BinOp {
|
||||
lhs: Box::new(lhs),
|
||||
op,
|
||||
rhs: Box::new(rhs),
|
||||
}
|
||||
},
|
||||
TypedExpr::IntToFloat { value } => TypedExpr::IntToFloat {
|
||||
value: Box::new(bubble_binop_vars(*value)),
|
||||
},
|
||||
expr => expr,
|
||||
};
|
||||
|
||||
expr
|
||||
}
|
||||
|
||||
pub fn propagate_type_conversions(
|
||||
expr: TypedExpr,
|
||||
root_ty: Type,
|
||||
symbols: &SymbolsTable,
|
||||
) -> TypedExpr {
|
||||
match expr {
|
||||
TypedExpr::Int { value } if root_ty == Type::Float => TypedExpr::IntToFloat {
|
||||
value: Box::new(TypedExpr::Int { value }),
|
||||
},
|
||||
TypedExpr::Var { name }
|
||||
if root_ty == Type::Float && symbols.resolve(&name).unwrap().ty == Some(Type::Int) =>
|
||||
{
|
||||
TypedExpr::IntToFloat {
|
||||
value: Box::new(TypedExpr::Var { name }),
|
||||
}
|
||||
},
|
||||
TypedExpr::IntToFloat { value } if root_ty == Type::Float => {
|
||||
propagate_type_conversions(*value, Type::Float, symbols)
|
||||
},
|
||||
TypedExpr::BinOp { lhs, rhs, op } => TypedExpr::BinOp {
|
||||
lhs: Box::new(propagate_type_conversions(*lhs, root_ty, symbols)),
|
||||
rhs: Box::new(propagate_type_conversions(*rhs, root_ty, symbols)),
|
||||
op,
|
||||
},
|
||||
expr => expr,
|
||||
}
|
||||
}
|
||||
|
||||
fn optimize_binop(lhs: TypedExpr, op: BinOp, rhs: TypedExpr) -> TypedExpr {
|
||||
let lhs = optimize_expr(lhs);
|
||||
let rhs = optimize_expr(rhs);
|
||||
|
||||
match (lhs, rhs) {
|
||||
(TypedExpr::Int { value: lhs, .. }, TypedExpr::Int { value: rhs, .. }) => TypedExpr::Int {
|
||||
value: op.evaluate(lhs, rhs),
|
||||
},
|
||||
(TypedExpr::Float { value: lhs, .. }, TypedExpr::Float { value: rhs, .. }) => {
|
||||
TypedExpr::Float {
|
||||
value: op.evaluate(lhs, rhs),
|
||||
}
|
||||
},
|
||||
(lhs, rhs) => optimize_special_cases(lhs, op, rhs),
|
||||
}
|
||||
}
|
||||
|
||||
fn optimize_special_cases(lhs: TypedExpr, op: BinOp, rhs: TypedExpr) -> TypedExpr {
|
||||
match (lhs, rhs) {
|
||||
// Addition of zero
|
||||
(lhs, TypedExpr::Int { value: 0, .. }) | (TypedExpr::Int { value: 0, .. }, lhs)
|
||||
if matches!(op, BinOp::Add) =>
|
||||
{
|
||||
lhs
|
||||
},
|
||||
(lhs, TypedExpr::Float { value: 0.0, .. }) | (TypedExpr::Float { value: 0.0, .. }, lhs)
|
||||
if matches!(op, BinOp::Add) =>
|
||||
{
|
||||
lhs
|
||||
},
|
||||
|
||||
// Subtraction by zero
|
||||
(lhs, TypedExpr::Int { value: 0, .. }) if matches!(op, BinOp::Sub) => lhs,
|
||||
(lhs, TypedExpr::Float { value: 0.0, .. }) if matches!(op, BinOp::Sub) => lhs,
|
||||
|
||||
// Multiplication/Division by one
|
||||
(lhs, TypedExpr::Int { value: 1, .. }) if matches!(op, BinOp::Mul | BinOp::Div) => lhs,
|
||||
(lhs, TypedExpr::Float { value: 1.0, .. }) if matches!(op, BinOp::Mul | BinOp::Div) => lhs,
|
||||
(TypedExpr::Int { value: 1, .. }, rhs) if matches!(op, BinOp::Mul) => rhs,
|
||||
(TypedExpr::Float { value: 1.0, .. }, rhs) if matches!(op, BinOp::Mul) => rhs,
|
||||
|
||||
// Multiplication by zero
|
||||
(_, TypedExpr::Int { value: 0, .. }) | (TypedExpr::Int { value: 0, .. }, _)
|
||||
if matches!(op, BinOp::Mul) =>
|
||||
{
|
||||
TypedExpr::Int { value: 0 }
|
||||
},
|
||||
(_, TypedExpr::Float { value: 0.0, .. }) | (TypedExpr::Float { value: 0.0, .. }, _)
|
||||
if matches!(op, BinOp::Mul) =>
|
||||
{
|
||||
TypedExpr::Float { value: 0.0 }
|
||||
},
|
||||
|
||||
// Zero division
|
||||
(TypedExpr::Int { value: 0, .. }, _) if matches!(op, BinOp::Div) => {
|
||||
TypedExpr::Int { value: 0 }
|
||||
},
|
||||
(TypedExpr::Float { value: 0.0, .. }, _) if matches!(op, BinOp::Div) => {
|
||||
TypedExpr::Float { value: 0.0 }
|
||||
},
|
||||
|
||||
// Default
|
||||
(lhs, rhs) => TypedExpr::BinOp {
|
||||
lhs: Box::new(lhs),
|
||||
op,
|
||||
rhs: Box::new(rhs),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn optimize_int_to_float(value: TypedExpr) -> TypedExpr {
|
||||
let value = optimize_expr(value);
|
||||
if let TypedExpr::Int { value } = value {
|
||||
TypedExpr::Float {
|
||||
value: value as f64,
|
||||
}
|
||||
} else {
|
||||
TypedExpr::IntToFloat {
|
||||
value: Box::new(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn reorder_commutative_expr(expr: TypedExpr) -> TypedExpr {
|
||||
match expr {
|
||||
TypedExpr::BinOp { lhs, op, rhs, .. } => {
|
||||
let commutative = rhs
|
||||
.bin_op()
|
||||
.map_or(OpCommutative::No, |op1| op.commutative_expr(&op1));
|
||||
|
||||
if let OpCommutative::Yes(op1) = commutative {
|
||||
let (lhs, rhs) = match *rhs {
|
||||
TypedExpr::BinOp {
|
||||
lhs: lhs1,
|
||||
rhs: rhs1,
|
||||
..
|
||||
} => (
|
||||
TypedExpr::BinOp { lhs, op, rhs: lhs1 },
|
||||
reorder_commutative_expr(*rhs1),
|
||||
),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
TypedExpr::BinOp {
|
||||
lhs: Box::new(lhs),
|
||||
op: op1,
|
||||
rhs: Box::new(rhs),
|
||||
}
|
||||
} else {
|
||||
TypedExpr::BinOp { lhs, op, rhs }
|
||||
}
|
||||
},
|
||||
expr => expr,
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for OLevel {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let str = match self {
|
||||
OLevel::None => "none",
|
||||
OLevel::O1 => "o1",
|
||||
OLevel::O2 => "o2",
|
||||
OLevel::O3 => "o3",
|
||||
};
|
||||
|
||||
write!(f, "{}", str)
|
||||
}
|
||||
}
|
||||
279
crates/compiler/src/ast/typed.rs
Normal file
279
crates/compiler/src/ast/typed.rs
Normal file
@@ -0,0 +1,279 @@
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use super::{BinOp, UntypedExpr, optimization};
|
||||
use crate::ast::optimization::OLevel;
|
||||
use crate::error;
|
||||
use crate::error::{SemanticError, SemanticErrorKind};
|
||||
use crate::symbols::{Symbol, SymbolsTable};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum Type {
|
||||
Int,
|
||||
Float,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TypedExpr {
|
||||
Int {
|
||||
value: i64,
|
||||
},
|
||||
Float {
|
||||
value: f64,
|
||||
},
|
||||
Var {
|
||||
name: Symbol,
|
||||
},
|
||||
BinOp {
|
||||
lhs: Box<TypedExpr>,
|
||||
op: BinOp,
|
||||
rhs: Box<TypedExpr>,
|
||||
},
|
||||
IntToFloat {
|
||||
value: Box<TypedExpr>,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<Type> for u8 {
|
||||
fn from(value: Type) -> Self {
|
||||
match value {
|
||||
Type::Int => 1,
|
||||
Type::Float => 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for Type {
|
||||
type Error = u8;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
1 => Ok(Type::Int),
|
||||
2 => Ok(Type::Float),
|
||||
b => Err(b),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Type {
|
||||
type Err = SemanticErrorKind;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.to_lowercase().as_str() {
|
||||
"i" => Ok(Type::Int),
|
||||
"f" => Ok(Type::Float),
|
||||
_ => Err(SemanticErrorKind::UnknownType(s.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Type {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Type::Int => write!(f, "int"),
|
||||
Type::Float => write!(f, "float"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TypedExpr {
|
||||
pub fn cast_to_float(self) -> TypedExpr {
|
||||
match self {
|
||||
Self::IntToFloat { .. } | Self::Float { .. } => self,
|
||||
_ => Self::IntToFloat {
|
||||
value: Box::new(self),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ty(&self, symbols: &SymbolsTable) -> Type {
|
||||
match self {
|
||||
Self::Int { .. } => Type::Int,
|
||||
Self::Float { .. } => Type::Float,
|
||||
Self::Var { name, .. } => symbols.type_of(name).expect("type not found"),
|
||||
Self::BinOp { lhs, .. } => lhs.ty(symbols),
|
||||
Self::IntToFloat { .. } => Type::Float,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bin_op(&self) -> Option<BinOp> {
|
||||
match self {
|
||||
Self::BinOp { op, .. } => Some(*op),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_var(&self) -> bool {
|
||||
match self {
|
||||
Self::Var { .. } => true,
|
||||
Self::IntToFloat { value } => value.is_var(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_const(&self) -> bool {
|
||||
match self {
|
||||
Self::Int { .. } | Self::Float { .. } => true,
|
||||
Self::IntToFloat { value } => value.is_const(),
|
||||
Self::BinOp { lhs, rhs, .. } => lhs.is_const() && rhs.is_const(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn precedence(&self) -> u8 {
|
||||
match self {
|
||||
Self::Int { .. } | Self::Float { .. } => 0,
|
||||
Self::Var { .. } => 0,
|
||||
Self::BinOp { op, .. } => op.precedence(),
|
||||
Self::IntToFloat { value } => value.precedence(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_untyped(
|
||||
expr: UntypedExpr,
|
||||
optimization_level: OLevel,
|
||||
symbols: &mut SymbolsTable,
|
||||
) -> error::Result<Self> {
|
||||
let expr = Self::convert_to_typed_expr(expr, symbols)?;
|
||||
let expr = Self::coerce_types(expr, symbols)?;
|
||||
let expr = if optimization_level > OLevel::None {
|
||||
let expr = if optimization_level >= OLevel::O3 {
|
||||
let ty = expr.ty(symbols);
|
||||
optimization::propagate_type_conversions(expr, ty, symbols)
|
||||
} else {
|
||||
expr
|
||||
};
|
||||
|
||||
let expr = if optimization_level >= OLevel::O2 {
|
||||
optimization::bubble_binop_vars(optimization::bubble_binop_vars(expr))
|
||||
} else {
|
||||
expr
|
||||
};
|
||||
|
||||
let expr = optimization::optimize_expr(expr);
|
||||
|
||||
expr
|
||||
} else {
|
||||
expr
|
||||
};
|
||||
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
fn convert_to_typed_expr(expr: UntypedExpr, symbols: &mut SymbolsTable) -> error::Result<Self> {
|
||||
let expr = match expr {
|
||||
UntypedExpr::Int { value, .. } => Self::Int { value },
|
||||
UntypedExpr::Float { value, .. } => Self::Float { value },
|
||||
UntypedExpr::Var {
|
||||
name,
|
||||
typename,
|
||||
span,
|
||||
} => {
|
||||
let ty = typename
|
||||
.and_then(|t| symbols.resolve(&t))
|
||||
.map(|data| data.name.as_str())
|
||||
.map(Type::from_str)
|
||||
.transpose()
|
||||
.map_err(|e| SemanticError::new(span, e))?;
|
||||
{
|
||||
let symbol = symbols.resolve_mut(&name).unwrap();
|
||||
match (symbol.ty, ty) {
|
||||
(Some(ty), Some(ty2)) if ty != ty2 => {
|
||||
return Err(SemanticError::new(
|
||||
span,
|
||||
SemanticErrorKind::DuplicateSymbol(symbol.name.clone()),
|
||||
)
|
||||
.into());
|
||||
},
|
||||
(None, Some(ty)) => {
|
||||
symbol.ty = Some(ty);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
Self::Var { name }
|
||||
},
|
||||
UntypedExpr::BinOp { lhs, op, rhs, .. } => {
|
||||
Self::check_division_by_zero(op, &rhs)?;
|
||||
|
||||
let lhs = Self::convert_to_typed_expr(*lhs, symbols)?;
|
||||
let rhs = Self::convert_to_typed_expr(*rhs, symbols)?;
|
||||
|
||||
Self::BinOp {
|
||||
lhs: Box::new(lhs),
|
||||
op,
|
||||
rhs: Box::new(rhs),
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
fn check_division_by_zero(op: BinOp, rhs: &UntypedExpr) -> error::Result<()> {
|
||||
match op {
|
||||
BinOp::Div if matches!(rhs, UntypedExpr::Int { .. } | UntypedExpr::Float { .. }) => {
|
||||
match &rhs {
|
||||
UntypedExpr::Int { span, value } if *value == 0 => {
|
||||
return Err(
|
||||
SemanticError::new(*span, SemanticErrorKind::DivisionByZero).into()
|
||||
);
|
||||
},
|
||||
UntypedExpr::Float { span, value } if *value == 0.0 => {
|
||||
return Err(
|
||||
SemanticError::new(*span, SemanticErrorKind::DivisionByZero).into()
|
||||
);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn coerce_types(expr: Self, symbols: &mut SymbolsTable) -> error::Result<Self> {
|
||||
let expr = match expr {
|
||||
TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::IntToFloat { .. } => expr,
|
||||
TypedExpr::Var { name, .. } => {
|
||||
let symbol = symbols.resolve_mut(&name).unwrap();
|
||||
|
||||
if symbol.ty.is_none() {
|
||||
symbol.ty = Some(Type::Int);
|
||||
}
|
||||
|
||||
expr
|
||||
},
|
||||
TypedExpr::BinOp { lhs, rhs, op } => {
|
||||
let lhs = Self::coerce_types(*lhs, symbols)?;
|
||||
let rhs = Self::coerce_types(*rhs, symbols)?;
|
||||
|
||||
Self::coerce_binop_types(lhs, op, rhs, symbols)?
|
||||
},
|
||||
};
|
||||
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
fn coerce_binop_types(
|
||||
lhs: Self,
|
||||
op: BinOp,
|
||||
rhs: Self,
|
||||
symbols: &SymbolsTable,
|
||||
) -> error::Result<Self> {
|
||||
let (lhs, rhs) = match (lhs.ty(symbols), rhs.ty(symbols)) {
|
||||
(Type::Int, Type::Int) => (lhs, rhs),
|
||||
(Type::Float, Type::Float) => (lhs, rhs),
|
||||
(Type::Int, Type::Float) => (lhs.cast_to_float(), rhs),
|
||||
(Type::Float, Type::Int) => (lhs, rhs.cast_to_float()),
|
||||
};
|
||||
|
||||
Ok(Self::BinOp {
|
||||
lhs: Box::new(lhs),
|
||||
op,
|
||||
rhs: Box::new(rhs),
|
||||
})
|
||||
}
|
||||
}
|
||||
36
crates/compiler/src/ast/untyped.rs
Normal file
36
crates/compiler/src/ast/untyped.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use super::{BinOp, Span};
|
||||
use crate::symbols::Symbol;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum UntypedExpr {
|
||||
Int {
|
||||
span: Span,
|
||||
value: i64,
|
||||
},
|
||||
Float {
|
||||
span: Span,
|
||||
value: f64,
|
||||
},
|
||||
Var {
|
||||
span: Span,
|
||||
name: Symbol,
|
||||
typename: Option<Symbol>,
|
||||
},
|
||||
BinOp {
|
||||
span: Span,
|
||||
lhs: Box<UntypedExpr>,
|
||||
op: BinOp,
|
||||
rhs: Box<UntypedExpr>,
|
||||
},
|
||||
}
|
||||
|
||||
impl UntypedExpr {
|
||||
pub fn span(&self) -> Span {
|
||||
match self {
|
||||
UntypedExpr::Float { span, .. } => *span,
|
||||
UntypedExpr::Int { span, .. } => *span,
|
||||
UntypedExpr::Var { span, .. } => *span,
|
||||
UntypedExpr::BinOp { span, .. } => *span,
|
||||
}
|
||||
}
|
||||
}
|
||||
216
crates/compiler/src/cli.rs
Normal file
216
crates/compiler/src/cli.rs
Normal file
@@ -0,0 +1,216 @@
|
||||
use std::ops::Deref;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::{CommandFactory, Parser, Subcommand};
|
||||
use compiler::ast::optimization::OLevel;
|
||||
|
||||
pub struct Args {
|
||||
inner: ArgsInner,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
pub struct ArgsInner {
|
||||
#[clap(subcommand)]
|
||||
pub command: Command,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum Command {
|
||||
Lex {
|
||||
input: PathBuf,
|
||||
output_tokens: PathBuf,
|
||||
output_symbols: PathBuf,
|
||||
},
|
||||
Syn {
|
||||
input: PathBuf,
|
||||
output_tree: PathBuf,
|
||||
},
|
||||
Sem {
|
||||
input: PathBuf,
|
||||
output_tree: PathBuf,
|
||||
#[clap(short, long, default_value_t = OLevel::None)]
|
||||
optimization_level: OLevel,
|
||||
},
|
||||
Gen {
|
||||
mode: GenMode,
|
||||
#[clap(short, long, default_value_t = OLevel::None)]
|
||||
optimization_level: OLevel,
|
||||
input: PathBuf,
|
||||
output: PathBuf,
|
||||
output_symbols: PathBuf,
|
||||
},
|
||||
Com {
|
||||
#[clap(short, long, default_value_t = OLevel::None)]
|
||||
optimization_level: OLevel,
|
||||
input: PathBuf,
|
||||
output: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, clap::ValueEnum)]
|
||||
pub enum GenMode {
|
||||
#[clap(name = "intermediate", alias = "i")]
|
||||
Intermediate,
|
||||
#[clap(name = "postfix", alias = "p")]
|
||||
Postfix,
|
||||
}
|
||||
|
||||
impl Args {
|
||||
pub fn parse() -> Self {
|
||||
let inner = match validate_inner(ArgsInner::parse()) {
|
||||
Ok(args) => args,
|
||||
Err(err) => {
|
||||
let mut command = ArgsInner::command();
|
||||
err.format(&mut command).exit();
|
||||
},
|
||||
};
|
||||
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Args {
|
||||
type Target = ArgsInner;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_inner(args: ArgsInner) -> Result<ArgsInner, clap::Error> {
|
||||
match &args.command {
|
||||
Command::Lex {
|
||||
input,
|
||||
output_tokens,
|
||||
output_symbols,
|
||||
..
|
||||
} => validate_lex(input, output_tokens, output_symbols)?,
|
||||
Command::Syn {
|
||||
input, output_tree, ..
|
||||
} => validate_syn(input, output_tree)?,
|
||||
Command::Sem {
|
||||
input, output_tree, ..
|
||||
} => validate_sem(input, output_tree)?,
|
||||
Command::Gen {
|
||||
input,
|
||||
output,
|
||||
output_symbols,
|
||||
..
|
||||
} => validate_gen(input, output, output_symbols)?,
|
||||
Command::Com { input, output, .. } => validate_com(input, output)?,
|
||||
};
|
||||
|
||||
Ok(args)
|
||||
}
|
||||
|
||||
fn validate_lex(
|
||||
input: &PathBuf,
|
||||
output_tokens: &PathBuf,
|
||||
output_symbols: &PathBuf,
|
||||
) -> Result<(), clap::Error> {
|
||||
if !input.is_file() {
|
||||
return Err(clap::Error::raw(
|
||||
clap::error::ErrorKind::InvalidValue,
|
||||
format!("Input file '{}' does not exist", input.display()),
|
||||
));
|
||||
}
|
||||
|
||||
if input == output_tokens {
|
||||
return Err(clap::Error::raw(
|
||||
clap::error::ErrorKind::InvalidValue,
|
||||
"Input and output files cannot be the same",
|
||||
));
|
||||
}
|
||||
|
||||
if input == output_symbols {
|
||||
return Err(clap::Error::raw(
|
||||
clap::error::ErrorKind::InvalidValue,
|
||||
"Input and output files cannot be the same",
|
||||
));
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_syn(input: &PathBuf, output_tree: &PathBuf) -> Result<(), clap::Error> {
|
||||
if !input.is_file() {
|
||||
return Err(clap::Error::raw(
|
||||
clap::error::ErrorKind::InvalidValue,
|
||||
format!("Input file '{}' does not exist", input.display()),
|
||||
));
|
||||
}
|
||||
|
||||
if output_tree == input {
|
||||
return Err(clap::Error::raw(
|
||||
clap::error::ErrorKind::InvalidValue,
|
||||
"Input and output files cannot be the same",
|
||||
));
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_sem(input: &PathBuf, output_tree: &PathBuf) -> Result<(), clap::Error> {
|
||||
if !input.is_file() {
|
||||
return Err(clap::Error::raw(
|
||||
clap::error::ErrorKind::InvalidValue,
|
||||
format!("Input file '{}' does not exist", input.display()),
|
||||
));
|
||||
}
|
||||
|
||||
if output_tree == input {
|
||||
return Err(clap::Error::raw(
|
||||
clap::error::ErrorKind::InvalidValue,
|
||||
"Input and output files cannot be the same",
|
||||
));
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_gen(
|
||||
input: &PathBuf,
|
||||
output: &PathBuf,
|
||||
output_symbols: &PathBuf,
|
||||
) -> Result<(), clap::Error> {
|
||||
if !input.is_file() {
|
||||
return Err(clap::Error::raw(
|
||||
clap::error::ErrorKind::InvalidValue,
|
||||
format!("Input file '{}' does not exist", input.display()),
|
||||
));
|
||||
}
|
||||
|
||||
if input == output {
|
||||
return Err(clap::Error::raw(
|
||||
clap::error::ErrorKind::InvalidValue,
|
||||
"Input and output files cannot be the same",
|
||||
));
|
||||
};
|
||||
|
||||
if input == output_symbols {
|
||||
return Err(clap::Error::raw(
|
||||
clap::error::ErrorKind::InvalidValue,
|
||||
"Input and output files cannot be the same",
|
||||
));
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_com(input: &PathBuf, output: &PathBuf) -> Result<(), clap::Error> {
|
||||
if !input.is_file() {
|
||||
return Err(clap::Error::raw(
|
||||
clap::error::ErrorKind::InvalidValue,
|
||||
format!("Input file '{}' does not exist", input.display()),
|
||||
));
|
||||
}
|
||||
|
||||
if input == output {
|
||||
return Err(clap::Error::raw(
|
||||
clap::error::ErrorKind::InvalidValue,
|
||||
"Input and output files cannot be the same",
|
||||
));
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
204
crates/compiler/src/error.rs
Normal file
204
crates/compiler/src/error.rs
Normal file
@@ -0,0 +1,204 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use crate::ast::Span;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub struct Error {
|
||||
span: Span,
|
||||
kind: ErrorKind,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ErrorKind {
|
||||
Lexical(LexicalErrorKind),
|
||||
Parse(ParseErrorKind),
|
||||
Semantic(SemanticErrorKind),
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub struct LexicalError {
|
||||
span: Span,
|
||||
kind: LexicalErrorKind,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum LexicalErrorKind {
|
||||
UnrecognizedToken,
|
||||
BadNumber(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub struct ParseError {
|
||||
span: Span,
|
||||
kind: ParseErrorKind,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ParseErrorKind {
|
||||
ExpectedExpr,
|
||||
UnexpectedToken,
|
||||
UnexpectedEOF,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub struct SemanticError {
|
||||
span: Span,
|
||||
kind: SemanticErrorKind,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SemanticErrorKind {
|
||||
UnknownType(String),
|
||||
DivisionByZero,
|
||||
DuplicateSymbol(String),
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn new(span: Span, kind: ErrorKind) -> Self {
|
||||
Self { span, kind }
|
||||
}
|
||||
|
||||
pub fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> &ErrorKind {
|
||||
&self.kind
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{} at position {}", self.kind, self.span.start)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ErrorKind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ErrorKind::Lexical(e) => write!(f, "lexical error: {}", e),
|
||||
ErrorKind::Parse(e) => write!(f, "parse error: {}", e),
|
||||
ErrorKind::Semantic(e) => write!(f, "semantic error: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LexicalError> for Error {
|
||||
fn from(e: LexicalError) -> Self {
|
||||
Self {
|
||||
span: e.span(),
|
||||
kind: ErrorKind::Lexical(e.kind),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseError> for Error {
|
||||
fn from(e: ParseError) -> Self {
|
||||
Self {
|
||||
span: e.span(),
|
||||
kind: ErrorKind::Parse(e.kind),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SemanticError> for Error {
|
||||
fn from(e: SemanticError) -> Self {
|
||||
Self {
|
||||
span: e.span(),
|
||||
kind: ErrorKind::Semantic(e.kind),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LexicalError {
|
||||
pub fn new(span: Span, kind: LexicalErrorKind) -> Self {
|
||||
Self { span, kind }
|
||||
}
|
||||
|
||||
pub fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> &LexicalErrorKind {
|
||||
&self.kind
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LexicalError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{} at position {}", self.kind, self.span.start)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LexicalErrorKind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
LexicalErrorKind::UnrecognizedToken => write!(f, "unrecognized token"),
|
||||
LexicalErrorKind::BadNumber(s) => write!(f, "bad number '{}'", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseError {
|
||||
pub fn new(span: Span, kind: ParseErrorKind) -> Self {
|
||||
Self { span, kind }
|
||||
}
|
||||
|
||||
pub fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> &ParseErrorKind {
|
||||
&self.kind
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ParseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{} at position {}", self.kind, self.span.start)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ParseErrorKind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ParseErrorKind::ExpectedExpr => write!(f, "expected expression"),
|
||||
ParseErrorKind::UnexpectedToken => write!(f, "unexpected token"),
|
||||
ParseErrorKind::UnexpectedEOF => write!(f, "unexpected end of file"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SemanticError {
|
||||
pub fn new(span: Span, kind: SemanticErrorKind) -> Self {
|
||||
Self { span, kind }
|
||||
}
|
||||
|
||||
pub fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> &SemanticErrorKind {
|
||||
&self.kind
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SemanticError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{} at position {}", self.kind, self.span.start)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SemanticErrorKind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
SemanticErrorKind::UnknownType(s) => write!(f, "unknown type '{}'", s),
|
||||
SemanticErrorKind::DivisionByZero => write!(f, "division by zero"),
|
||||
SemanticErrorKind::DuplicateSymbol(s) => write!(f, "duplicate symbol '{}'", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
5
crates/compiler/src/lib.rs
Normal file
5
crates/compiler/src/lib.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
pub mod ast;
|
||||
pub mod error;
|
||||
pub mod parse;
|
||||
pub mod representation;
|
||||
pub mod symbols;
|
||||
204
crates/compiler/src/main.rs
Normal file
204
crates/compiler/src/main.rs
Normal file
@@ -0,0 +1,204 @@
|
||||
mod cli;
|
||||
mod util;
|
||||
|
||||
use std::fs;
|
||||
use std::io::{self, Write};
|
||||
use std::path::Path;
|
||||
|
||||
use cli::GenMode;
|
||||
use compiler::ast::optimization::OLevel;
|
||||
use compiler::ast::typed::TypedExpr;
|
||||
use compiler::parse::parser::Parser;
|
||||
use compiler::representation::intermediate::IntermediateExpr;
|
||||
use compiler::symbols::SymbolsTable;
|
||||
use compiler::*;
|
||||
use integer_encoding::VarIntWriter;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let args = cli::Args::parse();
|
||||
|
||||
let result = match &args.command {
|
||||
cli::Command::Lex {
|
||||
input,
|
||||
output_tokens,
|
||||
output_symbols,
|
||||
} => lex_command(input, output_tokens, output_symbols),
|
||||
cli::Command::Syn { input, output_tree } => syn_command(input, output_tree),
|
||||
cli::Command::Sem {
|
||||
optimization_level,
|
||||
input,
|
||||
output_tree,
|
||||
} => sem_command(*optimization_level, input, output_tree),
|
||||
cli::Command::Gen {
|
||||
mode,
|
||||
optimization_level,
|
||||
input,
|
||||
output,
|
||||
output_symbols,
|
||||
} => gen_command(mode, *optimization_level, input, output, output_symbols),
|
||||
cli::Command::Com {
|
||||
optimization_level,
|
||||
input,
|
||||
output,
|
||||
} => com_command(*optimization_level, input, output),
|
||||
};
|
||||
|
||||
if let Err(e) = result {
|
||||
eprintln!("error: {}", e);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn com_command(o_level: OLevel, input: &Path, output: &Path) -> Result<(), anyhow::Error> {
|
||||
let input = fs::read_to_string(input)?;
|
||||
let mut symbols = SymbolsTable::default();
|
||||
let typed_expr = match {
|
||||
let tokens = parse::lexer::make_tokenizer(&input, &mut symbols);
|
||||
let mut parser = Parser::new(tokens);
|
||||
parser.parse()
|
||||
}
|
||||
.and_then(|expr| TypedExpr::from_untyped(expr, o_level, &mut symbols))
|
||||
{
|
||||
Ok(expr) => expr,
|
||||
Err(e) => {
|
||||
return Err(e.into());
|
||||
},
|
||||
};
|
||||
|
||||
let ir = IntermediateExpr::from_typed_expr(typed_expr, o_level >= OLevel::None, &mut symbols);
|
||||
{
|
||||
let used_symbols = util::collect_used_symbols(&ir);
|
||||
symbols.retain(&used_symbols);
|
||||
}
|
||||
|
||||
let mut writer = io::BufWriter::new(fs::File::create(output)?);
|
||||
symbols.write(&mut writer)?;
|
||||
writer.write_varint::<u64>(ir.len() as u64)?;
|
||||
for expr in ir {
|
||||
expr.write(&mut writer)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn gen_command(
|
||||
mode: &GenMode,
|
||||
o_level: OLevel,
|
||||
input: &Path,
|
||||
output: &Path,
|
||||
output_symbols: &Path,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let input = fs::read_to_string(input)?;
|
||||
let mut symbols = SymbolsTable::default();
|
||||
let typed_expr = match {
|
||||
let tokens = parse::lexer::make_tokenizer(&input, &mut symbols);
|
||||
let mut parser = Parser::new(tokens);
|
||||
parser.parse()
|
||||
}
|
||||
.and_then(|expr| TypedExpr::from_untyped(expr, o_level, &mut symbols))
|
||||
{
|
||||
Ok(expr) => expr,
|
||||
Err(e) => {
|
||||
return Err(e.into());
|
||||
},
|
||||
};
|
||||
|
||||
let mut writer = io::BufWriter::new(fs::File::create(output)?);
|
||||
|
||||
match mode {
|
||||
GenMode::Intermediate => {
|
||||
let intermediate_exprs = IntermediateExpr::from_typed_expr(
|
||||
typed_expr,
|
||||
o_level >= OLevel::None,
|
||||
&mut symbols,
|
||||
);
|
||||
|
||||
let used_symbols = util::collect_used_symbols(&intermediate_exprs);
|
||||
symbols.retain(&used_symbols);
|
||||
|
||||
util::print_intermediate_exprs(&intermediate_exprs, &mut writer)?;
|
||||
},
|
||||
GenMode::Postfix => {
|
||||
util::print_postfix_expr(&typed_expr, &mut writer)?;
|
||||
},
|
||||
}
|
||||
|
||||
let mut writer_symbols = io::BufWriter::new(fs::File::create(output_symbols)?);
|
||||
for (name, data) in &symbols {
|
||||
writeln!(writer_symbols, "{name} -> {}", data)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sem_command(o_level: OLevel, input: &Path, output_tree: &Path) -> Result<(), anyhow::Error> {
|
||||
let input = fs::read_to_string(input)?;
|
||||
let mut symbols = SymbolsTable::default();
|
||||
|
||||
let res = {
|
||||
let tokens = parse::lexer::make_tokenizer(&input, &mut symbols);
|
||||
let mut parser = Parser::new(tokens);
|
||||
parser.parse()
|
||||
}
|
||||
.and_then(|expr| TypedExpr::from_untyped(expr, o_level, &mut symbols));
|
||||
|
||||
match res {
|
||||
Ok(expr) => {
|
||||
let mut writer_tree = io::BufWriter::new(fs::File::create(output_tree)?);
|
||||
util::print_typed_expr(&expr, &symbols, &mut writer_tree)?;
|
||||
},
|
||||
Err(e) => return Err(e.into()),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn syn_command(input: &Path, output_tree: &Path) -> Result<(), anyhow::Error> {
|
||||
let input = fs::read_to_string(input)?;
|
||||
let mut symbols = SymbolsTable::default();
|
||||
|
||||
let tokens = parse::lexer::make_tokenizer(&input, &mut symbols);
|
||||
let mut parser = Parser::new(tokens);
|
||||
let res = parser.parse();
|
||||
|
||||
match res {
|
||||
Ok(expr) => {
|
||||
let mut writer_tree = io::BufWriter::new(fs::File::create(output_tree)?);
|
||||
util::print_untyped_expr(&expr, &mut writer_tree)?;
|
||||
},
|
||||
Err(e) => return Err(e.into()),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn lex_command(
|
||||
input: &Path,
|
||||
output_tokens: &Path,
|
||||
output_symbols: &Path,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let input = fs::read_to_string(input)?;
|
||||
let mut symbols = SymbolsTable::default();
|
||||
let tokens = parse::lexer::make_tokenizer(&input, &mut symbols).collect::<Result<Vec<_>, _>>();
|
||||
|
||||
match tokens {
|
||||
Ok(tokens) => {
|
||||
let mut writer_tokens = io::BufWriter::new(fs::File::create(output_tokens)?);
|
||||
|
||||
for (_, token, _) in tokens {
|
||||
writeln!(writer_tokens, "{token:>6} - {}", token.as_str())?;
|
||||
}
|
||||
|
||||
let mut writer_symbols = io::BufWriter::new(fs::File::create(output_symbols)?);
|
||||
for (name, data) in &symbols {
|
||||
writeln!(writer_symbols, "{name} -> {}", data)?;
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
return Err(e.into());
|
||||
},
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
215
crates/compiler/src/parse/lexer.rs
Normal file
215
crates/compiler/src/parse/lexer.rs
Normal file
@@ -0,0 +1,215 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use itertools::PeekNth;
|
||||
|
||||
use super::token::Token;
|
||||
use crate::ast::Span;
|
||||
use crate::error::{LexicalError, LexicalErrorKind};
|
||||
use crate::symbols::SymbolsTable;
|
||||
|
||||
pub type SpannedToken = (usize, Token, usize);
|
||||
pub type LexerResult = Result<SpannedToken, LexicalError>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Lexer<'s, T: Iterator<Item = char>> {
|
||||
chars: PeekNth<T>,
|
||||
pos: usize,
|
||||
pending: VecDeque<SpannedToken>,
|
||||
symbols: &'s mut SymbolsTable,
|
||||
}
|
||||
|
||||
pub fn make_tokenizer<'s>(
|
||||
input: &'s str,
|
||||
symbols: &'s mut SymbolsTable,
|
||||
) -> impl Iterator<Item = LexerResult> + 's {
|
||||
let chars = input.chars();
|
||||
Lexer::new(chars, symbols)
|
||||
}
|
||||
|
||||
impl<'s, T: Iterator<Item = char>> Lexer<'s, T> {
|
||||
pub fn new(chars: T, symbols: &'s mut SymbolsTable) -> Self {
|
||||
Self {
|
||||
chars: itertools::peek_nth(chars),
|
||||
pos: 0,
|
||||
pending: VecDeque::new(),
|
||||
symbols,
|
||||
}
|
||||
}
|
||||
|
||||
fn next_token(&mut self) -> LexerResult {
|
||||
while self.pending.is_empty() {
|
||||
self.consume_token()?;
|
||||
}
|
||||
|
||||
Ok(self.pending.pop_front().unwrap())
|
||||
}
|
||||
|
||||
fn consume_token(&mut self) -> Result<(), LexicalError> {
|
||||
if let Some(c) = self.peek_char_nth(0) {
|
||||
if self.is_name_start(c) {
|
||||
let name = self.lex_name()?;
|
||||
self.emit(name);
|
||||
} else if self.is_number_start(c) {
|
||||
let number = self.lex_number()?;
|
||||
self.emit(number);
|
||||
} else {
|
||||
self.consume_char(c)?;
|
||||
}
|
||||
} else {
|
||||
let pos = self.get_pos();
|
||||
self.emit((pos, Token::EndOfFile, pos));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn lex_name(&mut self) -> LexerResult {
|
||||
let mut name = String::new();
|
||||
let start = self.get_pos();
|
||||
|
||||
while let Some(c) = self.peek_char_nth(0) {
|
||||
if self.is_name_continue(c) {
|
||||
name.push(self.next_char().expect("lex_name: no more characters"));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let end = self.get_pos();
|
||||
|
||||
if let Some(id) = self.symbols.get(&name) {
|
||||
Ok((start, Token::Name(id), end))
|
||||
} else {
|
||||
let id = self.symbols.add(name.clone());
|
||||
// let id = self.symbols.get(&name).unwrap();
|
||||
Ok((start, Token::Name(id), end))
|
||||
}
|
||||
}
|
||||
|
||||
fn lex_number(&mut self) -> LexerResult {
|
||||
let start = self.get_pos();
|
||||
|
||||
let mut number = String::new();
|
||||
number.push(self.next_char().expect("lex_number: no more characters"));
|
||||
|
||||
let mut passed_dot = false;
|
||||
while let Some(c) = self.peek_char_nth(0) {
|
||||
if self.is_digit(c) {
|
||||
number.push(self.next_char().expect("lex_number: no more characters"));
|
||||
} else if !passed_dot
|
||||
&& c == '.'
|
||||
&& self.peek_char_nth(1).map_or(false, |c| self.is_digit(c))
|
||||
{
|
||||
passed_dot = true;
|
||||
number.push(self.next_char().expect("lex_number: no more characters"));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let end = self.get_pos();
|
||||
|
||||
Ok((
|
||||
start,
|
||||
match number.parse::<i64>() {
|
||||
Ok(n) => Token::Int(n),
|
||||
Err(_) => match number.parse::<f64>() {
|
||||
Ok(n) => Token::Float(n),
|
||||
Err(_) => {
|
||||
return Err(LexicalError::new(
|
||||
Span::new(start, end),
|
||||
LexicalErrorKind::BadNumber(number),
|
||||
));
|
||||
},
|
||||
},
|
||||
},
|
||||
end,
|
||||
))
|
||||
}
|
||||
|
||||
fn consume_char(&mut self, c: char) -> Result<(), LexicalError> {
|
||||
match c {
|
||||
'+' => self.eat_next_char(Token::Plus),
|
||||
'-' => self.eat_next_char(Token::Minus),
|
||||
'*' => self.eat_next_char(Token::Star),
|
||||
'/' => self.eat_next_char(Token::Slash),
|
||||
'(' => self.eat_next_char(Token::LParen),
|
||||
')' => self.eat_next_char(Token::RParen),
|
||||
'[' => self.eat_next_char(Token::LBracket),
|
||||
']' => self.eat_next_char(Token::RBracket),
|
||||
c if c.is_whitespace() => {
|
||||
let _start = self.get_pos();
|
||||
let _c = self.next_char();
|
||||
let _end = self.get_pos();
|
||||
|
||||
// if c == '\n' {
|
||||
// self.emit((start, Token::NewLine, end));
|
||||
// }
|
||||
},
|
||||
_ => {
|
||||
let pos = self.get_pos();
|
||||
let _ = self.next_char();
|
||||
return Err(LexicalError::new(
|
||||
Span::new(pos, self.get_pos()),
|
||||
LexicalErrorKind::UnrecognizedToken,
|
||||
));
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn eat_next_char(&mut self, token: Token) {
|
||||
let start = self.get_pos();
|
||||
let _ = self.next_char().expect("eat_next_char: no more characters");
|
||||
let end = self.get_pos();
|
||||
|
||||
self.emit((start, token, end));
|
||||
}
|
||||
|
||||
fn is_number_start(&self, c: char) -> bool {
|
||||
c.is_ascii_digit()
|
||||
}
|
||||
|
||||
fn is_digit(&self, c: char) -> bool {
|
||||
c.is_ascii_digit()
|
||||
}
|
||||
|
||||
fn is_name_start(&self, c: char) -> bool {
|
||||
c.is_alphabetic() || c == '_'
|
||||
}
|
||||
|
||||
fn is_name_continue(&self, c: char) -> bool {
|
||||
c.is_alphanumeric() || c == '_'
|
||||
}
|
||||
|
||||
fn next_char(&mut self) -> Option<char> {
|
||||
let char = self.chars.next()?;
|
||||
self.pos += char.len_utf8();
|
||||
|
||||
Some(char)
|
||||
}
|
||||
|
||||
fn emit(&mut self, token: SpannedToken) {
|
||||
self.pending.push_back(token);
|
||||
}
|
||||
|
||||
fn get_pos(&self) -> usize {
|
||||
self.pos
|
||||
}
|
||||
|
||||
fn peek_char_nth(&mut self, n: usize) -> Option<char> {
|
||||
self.chars.peek_nth(n).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s, T: Iterator<Item = char>> Iterator for Lexer<'s, T> {
|
||||
type Item = LexerResult;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self.next_token() {
|
||||
Ok((_, Token::EndOfFile, _)) => None,
|
||||
r => Some(r),
|
||||
}
|
||||
}
|
||||
}
|
||||
3
crates/compiler/src/parse/mod.rs
Normal file
3
crates/compiler/src/parse/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod lexer;
|
||||
pub mod parser;
|
||||
pub mod token;
|
||||
176
crates/compiler/src/parse/parser.rs
Normal file
176
crates/compiler/src/parse/parser.rs
Normal file
@@ -0,0 +1,176 @@
|
||||
use itertools::PeekNth;
|
||||
|
||||
use super::lexer::{LexerResult, SpannedToken};
|
||||
use super::token::Token;
|
||||
use crate::ast::{Span, UntypedExpr};
|
||||
use crate::error::{self, ParseError, ParseErrorKind};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Parser<T: Iterator<Item = LexerResult>> {
|
||||
tokens: PeekNth<T>,
|
||||
last_span: Span,
|
||||
}
|
||||
|
||||
impl<T> Parser<T>
|
||||
where
|
||||
T: Iterator<Item = LexerResult>,
|
||||
{
|
||||
pub fn new(tokens: T) -> Self {
|
||||
Self {
|
||||
tokens: itertools::peek_nth(tokens),
|
||||
last_span: Span::new(0, 0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(&mut self) -> error::Result<UntypedExpr> {
|
||||
let expr = self.parse_expr()?;
|
||||
if self.has_next() {
|
||||
let (start, _, end) = self.next_token()?;
|
||||
|
||||
return Err(
|
||||
ParseError::new(Span::new(start, end), ParseErrorKind::UnexpectedToken).into(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
fn parse_expr(&mut self) -> error::Result<UntypedExpr> {
|
||||
let lhs = self.parse_primary_expr()?;
|
||||
|
||||
self.parse_expr_inner(lhs, 0)
|
||||
}
|
||||
|
||||
fn parse_expr_inner(
|
||||
&mut self,
|
||||
lhs: UntypedExpr,
|
||||
min_precedence: u8,
|
||||
) -> error::Result<UntypedExpr> {
|
||||
let mut lhs = lhs;
|
||||
|
||||
let mut op = self.peek_token_nth(0).and_then(|token| token.as_bin_op());
|
||||
while let Some(op1) = op {
|
||||
if op1.precedence() < min_precedence {
|
||||
break;
|
||||
}
|
||||
|
||||
let _ = self.next_token()?;
|
||||
let mut rhs = self.parse_primary_expr()?;
|
||||
|
||||
op = self.peek_token_nth(0).and_then(|token| token.as_bin_op());
|
||||
while let Some(op2) = op {
|
||||
if op2.precedence() <= op1.precedence() {
|
||||
break;
|
||||
}
|
||||
|
||||
rhs = self.parse_expr_inner(rhs, op2.precedence())?;
|
||||
|
||||
op = self.peek_token_nth(0).and_then(|token| token.as_bin_op());
|
||||
}
|
||||
|
||||
lhs = UntypedExpr::BinOp {
|
||||
span: Span::new(lhs.span().start, rhs.span().end),
|
||||
lhs: Box::new(lhs),
|
||||
op: op1,
|
||||
rhs: Box::new(rhs),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(lhs)
|
||||
}
|
||||
|
||||
fn parse_primary_expr(&mut self) -> error::Result<UntypedExpr> {
|
||||
let token = self.next_token()?;
|
||||
|
||||
match token {
|
||||
(start, Token::Float(value), end) => Ok(UntypedExpr::Float {
|
||||
span: Span::new(start, end),
|
||||
value,
|
||||
}),
|
||||
(start, Token::Int(value), end) => Ok(UntypedExpr::Int {
|
||||
span: Span::new(start, end),
|
||||
value,
|
||||
}),
|
||||
(start, Token::Name(name), end) => {
|
||||
let (typename, end) = match self.peek_token_nth(0) {
|
||||
Some(Token::LBracket) => {
|
||||
let _ = self.next_token()?;
|
||||
let (_, typename, _) =
|
||||
self.expect_token_predicate(|t| matches!(t, Token::Name(_)))?;
|
||||
let (_, end) = self.expect_token(Token::RBracket)?;
|
||||
|
||||
let typename = match typename {
|
||||
Token::Name(id) => id,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
(Some(typename), end)
|
||||
},
|
||||
_ => (None, end),
|
||||
};
|
||||
|
||||
Ok(UntypedExpr::Var {
|
||||
span: Span::new(start, end),
|
||||
name,
|
||||
typename,
|
||||
})
|
||||
},
|
||||
(_, Token::LParen, _) => {
|
||||
let expr = self.parse_expr()?;
|
||||
let _ = self.expect_token(Token::RParen)?;
|
||||
|
||||
Ok(expr)
|
||||
},
|
||||
(start, _, end) => Err(ParseError::new(
|
||||
Span::new(start, end),
|
||||
ParseErrorKind::ExpectedExpr,
|
||||
))?,
|
||||
}
|
||||
}
|
||||
|
||||
fn next_token(&mut self) -> error::Result<SpannedToken> {
|
||||
let token = self.tokens.next();
|
||||
match token {
|
||||
Some(Ok((start, token, end))) => {
|
||||
self.last_span = Span::new(start, end);
|
||||
Ok((start, token, end))
|
||||
},
|
||||
Some(Err(e)) => Err(e.into()),
|
||||
None => Err(ParseError::new(self.last_span, ParseErrorKind::UnexpectedEOF).into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn peek_token_nth(&mut self, n: usize) -> Option<&Token> {
|
||||
self.tokens.peek_nth(n).and_then(|res| match res {
|
||||
Ok((_, token, _)) => Some(token),
|
||||
Err(_) => None,
|
||||
})
|
||||
}
|
||||
|
||||
fn has_next(&mut self) -> bool {
|
||||
self.tokens.peek().is_some()
|
||||
}
|
||||
|
||||
fn expect_token(&mut self, token: Token) -> error::Result<(usize, usize)> {
|
||||
let t = self.next_token()?;
|
||||
match t {
|
||||
(start, t, end) if t == token => Ok((start, end)),
|
||||
(start, _, end) => {
|
||||
Err(ParseError::new(Span::new(start, end), ParseErrorKind::UnexpectedToken).into())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_token_predicate(
|
||||
&mut self,
|
||||
pred: impl Fn(&Token) -> bool,
|
||||
) -> error::Result<SpannedToken> {
|
||||
let t = self.next_token()?;
|
||||
match t {
|
||||
(start, t, end) if pred(&t) => Ok((start, t, end)),
|
||||
(start, _, end) => {
|
||||
Err(ParseError::new(Span::new(start, end), ParseErrorKind::UnexpectedToken).into())
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
80
crates/compiler/src/parse/token.rs
Normal file
80
crates/compiler/src/parse/token.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
use std::fmt;
|
||||
|
||||
use crate::ast::BinOp;
|
||||
use crate::symbols::Symbol;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Token {
|
||||
Name(Symbol),
|
||||
Float(f64),
|
||||
Int(i64),
|
||||
// operators
|
||||
Plus, // +
|
||||
Minus, // -
|
||||
Star, // *
|
||||
Slash, // /
|
||||
// punctuation
|
||||
LParen, // (
|
||||
RParen, // )
|
||||
LBracket, // [
|
||||
RBracket, // ]
|
||||
// extra
|
||||
// NewLine, // new line
|
||||
EndOfFile, // end of file
|
||||
}
|
||||
|
||||
impl Token {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
Token::Name(_) => "name",
|
||||
Token::Float(_) => "float",
|
||||
Token::Int(_) => "int",
|
||||
Token::Plus => "plus",
|
||||
Token::Minus => "minus",
|
||||
Token::Star => "mul",
|
||||
Token::Slash => "div",
|
||||
Token::LParen => "lparen",
|
||||
Token::RParen => "rparen",
|
||||
Token::LBracket => "lbracket",
|
||||
Token::RBracket => "rbracket",
|
||||
// Token::NewLine => "new line",
|
||||
Token::EndOfFile => "end of file",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_bin_op(&self) -> Option<BinOp> {
|
||||
match self {
|
||||
Token::Plus => Some(BinOp::Add),
|
||||
Token::Minus => Some(BinOp::Sub),
|
||||
Token::Star => Some(BinOp::Mul),
|
||||
Token::Slash => Some(BinOp::Div),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Token {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Token::Name(id) => write!(f, "<id,{id}>"),
|
||||
Token::Float(number) => {
|
||||
if number == &number.trunc() {
|
||||
write!(f, "<{number}.0>")
|
||||
} else {
|
||||
write!(f, "<{number}>")
|
||||
}
|
||||
},
|
||||
Token::Int(number) => write!(f, "<{number}>"),
|
||||
Token::Plus => write!(f, "<+>"),
|
||||
Token::Minus => write!(f, "<->"),
|
||||
Token::Star => write!(f, "<*>"),
|
||||
Token::Slash => write!(f, "</>"),
|
||||
Token::LParen => write!(f, "<(>"),
|
||||
Token::RParen => write!(f, "<)>"),
|
||||
Token::LBracket => write!(f, "<[>"),
|
||||
Token::RBracket => write!(f, "<]>"),
|
||||
// Token::NewLine => write!(f, "<new line>"),
|
||||
Token::EndOfFile => write!(f, "<end of file>"),
|
||||
}
|
||||
}
|
||||
}
|
||||
247
crates/compiler/src/representation/intermediate.rs
Normal file
247
crates/compiler/src/representation/intermediate.rs
Normal file
@@ -0,0 +1,247 @@
|
||||
use std::{fmt, io};
|
||||
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use integer_encoding::{VarIntReader, VarIntWriter};
|
||||
|
||||
use crate::ast::BinOp;
|
||||
use crate::ast::typed::TypedExpr;
|
||||
use crate::representation::util::TempVarContext;
|
||||
use crate::symbols::{Symbol, SymbolsTable};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum IntermediateValue {
|
||||
Int { value: i64 },
|
||||
Float { value: f64 },
|
||||
Var { name: Symbol },
|
||||
}
|
||||
|
||||
impl IntermediateValue {
|
||||
pub fn as_u8(&self) -> u8 {
|
||||
match self {
|
||||
IntermediateValue::Int { .. } => b'i',
|
||||
IntermediateValue::Float { .. } => b'f',
|
||||
IntermediateValue::Var { .. } => b'v',
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&self, writer: &mut impl io::Write) -> io::Result<()> {
|
||||
writer.write_u8(self.as_u8())?;
|
||||
|
||||
match self {
|
||||
IntermediateValue::Int { value } => _ = writer.write_varint(*value)?,
|
||||
IntermediateValue::Float { value } => writer.write_f64::<BigEndian>(*value)?,
|
||||
IntermediateValue::Var { name } => _ = name.write(writer),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read(reader: &mut impl io::Read) -> io::Result<Self> {
|
||||
let b = reader.read_u8()?;
|
||||
match b {
|
||||
b'i' => Ok(IntermediateValue::Int {
|
||||
value: reader.read_varint()?,
|
||||
}),
|
||||
b'f' => Ok(IntermediateValue::Float {
|
||||
value: reader.read_f64::<BigEndian>()?,
|
||||
}),
|
||||
b'v' => Ok(IntermediateValue::Var {
|
||||
name: Symbol::read(reader)?,
|
||||
}),
|
||||
b => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!("invalid byte: {b}"),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for IntermediateValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
IntermediateValue::Int { value } => write!(f, "{}", value),
|
||||
IntermediateValue::Float { value } => {
|
||||
if *value == value.trunc() {
|
||||
write!(f, "{value}.0")
|
||||
} else {
|
||||
write!(f, "{value}")
|
||||
}
|
||||
},
|
||||
IntermediateValue::Var { name } => write!(f, "<id,{}>", name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum IntermediateExpr {
|
||||
IntToFloat {
|
||||
result: Symbol,
|
||||
value: IntermediateValue,
|
||||
},
|
||||
BinOp {
|
||||
result: Symbol,
|
||||
lhs: IntermediateValue,
|
||||
op: BinOp,
|
||||
rhs: IntermediateValue,
|
||||
},
|
||||
Identity {
|
||||
result: Symbol,
|
||||
value: IntermediateValue,
|
||||
},
|
||||
}
|
||||
|
||||
impl fmt::Display for IntermediateExpr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
IntermediateExpr::IntToFloat { result, value } => {
|
||||
write!(f, "i2f <id,{}>, {}", result, value)
|
||||
},
|
||||
IntermediateExpr::BinOp {
|
||||
result,
|
||||
lhs,
|
||||
op,
|
||||
rhs,
|
||||
} => {
|
||||
write!(f, "{:#} <id,{}>, {}, {}", op, result, lhs, rhs)
|
||||
},
|
||||
IntermediateExpr::Identity { result, value } => {
|
||||
write!(f, "idt <id,{}>, {}", result, value)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntermediateExpr {
|
||||
pub fn as_u8(&self) -> u8 {
|
||||
match self {
|
||||
IntermediateExpr::IntToFloat { .. } => 0x01,
|
||||
IntermediateExpr::BinOp { .. } => 0x02,
|
||||
IntermediateExpr::Identity { .. } => 0x03,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&self, writer: &mut impl io::Write) -> io::Result<()> {
|
||||
writer.write_u8(self.as_u8())?;
|
||||
|
||||
match self {
|
||||
IntermediateExpr::IntToFloat { result, value } => {
|
||||
result.write(writer)?;
|
||||
value.write(writer)?;
|
||||
},
|
||||
IntermediateExpr::BinOp {
|
||||
result,
|
||||
lhs,
|
||||
op,
|
||||
rhs,
|
||||
} => {
|
||||
result.write(writer)?;
|
||||
lhs.write(writer)?;
|
||||
writer.write_u8(op.into())?;
|
||||
rhs.write(writer)?;
|
||||
},
|
||||
IntermediateExpr::Identity { result, value } => {
|
||||
result.write(writer)?;
|
||||
value.write(writer)?;
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read(reader: &mut impl io::Read) -> io::Result<Self> {
|
||||
let b = reader.read_u8()?;
|
||||
match b {
|
||||
0x01 => Ok(IntermediateExpr::IntToFloat {
|
||||
result: Symbol::read(reader)?,
|
||||
value: IntermediateValue::read(reader)?,
|
||||
}),
|
||||
0x02 => Ok(IntermediateExpr::BinOp {
|
||||
result: Symbol::read(reader)?,
|
||||
lhs: IntermediateValue::read(reader)?,
|
||||
op: BinOp::try_from(reader.read_u8()?)
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "invalid bin op"))?,
|
||||
rhs: IntermediateValue::read(reader)?,
|
||||
}),
|
||||
0x03 => Ok(IntermediateExpr::Identity {
|
||||
result: Symbol::read(reader)?,
|
||||
value: IntermediateValue::read(reader)?,
|
||||
}),
|
||||
b => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!("invalid byte: {b}"),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_typed_expr(
|
||||
expr: TypedExpr,
|
||||
reuse_symbols: bool,
|
||||
symbols: &mut SymbolsTable,
|
||||
) -> Vec<IntermediateExpr> {
|
||||
let ty = expr.ty(symbols);
|
||||
|
||||
let mut exprs = Vec::new();
|
||||
let mut temp_var_context = TempVarContext::new(reuse_symbols, symbols);
|
||||
|
||||
let value = Self::from_typed_expr_inner(expr, &mut exprs, &mut temp_var_context);
|
||||
|
||||
if exprs.is_empty() {
|
||||
return vec![IntermediateExpr::Identity {
|
||||
result: temp_var_context.get_temp_var(ty),
|
||||
value,
|
||||
}];
|
||||
}
|
||||
|
||||
exprs
|
||||
}
|
||||
|
||||
fn from_typed_expr_inner(
|
||||
expr: TypedExpr,
|
||||
exprs: &mut Vec<IntermediateExpr>,
|
||||
temp_var_context: &mut TempVarContext,
|
||||
) -> IntermediateValue {
|
||||
let ty = expr.ty(temp_var_context.symbols());
|
||||
|
||||
match expr {
|
||||
TypedExpr::Int { value, .. } => IntermediateValue::Int { value },
|
||||
TypedExpr::Float { value, .. } => IntermediateValue::Float { value },
|
||||
TypedExpr::Var { name, .. } => IntermediateValue::Var { name },
|
||||
TypedExpr::BinOp { lhs, op, rhs, .. } => {
|
||||
let lhs = Self::from_typed_expr_inner(*lhs, exprs, temp_var_context);
|
||||
let rhs = Self::from_typed_expr_inner(*rhs, exprs, temp_var_context);
|
||||
|
||||
if let IntermediateValue::Var { name } = lhs {
|
||||
temp_var_context.free_temp_var(name)
|
||||
}
|
||||
|
||||
if let IntermediateValue::Var { name } = rhs {
|
||||
temp_var_context.free_temp_var(name)
|
||||
}
|
||||
|
||||
let result = temp_var_context.get_temp_var(ty);
|
||||
|
||||
exprs.push(IntermediateExpr::BinOp {
|
||||
result,
|
||||
lhs,
|
||||
op,
|
||||
rhs,
|
||||
});
|
||||
|
||||
IntermediateValue::Var { name: result }
|
||||
},
|
||||
TypedExpr::IntToFloat { value, .. } => {
|
||||
let value = Self::from_typed_expr_inner(*value, exprs, temp_var_context);
|
||||
|
||||
if let IntermediateValue::Var { name: value } = value {
|
||||
temp_var_context.free_temp_var(value)
|
||||
}
|
||||
|
||||
let result = temp_var_context.get_temp_var(ty);
|
||||
|
||||
exprs.push(IntermediateExpr::IntToFloat { result, value });
|
||||
|
||||
IntermediateValue::Var { name: result }
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
2
crates/compiler/src/representation/mod.rs
Normal file
2
crates/compiler/src/representation/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod intermediate;
|
||||
mod util;
|
||||
71
crates/compiler/src/representation/util.rs
Normal file
71
crates/compiler/src/representation/util.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
use crate::ast::typed::Type;
|
||||
use crate::symbols::{Symbol, SymbolsTable};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TempVarContext<'t> {
|
||||
counter: usize,
|
||||
floats: Vec<Symbol>,
|
||||
ints: Vec<Symbol>,
|
||||
reuse_symbols: bool,
|
||||
symbols: &'t mut SymbolsTable,
|
||||
}
|
||||
|
||||
impl<'t> TempVarContext<'t> {
|
||||
pub fn new(reuse_symbols: bool, symbols: &'t mut SymbolsTable) -> Self {
|
||||
Self {
|
||||
counter: 0,
|
||||
floats: Vec::new(),
|
||||
ints: Vec::new(),
|
||||
reuse_symbols,
|
||||
symbols,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_temp_var(&mut self, ty: Type) -> Symbol {
|
||||
if self.reuse_symbols {
|
||||
match ty {
|
||||
Type::Int if !self.ints.is_empty() => self.ints.pop().unwrap(),
|
||||
Type::Float if !self.floats.is_empty() => self.floats.pop().unwrap(),
|
||||
_ => self.allocate_temp_var(ty),
|
||||
}
|
||||
} else {
|
||||
self.allocate_temp_var(ty)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn symbols(&self) -> &SymbolsTable {
|
||||
self.symbols
|
||||
}
|
||||
|
||||
fn allocate_temp_var(&mut self, ty: Type) -> Symbol {
|
||||
let result = self.symbols.add(format!("#T{}", self.counter));
|
||||
{
|
||||
let data = self.symbols.resolve_mut(&result).unwrap();
|
||||
data.ty = Some(ty);
|
||||
data.temporary = true;
|
||||
}
|
||||
self.counter += 1;
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn free_temp_var(&mut self, name: Symbol) {
|
||||
let Some(data) = self.symbols.resolve(&name) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !data.temporary {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(ty) = data.ty else {
|
||||
return;
|
||||
};
|
||||
|
||||
if ty == Type::Int {
|
||||
self.ints.push(name);
|
||||
} else {
|
||||
self.floats.push(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
217
crates/compiler/src/symbols/mod.rs
Normal file
217
crates/compiler/src/symbols/mod.rs
Normal file
@@ -0,0 +1,217 @@
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::{HashMap, hash_map};
|
||||
use std::fmt::Display;
|
||||
use std::io;
|
||||
|
||||
use byteorder::{ReadBytesExt, WriteBytesExt};
|
||||
use integer_encoding::{VarIntReader, VarIntWriter};
|
||||
|
||||
use crate::ast::typed::Type;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Symbol(u64);
|
||||
|
||||
impl Symbol {
|
||||
pub fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
|
||||
_ = writer.write_varint::<u64>(self.0)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read<R: io::Read>(reader: &mut R) -> io::Result<Self> {
|
||||
Ok(Self(reader.read_varint::<u64>()?))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Symbol> for u64 {
|
||||
fn from(value: &Symbol) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for Symbol {
|
||||
fn from(value: u64) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SymbolData {
|
||||
pub id: Symbol,
|
||||
pub name: String,
|
||||
pub ty: Option<Type>,
|
||||
pub temporary: bool,
|
||||
}
|
||||
|
||||
impl SymbolData {
|
||||
fn read(reader: &mut impl io::Read) -> io::Result<Self> {
|
||||
let id = Symbol::read(reader)?;
|
||||
let name = {
|
||||
let n = reader.read_varint::<u64>()? as usize;
|
||||
let mut buf = vec![0; n];
|
||||
reader.read_exact(&mut buf)?;
|
||||
String::from_utf8(buf)
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "invalid utf-8"))?
|
||||
};
|
||||
|
||||
let ty = {
|
||||
let b = reader.read_u8()?;
|
||||
Type::try_from(b).ok()
|
||||
};
|
||||
|
||||
let temporary = reader.read_u8()? != 0;
|
||||
|
||||
Ok(Self {
|
||||
id,
|
||||
name,
|
||||
ty,
|
||||
temporary,
|
||||
})
|
||||
}
|
||||
|
||||
fn write(&self, writer: &mut impl io::Write) -> io::Result<()> {
|
||||
self.id.write(writer)?;
|
||||
writer.write_varint::<u64>(self.name.len() as u64)?;
|
||||
writer.write_all(self.name.as_bytes())?;
|
||||
writer.write_u8(self.ty.map_or(0, |ty| ty.into()))?;
|
||||
writer.write_u8(self.temporary as u8)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SymbolData {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut builder = f.debug_struct("symbol");
|
||||
builder.field("id", &self.id.0);
|
||||
|
||||
if let Some(ty) = self.ty {
|
||||
builder.field("type", &ty);
|
||||
}
|
||||
|
||||
if self.temporary {
|
||||
builder.field("temporary", &self.temporary);
|
||||
}
|
||||
|
||||
builder.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Symbol {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SymbolsTable {
|
||||
symbols: HashMap<String, SymbolData>,
|
||||
next_id: u64,
|
||||
}
|
||||
|
||||
impl SymbolsTable {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
symbols: HashMap::new(),
|
||||
next_id: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(reader: &mut impl io::Read) -> io::Result<Self> {
|
||||
let n = reader.read_varint::<u64>()?;
|
||||
|
||||
let mut max_id = 0;
|
||||
let mut symbols = Vec::with_capacity(n as usize);
|
||||
for _ in 0..n {
|
||||
let data = SymbolData::read(reader)?;
|
||||
max_id = max_id.max(data.id.0);
|
||||
symbols.push(data);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
symbols: symbols
|
||||
.into_iter()
|
||||
.map(|data| (data.name.clone(), data))
|
||||
.collect(),
|
||||
next_id: max_id + 1,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn write(&self, writer: &mut impl io::Write) -> io::Result<()> {
|
||||
writer.write_varint::<u64>(self.symbols.len() as u64)?;
|
||||
|
||||
for data in self.symbols.values() {
|
||||
data.write(writer)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn retain(&mut self, symbols: &[Symbol]) {
|
||||
self.symbols.retain(|_, data| symbols.contains(&data.id));
|
||||
}
|
||||
|
||||
pub fn add(&mut self, name: impl Into<String>) -> Symbol {
|
||||
let name = name.into();
|
||||
|
||||
match self.symbols.entry(name.clone()) {
|
||||
Entry::Occupied(e) => e.get().id,
|
||||
Entry::Vacant(e) => {
|
||||
let id = self.next_id.into();
|
||||
e.insert(SymbolData {
|
||||
id,
|
||||
name,
|
||||
ty: None,
|
||||
temporary: false,
|
||||
});
|
||||
self.next_id += 1;
|
||||
|
||||
id
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, name: &str) -> Option<Symbol> {
|
||||
self.symbols.get(name).map(|data| data.id)
|
||||
}
|
||||
|
||||
pub fn resolve(&self, symbol: &Symbol) -> Option<&SymbolData> {
|
||||
self.symbols.values().find(|data| &data.id == symbol)
|
||||
}
|
||||
|
||||
pub fn resolve_mut(&mut self, symbol: &Symbol) -> Option<&mut SymbolData> {
|
||||
self.symbols.values_mut().find(|data| &data.id == symbol)
|
||||
}
|
||||
|
||||
pub fn type_of(&self, symbol: &Symbol) -> Option<Type> {
|
||||
self.resolve(symbol).and_then(|data| data.ty)
|
||||
}
|
||||
|
||||
pub fn is_temporary(&self, symbol: &Symbol) -> Option<bool> {
|
||||
self.resolve(symbol).map(|data| data.temporary)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SymbolsTable {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for SymbolsTable {
|
||||
type Item = (String, SymbolData);
|
||||
type IntoIter = hash_map::IntoIter<String, SymbolData>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.symbols.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a SymbolsTable {
|
||||
type Item = (&'a String, &'a SymbolData);
|
||||
type IntoIter = hash_map::Iter<'a, String, SymbolData>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.symbols.iter()
|
||||
}
|
||||
}
|
||||
192
crates/compiler/src/util.rs
Normal file
192
crates/compiler/src/util.rs
Normal file
@@ -0,0 +1,192 @@
|
||||
use std::collections::HashSet;
|
||||
use std::io;
|
||||
|
||||
use crate::ast::UntypedExpr;
|
||||
use crate::ast::typed::TypedExpr;
|
||||
use crate::representation::intermediate::{IntermediateExpr, IntermediateValue};
|
||||
use crate::symbols::{Symbol, SymbolsTable};
|
||||
|
||||
pub fn collect_used_symbols(expr: &[IntermediateExpr]) -> Vec<Symbol> {
|
||||
let mut used_symbols = HashSet::new();
|
||||
|
||||
for expr in expr {
|
||||
match expr {
|
||||
IntermediateExpr::IntToFloat { result, value } => {
|
||||
used_symbols.insert(*result);
|
||||
|
||||
if let IntermediateValue::Var { name } = value {
|
||||
used_symbols.insert(*name);
|
||||
}
|
||||
},
|
||||
IntermediateExpr::BinOp {
|
||||
result, lhs, rhs, ..
|
||||
} => {
|
||||
used_symbols.insert(*result);
|
||||
|
||||
if let IntermediateValue::Var { name } = lhs {
|
||||
used_symbols.insert(*name);
|
||||
}
|
||||
|
||||
if let IntermediateValue::Var { name } = rhs {
|
||||
used_symbols.insert(*name);
|
||||
}
|
||||
},
|
||||
IntermediateExpr::Identity { result, value } => {
|
||||
used_symbols.insert(*result);
|
||||
|
||||
if let IntermediateValue::Var { name } = value {
|
||||
used_symbols.insert(*name);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
used_symbols.into_iter().collect()
|
||||
}
|
||||
|
||||
pub fn print_intermediate_exprs(
|
||||
exprs: &[IntermediateExpr],
|
||||
writer: &mut impl io::Write,
|
||||
) -> io::Result<()> {
|
||||
for expr in exprs {
|
||||
writeln!(writer, "{}", expr)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn print_postfix_expr(expr: &TypedExpr, writer: &mut impl io::Write) -> io::Result<()> {
|
||||
match expr {
|
||||
TypedExpr::Int { value, .. } => write!(writer, "{} ", value)?,
|
||||
TypedExpr::Float { value, .. } => {
|
||||
if value == &value.trunc() {
|
||||
write!(writer, "{}.0 ", value)?
|
||||
} else {
|
||||
write!(writer, "{} ", value)?
|
||||
}
|
||||
},
|
||||
TypedExpr::Var { name, .. } => write!(writer, "<id,{}> ", name)?,
|
||||
TypedExpr::BinOp { lhs, op, rhs, .. } => {
|
||||
print_postfix_expr(lhs, writer)?;
|
||||
print_postfix_expr(rhs, writer)?;
|
||||
write!(writer, "{} ", op)?;
|
||||
},
|
||||
TypedExpr::IntToFloat { value, .. } => {
|
||||
print_postfix_expr(value, writer)?;
|
||||
write!(writer, "i2f ")?;
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_typed_expr(
|
||||
expr: &TypedExpr,
|
||||
symbols: &SymbolsTable,
|
||||
writer: &mut impl io::Write,
|
||||
prefix: &str,
|
||||
is_last: bool,
|
||||
) -> io::Result<()> {
|
||||
let branch = if is_last { "└──" } else { "├──" };
|
||||
|
||||
write!(writer, "{}{}", prefix, branch)?;
|
||||
|
||||
match expr {
|
||||
TypedExpr::Int { value, .. } => writeln!(writer, "<{}>", value)?,
|
||||
TypedExpr::Float { value, .. } => {
|
||||
if value == &value.trunc() {
|
||||
writeln!(writer, "<{}.0>", value)?
|
||||
} else {
|
||||
writeln!(writer, "<{}>", value)?
|
||||
};
|
||||
},
|
||||
TypedExpr::Var { name, .. } => {
|
||||
let ty = symbols.resolve(name).and_then(|data| data.ty);
|
||||
write!(writer, "<id,{}", name)?;
|
||||
if let Some(ty) = ty {
|
||||
write!(writer, ",{}", ty)?
|
||||
};
|
||||
writeln!(writer, ">")?;
|
||||
},
|
||||
TypedExpr::BinOp { lhs, op, rhs, .. } => {
|
||||
writeln!(writer, "<{}>", op)?;
|
||||
|
||||
let new_prefix = if is_last {
|
||||
format!("{} ", prefix)
|
||||
} else {
|
||||
format!("{}│ ", prefix)
|
||||
};
|
||||
|
||||
write_typed_expr(lhs, symbols, writer, &new_prefix, false)?;
|
||||
write_typed_expr(rhs, symbols, writer, &new_prefix, true)?;
|
||||
},
|
||||
TypedExpr::IntToFloat { value, .. } => {
|
||||
writeln!(writer, "i2f")?;
|
||||
|
||||
let new_prefix = if is_last {
|
||||
format!("{} ", prefix)
|
||||
} else {
|
||||
format!("{}│ ", prefix)
|
||||
};
|
||||
|
||||
write_typed_expr(value, symbols, writer, &new_prefix, true)?;
|
||||
},
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn print_typed_expr(
|
||||
expr: &TypedExpr,
|
||||
symbols: &SymbolsTable,
|
||||
writer: &mut impl io::Write,
|
||||
) -> io::Result<()> {
|
||||
write_typed_expr(expr, symbols, writer, "", true)
|
||||
}
|
||||
|
||||
fn write_untyped_expr(
|
||||
expr: &UntypedExpr,
|
||||
writer: &mut impl io::Write,
|
||||
prefix: &str,
|
||||
is_last: bool,
|
||||
) -> io::Result<()> {
|
||||
let branch = if is_last { "└──" } else { "├──" };
|
||||
|
||||
write!(writer, "{}{}", prefix, branch)?;
|
||||
|
||||
match expr {
|
||||
UntypedExpr::Int { value, .. } => writeln!(writer, "<{}>", value),
|
||||
UntypedExpr::Float { value, .. } => {
|
||||
if value == &value.trunc() {
|
||||
writeln!(writer, "<{}.0>", value)
|
||||
} else {
|
||||
writeln!(writer, "<{}>", value)
|
||||
}
|
||||
},
|
||||
UntypedExpr::Var {
|
||||
name: id, typename, ..
|
||||
} => {
|
||||
write!(writer, "<id,{}", id)?;
|
||||
if let Some(typename) = typename {
|
||||
write!(writer, ",{}", typename)?;
|
||||
}
|
||||
writeln!(writer, ">")
|
||||
},
|
||||
UntypedExpr::BinOp { lhs, op, rhs, .. } => {
|
||||
writeln!(writer, "<{}>", op)?;
|
||||
|
||||
let new_prefix = if is_last {
|
||||
format!("{} ", prefix)
|
||||
} else {
|
||||
format!("{}│ ", prefix)
|
||||
};
|
||||
|
||||
write_untyped_expr(lhs, writer, &new_prefix, false)?;
|
||||
write_untyped_expr(rhs, writer, &new_prefix, true)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_untyped_expr(expr: &UntypedExpr, writer: &mut impl io::Write) -> io::Result<()> {
|
||||
write_untyped_expr(expr, writer, "", true)
|
||||
}
|
||||
14
crates/interpreter/Cargo.toml
Normal file
14
crates/interpreter/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "interpreter"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
compiler = { path = "../compiler" }
|
||||
anyhow = { workspace = true }
|
||||
byteorder = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
inquire = "0.7.5"
|
||||
integer-encoding = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
46
crates/interpreter/src/cli.rs
Normal file
46
crates/interpreter/src/cli.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use std::ops::Deref;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::{CommandFactory, Parser};
|
||||
|
||||
pub struct Args {
|
||||
inner: ArgsInner,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
pub struct ArgsInner {
|
||||
pub input: PathBuf,
|
||||
}
|
||||
|
||||
impl Args {
|
||||
pub fn parse() -> Self {
|
||||
let inner = match validate_inner(ArgsInner::parse()) {
|
||||
Ok(args) => args,
|
||||
Err(err) => {
|
||||
let mut command = ArgsInner::command();
|
||||
err.format(&mut command).exit();
|
||||
},
|
||||
};
|
||||
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Args {
|
||||
type Target = ArgsInner;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_inner(args: ArgsInner) -> Result<ArgsInner, clap::Error> {
|
||||
if args.input.is_file() {
|
||||
Ok(args)
|
||||
} else {
|
||||
Err(clap::Error::raw(
|
||||
clap::error::ErrorKind::InvalidValue,
|
||||
format!("Input file '{}' does not exist", args.input.display()),
|
||||
))
|
||||
}
|
||||
}
|
||||
143
crates/interpreter/src/interpreter/mod.rs
Normal file
143
crates/interpreter/src/interpreter/mod.rs
Normal file
@@ -0,0 +1,143 @@
|
||||
mod value;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Context;
|
||||
use compiler::ast::BinOp;
|
||||
use compiler::ast::typed::Type;
|
||||
use compiler::representation::intermediate::{IntermediateExpr, IntermediateValue};
|
||||
use compiler::symbols::{Symbol, SymbolsTable};
|
||||
pub use value::Value;
|
||||
|
||||
pub struct Interpreter<I> {
|
||||
exprs: Vec<IntermediateExpr>,
|
||||
symbols: SymbolsTable,
|
||||
initializer: I,
|
||||
}
|
||||
|
||||
type Variables = HashMap<Symbol, Value>;
|
||||
|
||||
impl<I> Interpreter<I>
|
||||
where
|
||||
I: Fn(&Type, &str) -> Value,
|
||||
{
|
||||
pub fn new(exprs: Vec<IntermediateExpr>, symbols: SymbolsTable, initializer: I) -> Self {
|
||||
Self {
|
||||
symbols,
|
||||
exprs,
|
||||
initializer,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> anyhow::Result<Option<Value>> {
|
||||
let mut variables = self.initialize_variables()?;
|
||||
let mut last_value = None;
|
||||
|
||||
for expr in &self.exprs {
|
||||
let (result, value) = match expr {
|
||||
IntermediateExpr::IntToFloat { result, value } => {
|
||||
let value = Self::intermediate_value(&variables, value)?;
|
||||
(result, self.int2float(value))
|
||||
},
|
||||
IntermediateExpr::BinOp {
|
||||
result,
|
||||
lhs,
|
||||
op,
|
||||
rhs,
|
||||
} => {
|
||||
let lhs = Self::intermediate_value(&variables, lhs)?;
|
||||
let rhs = Self::intermediate_value(&variables, rhs)?;
|
||||
(result, self.bin_op(lhs, op, rhs)?)
|
||||
},
|
||||
IntermediateExpr::Identity { result, value } => {
|
||||
let value = Self::intermediate_value(&variables, value)?;
|
||||
(result, self.identity(value))
|
||||
},
|
||||
};
|
||||
|
||||
Self::store(&mut variables, result, value)?;
|
||||
last_value = Some(value);
|
||||
}
|
||||
|
||||
Ok(last_value)
|
||||
}
|
||||
|
||||
fn initialize_variables(&self) -> anyhow::Result<Variables> {
|
||||
let mut variables = Variables::new();
|
||||
for (_, data) in &self.symbols {
|
||||
if data.ty.is_none() {
|
||||
anyhow::bail!("type not found for '{}'", data.name);
|
||||
}
|
||||
|
||||
if !data.temporary {
|
||||
variables.insert(data.id, (self.initializer)(&data.ty.unwrap(), &data.name));
|
||||
} else {
|
||||
variables.insert(data.id, match data.ty {
|
||||
None => continue,
|
||||
Some(Type::Int) => Value::Int(0),
|
||||
Some(Type::Float) => Value::Float(0.0),
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
Ok(variables)
|
||||
}
|
||||
|
||||
fn int2float(&self, value: Value) -> Value {
|
||||
match value {
|
||||
Value::Int(v) => Value::Float(v as f64),
|
||||
_ => value,
|
||||
}
|
||||
}
|
||||
|
||||
fn identity(&self, value: Value) -> Value {
|
||||
value
|
||||
}
|
||||
|
||||
fn bin_op(&self, lhs: Value, op: &BinOp, rhs: Value) -> anyhow::Result<Value> {
|
||||
match (lhs, rhs) {
|
||||
(Value::Int(lhs), Value::Int(rhs)) => Ok(Value::Int(match op {
|
||||
BinOp::Add => lhs + rhs,
|
||||
BinOp::Sub => lhs - rhs,
|
||||
BinOp::Mul => lhs * rhs,
|
||||
BinOp::Div => lhs / rhs,
|
||||
})),
|
||||
(Value::Float(lhs), Value::Float(rhs)) => Ok(Value::Float(match op {
|
||||
BinOp::Add => lhs + rhs,
|
||||
BinOp::Sub => lhs - rhs,
|
||||
BinOp::Mul => lhs * rhs,
|
||||
BinOp::Div => lhs / rhs,
|
||||
})),
|
||||
_ => Err(anyhow::anyhow!("invalid types for binary operation")),
|
||||
}
|
||||
}
|
||||
|
||||
fn intermediate_value(
|
||||
variables: &Variables,
|
||||
value: &IntermediateValue,
|
||||
) -> anyhow::Result<Value> {
|
||||
Ok(match value {
|
||||
IntermediateValue::Int { value } => Value::Int(*value),
|
||||
IntermediateValue::Float { value } => Value::Float(*value),
|
||||
IntermediateValue::Var { name } => Self::load(variables, name)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn load(variables: &Variables, symbol: &Symbol) -> anyhow::Result<Value> {
|
||||
let symbol_value = variables.get(symbol).context("no such variable")?;
|
||||
|
||||
Ok(*symbol_value)
|
||||
}
|
||||
|
||||
fn store(variables: &mut Variables, symbol: &Symbol, value: Value) -> anyhow::Result<()> {
|
||||
let symbol_value = variables.get_mut(symbol).context("no such variable")?;
|
||||
|
||||
match (symbol_value, value) {
|
||||
(Value::Int(v), Value::Int(a)) => *v = a,
|
||||
(Value::Float(v), Value::Float(a)) => *v = a,
|
||||
_ => return Err(anyhow::anyhow!("invalid types for assignment")),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
34
crates/interpreter/src/interpreter/value.rs
Normal file
34
crates/interpreter/src/interpreter/value.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Value {
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
}
|
||||
|
||||
impl From<i64> for Value {
|
||||
fn from(value: i64) -> Self {
|
||||
Self::Int(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for Value {
|
||||
fn from(value: f64) -> Self {
|
||||
Self::Float(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Value {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Value::Int(v) => write!(f, "{}", v),
|
||||
Value::Float(v) => {
|
||||
if v == &v.trunc() {
|
||||
write!(f, "{}.0", v)
|
||||
} else {
|
||||
write!(f, "{}", v)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
1
crates/interpreter/src/lib.rs
Normal file
1
crates/interpreter/src/lib.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod interpreter;
|
||||
61
crates/interpreter/src/main.rs
Normal file
61
crates/interpreter/src/main.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
use std::path::Path;
|
||||
use std::{fs, io};
|
||||
|
||||
use compiler::ast::typed::Type;
|
||||
use compiler::representation::intermediate::IntermediateExpr;
|
||||
use compiler::symbols::SymbolsTable;
|
||||
use inquire::CustomType;
|
||||
use integer_encoding::VarIntReader;
|
||||
use interpreter::interpreter::{Interpreter, Value};
|
||||
|
||||
mod cli;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let args = cli::Args::parse();
|
||||
|
||||
let result = int_command(&args.input);
|
||||
|
||||
if let Err(e) = result {
|
||||
eprintln!("error: {}", e);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn int_command(input: &Path) -> Result<(), anyhow::Error> {
|
||||
let (symbols, ir) = {
|
||||
let mut reader = io::BufReader::new(fs::File::open(input)?);
|
||||
let symbols = SymbolsTable::read(&mut reader)?;
|
||||
let n = reader.read_varint::<u64>()?;
|
||||
let mut ir = Vec::with_capacity(n as usize);
|
||||
for _ in 0..n {
|
||||
ir.push(IntermediateExpr::read(&mut reader)?);
|
||||
}
|
||||
(symbols, ir)
|
||||
};
|
||||
|
||||
let mut interpreter = Interpreter::new(ir, symbols, |ty, name| match ty {
|
||||
Type::Int => Value::Int(
|
||||
CustomType::new(&format!("Input int {}", name))
|
||||
.with_default(0)
|
||||
.prompt()
|
||||
.unwrap_or_default(),
|
||||
),
|
||||
Type::Float => Value::Float(
|
||||
CustomType::new(&format!("Input float {}", name))
|
||||
.with_default(0.0)
|
||||
.prompt()
|
||||
.unwrap_or_default(),
|
||||
),
|
||||
});
|
||||
|
||||
let result = interpreter.run()?;
|
||||
|
||||
if let Some(result) = result {
|
||||
println!("Result: {}", result);
|
||||
} else {
|
||||
println!("No result");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user