1
0

separate interpreter and compiler

This commit is contained in:
2024-12-02 13:57:32 +03:00
parent 8030670a51
commit e5b63d5e2d
27 changed files with 214 additions and 92 deletions

View 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 }

View 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("/"),
}
}
}
}

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

View 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),
})
}
}

View 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
View 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(())
}

View 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),
}
}
}

View 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
View 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(())
}

View 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),
}
}
}

View File

@@ -0,0 +1,3 @@
pub mod lexer;
pub mod parser;
pub mod token;

View 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())
},
}
}
}

View 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>"),
}
}
}

View 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 }
},
}
}
}

View File

@@ -0,0 +1,2 @@
pub mod intermediate;
mod util;

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

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

View 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 }

View 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()),
))
}
}

View 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(())
}
}

View 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)
}
},
}
}
}

View File

@@ -0,0 +1 @@
pub mod interpreter;

View 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(())
}