2022-02-06 22:19:55 +00:00
|
|
|
use std::cell::Cell;
|
2022-01-05 01:08:25 +00:00
|
|
|
use std::collections::BTreeMap;
|
2022-02-15 22:56:52 +00:00
|
|
|
use std::fmt::Write;
|
2022-01-05 01:08:25 +00:00
|
|
|
|
2022-02-06 22:19:55 +00:00
|
|
|
use super::parser;
|
2022-01-04 22:05:25 +00:00
|
|
|
use crate::rtlil;
|
2022-02-16 21:17:25 +00:00
|
|
|
pub use callable::{Callable, CallableContext, CallableId};
|
2022-02-15 20:32:55 +00:00
|
|
|
pub use types::{Type, TypeStruct, TypingContext};
|
2022-01-23 20:04:19 +00:00
|
|
|
|
|
|
|
mod callable;
|
2022-02-06 22:19:55 +00:00
|
|
|
#[cfg(never)]
|
|
|
|
pub mod lowering;
|
2022-01-30 22:54:19 +00:00
|
|
|
pub mod typed_ir;
|
2022-02-01 18:46:06 +00:00
|
|
|
pub mod types;
|
2022-01-04 22:05:25 +00:00
|
|
|
|
2022-02-15 20:32:55 +00:00
|
|
|
#[cfg(never)]
|
2022-02-06 22:19:55 +00:00
|
|
|
use crate::builtin_cells::get_builtins;
|
|
|
|
|
|
|
|
// pub use lowering::lower_module;
|
|
|
|
|
2022-01-17 14:37:07 +00:00
|
|
|
/// lots of code is still not width-aware, this constant keeps track of that
|
|
|
|
const TODO_WIDTH: u32 = 1;
|
|
|
|
|
2022-01-04 22:05:25 +00:00
|
|
|
fn make_pubid(id: &str) -> String {
|
|
|
|
"\\".to_owned() + id
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
2022-01-04 23:13:56 +00:00
|
|
|
pub enum CompileErrorKind {
|
2022-01-05 01:09:08 +00:00
|
|
|
UndefinedReference(String),
|
2022-01-14 14:32:00 +00:00
|
|
|
BadArgCount { received: usize, expected: usize },
|
2022-02-06 22:19:55 +00:00
|
|
|
TodoError(String),
|
2022-02-15 20:32:55 +00:00
|
|
|
TypeError { expected: Type, found: Type },
|
2022-01-04 23:13:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct CompileError {
|
|
|
|
kind: CompileErrorKind,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl CompileError {
|
|
|
|
fn new(kind: CompileErrorKind) -> Self {
|
2022-01-05 01:09:08 +00:00
|
|
|
Self { kind }
|
2022-01-04 23:13:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-17 20:02:11 +00:00
|
|
|
/// A user-defined signal
|
2022-02-15 20:32:55 +00:00
|
|
|
pub struct Signal {
|
2022-01-17 20:02:11 +00:00
|
|
|
/// the user-visible name of the signal
|
|
|
|
pub name: String,
|
|
|
|
/// the id of the signal in RTLIL
|
2022-01-17 20:04:22 +00:00
|
|
|
pub il_id: String,
|
2022-01-17 20:02:11 +00:00
|
|
|
/// the type of the signal
|
2022-02-15 20:32:55 +00:00
|
|
|
pub typ: Type,
|
2022-01-17 20:02:11 +00:00
|
|
|
// unique ID of the signal
|
|
|
|
// pub uid: u64,
|
|
|
|
}
|
|
|
|
|
2022-02-15 20:32:55 +00:00
|
|
|
impl Signal {
|
2022-01-17 20:04:22 +00:00
|
|
|
fn sigspec(&self) -> rtlil::SigSpec {
|
|
|
|
rtlil::SigSpec::Wire(self.il_id.to_owned())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-15 20:32:55 +00:00
|
|
|
pub struct Context {
|
2022-01-17 20:02:11 +00:00
|
|
|
/// map callable name to callable
|
2022-02-16 16:38:56 +00:00
|
|
|
callable_names: BTreeMap<String, CallableId>,
|
2022-02-16 21:17:25 +00:00
|
|
|
callables: CallableContext,
|
2022-02-15 20:32:55 +00:00
|
|
|
/// type names
|
|
|
|
typenames: BTreeMap<String, Type>,
|
|
|
|
types: TypingContext,
|
2022-01-17 20:02:11 +00:00
|
|
|
/// map signal name to Signal
|
2022-02-15 20:32:55 +00:00
|
|
|
signals: BTreeMap<String, typed_ir::Signal>,
|
2022-02-06 22:19:55 +00:00
|
|
|
/// incrementing counter for unique IDs
|
|
|
|
ids: Counter,
|
|
|
|
}
|
|
|
|
|
|
|
|
struct Counter(Cell<usize>);
|
|
|
|
|
|
|
|
impl Counter {
|
|
|
|
fn new() -> Counter {
|
|
|
|
Counter(Cell::new(0))
|
|
|
|
}
|
|
|
|
fn next(&self) -> usize {
|
|
|
|
let next = self.0.get() + 1;
|
|
|
|
self.0.set(next);
|
|
|
|
next
|
|
|
|
}
|
2022-01-04 23:13:56 +00:00
|
|
|
}
|
2022-01-04 22:05:25 +00:00
|
|
|
|
2022-02-15 20:32:55 +00:00
|
|
|
impl Context {
|
2022-02-06 22:19:55 +00:00
|
|
|
pub fn new() -> Self {
|
2022-02-16 21:17:25 +00:00
|
|
|
let mut tcx = TypingContext::new();
|
|
|
|
let ccx = CallableContext::new(&mut tcx);
|
|
|
|
|
|
|
|
let typenames = [
|
|
|
|
("Logic".to_string(), tcx.primitives.logic),
|
|
|
|
("Num".to_string(), tcx.primitives.elabnum),
|
|
|
|
]
|
|
|
|
.into();
|
|
|
|
|
|
|
|
let callable_names = [("reduce_or".to_string(), ccx.builtins.reduce_or)].into();
|
|
|
|
|
2022-02-06 22:19:55 +00:00
|
|
|
Context {
|
2022-02-16 21:17:25 +00:00
|
|
|
callables: ccx,
|
|
|
|
callable_names,
|
2022-02-06 22:19:55 +00:00
|
|
|
signals: BTreeMap::new(),
|
2022-02-16 21:17:25 +00:00
|
|
|
types: tcx,
|
|
|
|
typenames,
|
2022-02-06 22:19:55 +00:00
|
|
|
ids: Counter::new(),
|
|
|
|
}
|
|
|
|
}
|
2022-01-17 20:04:22 +00:00
|
|
|
|
2022-02-06 22:19:55 +00:00
|
|
|
fn try_get_signal(&self, signame: &str) -> Result<&typed_ir::Signal, CompileError> {
|
2022-02-15 20:32:55 +00:00
|
|
|
self.signals.get(signame).ok_or_else(|| {
|
2022-01-20 18:55:17 +00:00
|
|
|
CompileError::new(CompileErrorKind::UndefinedReference(signame.to_owned()))
|
|
|
|
})
|
2022-01-17 20:04:22 +00:00
|
|
|
}
|
2022-02-06 20:02:55 +00:00
|
|
|
|
2022-02-15 20:32:55 +00:00
|
|
|
fn try_get_type(&self, typename: &str) -> Result<Type, CompileError> {
|
2022-02-15 20:41:01 +00:00
|
|
|
self.typenames.get(typename).copied().ok_or_else(|| {
|
2022-02-06 20:02:55 +00:00
|
|
|
CompileError::new(CompileErrorKind::UndefinedReference(typename.to_owned()))
|
|
|
|
})
|
|
|
|
}
|
2022-01-17 20:04:22 +00:00
|
|
|
|
2022-02-16 16:38:56 +00:00
|
|
|
fn try_get_callable(&self, callname: &str) -> Result<CallableId, CompileError> {
|
|
|
|
self.callable_names.get(callname).copied().ok_or_else(|| {
|
2022-02-15 20:32:55 +00:00
|
|
|
CompileError::new(CompileErrorKind::UndefinedReference(callname.to_owned()))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-02-16 15:37:12 +00:00
|
|
|
fn eval_expression(&self, expr: &typed_ir::Expr) -> Result<types::ElabData, CompileError> {
|
|
|
|
match &expr.kind {
|
|
|
|
typed_ir::ExprKind::Literal(lit) => Ok(lit.clone()),
|
|
|
|
typed_ir::ExprKind::Path(_) => todo!("evaluate path"),
|
2022-02-20 16:49:28 +00:00
|
|
|
typed_ir::ExprKind::Call {
|
|
|
|
called,
|
|
|
|
args,
|
|
|
|
genargs,
|
|
|
|
} => todo!("evaluate call"),
|
2022-02-16 15:37:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-06 22:19:55 +00:00
|
|
|
fn type_expression(
|
|
|
|
&self,
|
|
|
|
expr: &parser::expression::Expression,
|
|
|
|
) -> Result<typed_ir::Expr, CompileError> {
|
2022-02-15 20:32:55 +00:00
|
|
|
use parser::expression::Expression;
|
|
|
|
let id = typed_ir::ExprId(self.ids.next() as u32);
|
|
|
|
let t_expr = match expr {
|
|
|
|
Expression::Path(name) => {
|
|
|
|
let signal = self.try_get_signal(name)?;
|
|
|
|
typed_ir::Expr {
|
|
|
|
id,
|
|
|
|
kind: typed_ir::ExprKind::Path(signal.id),
|
|
|
|
typ: signal.typ,
|
|
|
|
}
|
|
|
|
}
|
2022-02-16 15:37:12 +00:00
|
|
|
Expression::Literal(lit) => {
|
|
|
|
// TODO: make this a proper enum instead of having to match on everything
|
|
|
|
let data = match lit.kind() {
|
|
|
|
parser::tokens::TokenKind::Number => {
|
|
|
|
let num = lit.span().fragment().parse().unwrap();
|
|
|
|
self.types.make_elabnum_u32(num)
|
|
|
|
}
|
|
|
|
_ => unreachable!("non-literal token in literal?"),
|
|
|
|
};
|
|
|
|
typed_ir::Expr {
|
|
|
|
id,
|
|
|
|
kind: typed_ir::ExprKind::Literal(data),
|
|
|
|
typ: self.types.primitives.infer,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Expression::UnOp(op) => {
|
|
|
|
let a = self.type_expression(&op.a)?;
|
|
|
|
typed_ir::Expr {
|
|
|
|
id,
|
|
|
|
kind: typed_ir::ExprKind::Call {
|
2022-02-16 21:17:25 +00:00
|
|
|
called: self.callables.builtins.bitnot,
|
2022-02-16 15:37:12 +00:00
|
|
|
args: vec![a],
|
2022-02-20 16:49:28 +00:00
|
|
|
genargs: vec![],
|
2022-02-16 15:37:12 +00:00
|
|
|
},
|
|
|
|
typ: self.types.primitives.infer,
|
|
|
|
}
|
|
|
|
}
|
2022-02-15 20:32:55 +00:00
|
|
|
Expression::BinOp(op) => {
|
|
|
|
let (a, b) = (self.type_expression(&op.a)?, self.type_expression(&op.b)?);
|
|
|
|
typed_ir::Expr {
|
|
|
|
id,
|
|
|
|
kind: typed_ir::ExprKind::Call {
|
2022-02-16 21:17:25 +00:00
|
|
|
called: self.callables.builtins.xor,
|
2022-02-15 20:32:55 +00:00
|
|
|
args: vec![a, b],
|
2022-02-20 16:49:28 +00:00
|
|
|
genargs: vec![],
|
2022-02-15 20:32:55 +00:00
|
|
|
},
|
2022-02-15 22:56:52 +00:00
|
|
|
typ: self.types.primitives.infer,
|
2022-02-15 20:32:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Expression::Call(call) => {
|
|
|
|
let args_resolved = call
|
|
|
|
.args
|
|
|
|
.iter()
|
|
|
|
.map(|expr| self.type_expression(expr))
|
|
|
|
.collect::<Result<Vec<_>, _>>()?;
|
2022-02-16 21:17:25 +00:00
|
|
|
let called = self.try_get_callable(call.name.fragment())?;
|
2022-02-18 11:59:53 +00:00
|
|
|
let called_callable = self.callables.get(called);
|
|
|
|
if args_resolved.len() != called_callable.argcount() {
|
|
|
|
return Err(CompileError::new(CompileErrorKind::BadArgCount {
|
|
|
|
received: args_resolved.len(),
|
|
|
|
expected: called_callable.argcount(),
|
|
|
|
}));
|
|
|
|
}
|
2022-02-20 16:49:28 +00:00
|
|
|
let genargs_resolved = called_callable
|
|
|
|
.genargs
|
|
|
|
.iter()
|
|
|
|
.map(|genarg| genarg.1)
|
|
|
|
.collect();
|
2022-02-15 20:32:55 +00:00
|
|
|
typed_ir::Expr {
|
|
|
|
id,
|
|
|
|
kind: typed_ir::ExprKind::Call {
|
2022-02-16 21:17:25 +00:00
|
|
|
called,
|
2022-02-15 20:32:55 +00:00
|
|
|
args: args_resolved,
|
2022-02-20 16:49:28 +00:00
|
|
|
genargs: genargs_resolved,
|
2022-02-15 20:32:55 +00:00
|
|
|
},
|
2022-02-15 22:56:52 +00:00
|
|
|
typ: self.types.primitives.infer,
|
2022-02-15 20:32:55 +00:00
|
|
|
}
|
|
|
|
}
|
2022-02-06 22:19:55 +00:00
|
|
|
};
|
2022-02-15 20:32:55 +00:00
|
|
|
Ok(t_expr)
|
2022-01-17 18:20:51 +00:00
|
|
|
}
|
|
|
|
|
2022-02-06 22:19:55 +00:00
|
|
|
fn type_comb(
|
|
|
|
&mut self,
|
|
|
|
comb: &parser::comb::CombBlock,
|
|
|
|
) -> Result<typed_ir::Block, CompileError> {
|
|
|
|
let mut signals = Vec::new();
|
|
|
|
|
|
|
|
for port in comb.ports.iter() {
|
|
|
|
let sig_id = self.ids.next();
|
2022-02-16 15:37:12 +00:00
|
|
|
|
2022-02-06 22:19:55 +00:00
|
|
|
let sig_typename = &port.net.typ;
|
2022-02-16 15:37:12 +00:00
|
|
|
let mut sig_type = self.try_get_type(sig_typename.name.fragment())?;
|
|
|
|
if let Some(arg) = &sig_typename.generics {
|
|
|
|
let elab_expr = self.type_expression(arg)?;
|
|
|
|
let elab_val = self.eval_expression(&elab_expr)?;
|
|
|
|
sig_type = self
|
|
|
|
.types
|
|
|
|
.parameterize(sig_type, &[types::GenericArg::Elab(elab_val)])
|
|
|
|
.unwrap();
|
|
|
|
}
|
|
|
|
|
2022-02-06 22:19:55 +00:00
|
|
|
let sig = typed_ir::Signal {
|
2022-02-15 20:32:55 +00:00
|
|
|
id: typed_ir::DefId(sig_id as u32),
|
2022-02-06 22:19:55 +00:00
|
|
|
typ: sig_type,
|
|
|
|
};
|
2022-02-15 20:32:55 +00:00
|
|
|
signals.push(sig.clone());
|
2022-02-06 22:19:55 +00:00
|
|
|
self.signals.insert(port.net.name.to_string(), sig);
|
2022-01-20 18:55:17 +00:00
|
|
|
}
|
2022-01-05 01:09:08 +00:00
|
|
|
|
2022-02-06 22:19:55 +00:00
|
|
|
let ret_typename = &comb.ret.name;
|
2022-02-15 22:56:52 +00:00
|
|
|
let _ret_type = self.try_get_type(ret_typename.fragment())?;
|
|
|
|
// TODO: use ret type
|
2022-01-05 01:08:25 +00:00
|
|
|
|
2022-02-06 22:19:55 +00:00
|
|
|
let root_expr = self.type_expression(&comb.expr)?;
|
2022-01-05 01:38:56 +00:00
|
|
|
|
2022-02-06 22:19:55 +00:00
|
|
|
Ok(typed_ir::Block {
|
|
|
|
signals,
|
|
|
|
expr: root_expr,
|
|
|
|
})
|
2022-02-04 23:58:47 +00:00
|
|
|
}
|
2022-01-23 20:04:19 +00:00
|
|
|
|
2022-02-15 20:32:55 +00:00
|
|
|
pub fn type_module(&mut self, module: parser::Module) -> Result<typed_ir::Block, CompileError> {
|
2022-02-06 22:19:55 +00:00
|
|
|
for item in module.items {
|
|
|
|
let block = match &item {
|
|
|
|
parser::ModuleItem::Comb(comb) => self.type_comb(comb)?,
|
2022-02-15 20:44:10 +00:00
|
|
|
parser::ModuleItem::Proc(_) => todo!("proc block"),
|
|
|
|
parser::ModuleItem::State(_) => todo!("state block"),
|
2022-02-06 22:19:55 +00:00
|
|
|
};
|
|
|
|
return Ok(block);
|
2022-01-04 22:05:25 +00:00
|
|
|
}
|
2022-02-06 22:19:55 +00:00
|
|
|
Err(CompileError::new(CompileErrorKind::TodoError(
|
|
|
|
"no blocks in module".to_string(),
|
|
|
|
)))
|
2022-01-04 22:05:25 +00:00
|
|
|
}
|
2022-02-15 22:56:52 +00:00
|
|
|
|
2022-02-20 17:26:14 +00:00
|
|
|
pub fn infer_expr_types(&mut self, expr: &typed_ir::Expr) -> typed_ir::Expr {
|
2022-02-20 16:49:28 +00:00
|
|
|
if self.types.is_fully_typed(expr.typ) {
|
|
|
|
// there is nothing more to infer
|
|
|
|
return expr.clone();
|
|
|
|
}
|
|
|
|
match &expr.kind {
|
|
|
|
typed_ir::ExprKind::Literal(_) => todo!(),
|
|
|
|
typed_ir::ExprKind::Path(_) => todo!(),
|
|
|
|
typed_ir::ExprKind::Call {
|
|
|
|
called,
|
|
|
|
args,
|
|
|
|
genargs,
|
|
|
|
} => {
|
2022-02-20 17:26:14 +00:00
|
|
|
let args_typed: Vec<_> = args.iter().map(|ex| self.infer_expr_types(ex)).collect();
|
2022-02-20 16:49:28 +00:00
|
|
|
let callee_def = self.callables.get(*called);
|
2022-02-20 19:12:35 +00:00
|
|
|
|
|
|
|
let param_types: Vec<_> = callee_def.args.iter().map(|param| param.1).collect();
|
|
|
|
let inferred_args: Vec<_> = param_types
|
|
|
|
.iter()
|
|
|
|
.zip(&args_typed)
|
|
|
|
.map(|(param, arg)| self.types.infer_type(*param, arg.typ))
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
let mut genargs: Vec<_> = callee_def.genargs.iter().map(|a| a.1).collect();
|
|
|
|
|
|
|
|
let mut new_type = callee_def.ret_type;
|
|
|
|
|
|
|
|
if genargs.len() != 0 {
|
|
|
|
// need to infer generic arguments
|
|
|
|
for inf_res in inferred_args {
|
|
|
|
match inf_res {
|
|
|
|
types::InferenceResult::First(_) => todo!(),
|
|
|
|
types::InferenceResult::Second(_) => todo!(),
|
|
|
|
types::InferenceResult::TypeVar(dbi, tvar, typ) => {
|
|
|
|
assert_eq!(dbi, 0);
|
|
|
|
// TODO: type check argument instead of just using it
|
|
|
|
genargs[tvar as usize] = typ;
|
|
|
|
}
|
|
|
|
types::InferenceResult::Incompatible => todo!(),
|
|
|
|
types::InferenceResult::Ambigous => todo!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: HACKY HACKY HACK
|
|
|
|
new_type = genargs[0];
|
2022-02-20 16:49:28 +00:00
|
|
|
}
|
2022-02-20 19:12:35 +00:00
|
|
|
|
|
|
|
let mut new_expr = expr.clone();
|
|
|
|
new_expr.typ = new_type;
|
|
|
|
new_expr.kind = typed_ir::ExprKind::Call {
|
|
|
|
called: called.clone(),
|
|
|
|
args: args_typed,
|
|
|
|
genargs,
|
|
|
|
};
|
|
|
|
new_expr
|
2022-02-20 16:49:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-20 17:26:14 +00:00
|
|
|
pub fn infer_types(&mut self, mut block: typed_ir::Block) -> typed_ir::Block {
|
2022-02-20 16:49:28 +00:00
|
|
|
let new_root = self.infer_expr_types(&block.expr);
|
|
|
|
block.expr = new_root;
|
|
|
|
block
|
|
|
|
}
|
|
|
|
|
2022-02-15 22:56:52 +00:00
|
|
|
pub fn pretty_typed_block(
|
|
|
|
&self,
|
|
|
|
w: &mut dyn std::fmt::Write,
|
|
|
|
block: &typed_ir::Block,
|
|
|
|
) -> std::fmt::Result {
|
|
|
|
for sig in &block.signals {
|
|
|
|
let mut typ_pretty = String::new();
|
|
|
|
self.types.pretty_type(&mut typ_pretty, sig.typ)?;
|
2022-02-16 15:37:12 +00:00
|
|
|
writeln!(w, "sig_{}: {}", sig.id.0, typ_pretty)?
|
2022-02-15 22:56:52 +00:00
|
|
|
}
|
|
|
|
self.pretty_typed_expr(w, &block.expr)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn pretty_typed_expr(
|
|
|
|
&self,
|
|
|
|
w: &mut dyn std::fmt::Write,
|
|
|
|
expr: &typed_ir::Expr,
|
|
|
|
) -> std::fmt::Result {
|
|
|
|
let expr_pretty = match &expr.kind {
|
2022-02-16 15:37:12 +00:00
|
|
|
typed_ir::ExprKind::Literal(_) => todo!(),
|
|
|
|
typed_ir::ExprKind::Path(path) => format!("sig_{}", path.0),
|
2022-02-20 16:49:28 +00:00
|
|
|
typed_ir::ExprKind::Call {
|
|
|
|
called,
|
|
|
|
args,
|
|
|
|
genargs,
|
|
|
|
} => {
|
2022-02-15 22:56:52 +00:00
|
|
|
let args = args
|
|
|
|
.iter()
|
|
|
|
.map(|arg| {
|
|
|
|
self.pretty_typed_expr(w, arg)?;
|
|
|
|
Ok(format!("_{}", arg.id.0))
|
|
|
|
})
|
|
|
|
.collect::<Result<Vec<_>, std::fmt::Error>>()?;
|
2022-02-16 21:17:25 +00:00
|
|
|
let callable = self.callables.get(*called);
|
2022-02-20 16:49:28 +00:00
|
|
|
let genargs = genargs
|
|
|
|
.iter()
|
|
|
|
.map(|param| {
|
|
|
|
let mut type_str = String::new();
|
|
|
|
self.types.pretty_type(&mut type_str, *param)?;
|
|
|
|
Ok(type_str)
|
|
|
|
})
|
|
|
|
.collect::<Result<Vec<_>, std::fmt::Error>>()?;
|
|
|
|
format!(
|
|
|
|
"{}<{}>({})",
|
|
|
|
callable.name(),
|
|
|
|
genargs.join(", "),
|
|
|
|
args.join(", ")
|
|
|
|
)
|
2022-02-15 22:56:52 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
let mut type_pretty = String::new();
|
|
|
|
self.types.pretty_type(&mut type_pretty, expr.typ)?;
|
|
|
|
writeln!(w, "let _{}: {} = {}", expr.id.0, type_pretty, expr_pretty)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
2022-01-04 22:05:25 +00:00
|
|
|
}
|