use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use crate::machine::diag::Diagnostic;
use crate::machine::lexer::NumLit;
use crate::machine::rational::Rational;
use crate::machine::span::{Span, Spanned};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SourceUnit {
pub stmts: Vec<Stmt>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Stmt {
Machine(MachineDecl),
Param(ParamDecl),
Osc(OscDecl),
Space(SpaceDecl),
Object(ObjectDecl),
Map(MapStmt),
Wire(WireStmt),
Include(IncludeStmt),
Template(TemplateDecl),
Instance(InstanceStmt),
For(ForStmt),
}
impl Stmt {
pub fn span(&self) -> Span {
match self {
Stmt::Machine(s) => s.span,
Stmt::Param(s) => s.span,
Stmt::Osc(s) => s.span,
Stmt::Space(s) => s.span,
Stmt::Object(s) => s.span,
Stmt::Map(s) => s.span,
Stmt::Wire(s) => s.span,
Stmt::Include(s) => s.span,
Stmt::Template(s) => s.span,
Stmt::Instance(s) => s.span,
Stmt::For(s) => s.span,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MachineDecl {
pub name: Spanned<String>,
pub body: Vec<Stmt>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParamDecl {
pub name: Name,
pub default: Option<Expr>,
pub span: Span,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FreqUnit {
Hz,
KHz,
MHz,
GHz,
}
impl FreqUnit {
pub const fn scale(self) -> i128 {
match self {
FreqUnit::Hz => 1,
FreqUnit::KHz => 1_000,
FreqUnit::MHz => 1_000_000,
FreqUnit::GHz => 1_000_000_000,
}
}
pub const fn as_str(self) -> &'static str {
match self {
FreqUnit::Hz => "Hz",
FreqUnit::KHz => "kHz",
FreqUnit::MHz => "MHz",
FreqUnit::GHz => "GHz",
}
}
pub fn from_spelling(s: &str) -> Option<FreqUnit> {
Some(match s {
"Hz" => FreqUnit::Hz,
"kHz" | "KHz" => FreqUnit::KHz,
"MHz" => FreqUnit::MHz,
"GHz" => FreqUnit::GHz,
_ => return None,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OscDecl {
pub name: Name,
pub freq: Expr,
pub unit: Spanned<FreqUnit>,
pub span: Span,
}
impl OscDecl {
pub fn frequency_hz(&self) -> Result<Rational, Diagnostic> {
let base = self.freq.eval_rational()?;
let scale = Rational::new(self.unit.node.scale(), 1)
.ok_or_else(|| Diagnostic::new(self.unit.span, "frequency unit is out of range"))?;
base.checked_mul(scale)
.ok_or_else(|| Diagnostic::new(self.freq.span(), "frequency is out of range"))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SpaceDecl {
pub name: Name,
pub props: Vec<Property>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ObjectDecl {
pub name: Name,
pub class: Spanned<String>,
pub props: Vec<Property>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Property {
pub name: Name,
pub value: Expr,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MapStmt {
pub space: Name,
pub base: Expr,
pub size: Expr,
pub target: Expr,
pub props: Vec<Property>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WireStmt {
pub from: Path,
pub to: Path,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IncludeStmt {
pub path: Spanned<String>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TemplateDecl {
pub name: Name,
pub params: Vec<TemplateParam>,
pub body: Vec<Stmt>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TemplateParam {
pub name: Name,
pub default: Option<Expr>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InstanceStmt {
pub name: Name,
pub template: Name,
pub args: Vec<Arg>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Arg {
pub name: Option<Name>,
pub value: Expr,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ForStmt {
pub var: Name,
pub start: Expr,
pub end: Expr,
pub inclusive: bool,
pub body: Vec<Stmt>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Name {
pub parts: Vec<NamePart>,
pub span: Span,
}
impl Name {
pub fn as_literal(&self) -> Option<&str> {
match self.parts.as_slice() {
[NamePart::Literal(text)] => Some(text),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NamePart {
Literal(String),
Substitution(Expr),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Path {
pub segments: Vec<Name>,
pub span: Span,
}
impl Path {
pub fn as_literal(&self) -> Option<String> {
let mut out = String::new();
for (i, seg) in self.segments.iter().enumerate() {
if i > 0 {
out.push('.');
}
out.push_str(seg.as_literal()?);
}
Some(out)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnOp {
Neg,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BinOp {
Add,
Sub,
Mul,
Div,
Rem,
}
impl BinOp {
pub const fn as_str(self) -> &'static str {
match self {
BinOp::Add => "+",
BinOp::Sub => "-",
BinOp::Mul => "*",
BinOp::Div => "/",
BinOp::Rem => "%",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Expr {
Num(Spanned<NumLit>),
Str(Spanned<String>),
Bool(Spanned<bool>),
Path(Path),
Call {
callee: Path,
args: Vec<Expr>,
span: Span,
},
Unary {
op: UnOp,
operand: alloc::boxed::Box<Expr>,
span: Span,
},
Binary {
op: BinOp,
lhs: alloc::boxed::Box<Expr>,
rhs: alloc::boxed::Box<Expr>,
span: Span,
},
List {
items: Vec<Expr>,
span: Span,
},
Map {
entries: Vec<Property>,
span: Span,
},
}
impl Expr {
pub fn span(&self) -> Span {
match self {
Expr::Num(n) => n.span,
Expr::Str(s) => s.span,
Expr::Bool(b) => b.span,
Expr::Path(p) => p.span,
Expr::Call { span, .. }
| Expr::Unary { span, .. }
| Expr::Binary { span, .. }
| Expr::List { span, .. }
| Expr::Map { span, .. } => *span,
}
}
pub fn eval_rational(&self) -> Result<Rational, Diagnostic> {
match self {
Expr::Num(n) => Rational::new(i128::from(n.node.value), 1)
.ok_or_else(|| Diagnostic::new(n.span, "number is out of range")),
Expr::Unary { op, operand, span } => {
let v = operand.eval_rational()?;
match op {
UnOp::Neg => v
.checked_neg()
.ok_or_else(|| Diagnostic::new(*span, "value is out of range")),
}
}
Expr::Binary { op, lhs, rhs, span } => {
let a = lhs.eval_rational()?;
let b = rhs.eval_rational()?;
let out = match op {
BinOp::Add => a.checked_add(b),
BinOp::Sub => a.checked_sub(b),
BinOp::Mul => a.checked_mul(b),
BinOp::Div => {
if b == Rational::ZERO {
return Err(Diagnostic::new(rhs.span(), "division by zero"));
}
a.checked_div(b)
}
BinOp::Rem => match (a.to_integer(), b.to_integer()) {
(Some(_), Some(0)) => {
return Err(Diagnostic::new(rhs.span(), "division by zero"));
}
(Some(x), Some(y)) => Rational::new(x % y, 1),
_ => {
return Err(Diagnostic::new(
*span,
"`%` needs whole numbers on both sides",
));
}
},
};
out.ok_or_else(|| Diagnostic::new(*span, "value is out of range"))
}
other => Err(Diagnostic::new(
other.span(),
"expected a constant number here; names are only known after resolution",
)),
}
}
}
impl SourceUnit {
pub fn dump(&self) -> String {
let mut out = String::new();
for stmt in &self.stmts {
dump_stmt(stmt, 0, &mut out);
}
out
}
}
fn indent(depth: usize, out: &mut String) {
for _ in 0..depth {
out.push_str(" ");
}
}
fn dump_stmt(stmt: &Stmt, depth: usize, out: &mut String) {
indent(depth, out);
match stmt {
Stmt::Machine(s) => {
out.push_str(&format!("machine {} {{\n", quote(&s.name.node)));
dump_body(&s.body, depth, out);
}
Stmt::Param(s) => {
out.push_str(&format!("param {}", dump_name(&s.name)));
if let Some(d) = &s.default {
out.push_str(&format!(" = {}", dump_expr(d)));
}
out.push('\n');
}
Stmt::Osc(s) => {
out.push_str(&format!(
"osc {} = {}",
dump_name(&s.name),
dump_expr(&s.freq)
));
out.push(' ');
out.push_str(s.unit.node.as_str());
out.push('\n');
}
Stmt::Space(s) => {
out.push_str(&format!("space {} ", dump_name(&s.name)));
dump_props(&s.props, out);
out.push('\n');
}
Stmt::Object(s) => {
out.push_str(&format!(
"object {} {} ",
dump_name(&s.name),
quote(&s.class.node)
));
dump_props(&s.props, out);
out.push('\n');
}
Stmt::Map(s) => {
out.push_str(&format!(
"map {} {} size {} = {}",
dump_name(&s.space),
dump_expr(&s.base),
dump_expr(&s.size),
dump_expr(&s.target)
));
if !s.props.is_empty() {
out.push(' ');
dump_props(&s.props, out);
}
out.push('\n');
}
Stmt::Wire(s) => {
out.push_str(&format!(
"wire {} -> {}\n",
dump_path(&s.from),
dump_path(&s.to)
));
}
Stmt::Include(s) => {
out.push_str(&format!("include {}\n", quote(&s.path.node)));
}
Stmt::Template(s) => {
out.push_str(&format!("template {}(", dump_name(&s.name)));
for (i, p) in s.params.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
out.push_str(&dump_name(&p.name));
if let Some(d) = &p.default {
out.push_str(&format!(" = {}", dump_expr(d)));
}
}
out.push_str(") {\n");
dump_body(&s.body, depth, out);
}
Stmt::Instance(s) => {
out.push_str(&format!(
"instance {} = {}(",
dump_name(&s.name),
dump_name(&s.template)
));
for (i, a) in s.args.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
if let Some(n) = &a.name {
out.push_str(&format!("{} = ", dump_name(n)));
}
out.push_str(&dump_expr(&a.value));
}
out.push_str(")\n");
}
Stmt::For(s) => {
out.push_str(&format!(
"for {} in {}{}{} {{\n",
dump_name(&s.var),
dump_expr(&s.start),
if s.inclusive { "..=" } else { ".." },
dump_expr(&s.end)
));
dump_body(&s.body, depth, out);
}
}
}
fn dump_body(body: &[Stmt], depth: usize, out: &mut String) {
for stmt in body {
dump_stmt(stmt, depth + 1, out);
}
indent(depth, out);
out.push_str("}\n");
}
fn dump_props(props: &[Property], out: &mut String) {
out.push('{');
for (i, p) in props.iter().enumerate() {
out.push_str(if i > 0 { ", " } else { " " });
out.push_str(&format!("{} = {}", dump_name(&p.name), dump_expr(&p.value)));
}
out.push_str(if props.is_empty() { "}" } else { " }" });
}
fn dump_name(name: &Name) -> String {
let mut out = String::new();
for part in &name.parts {
match part {
NamePart::Literal(text) => out.push_str(text),
NamePart::Substitution(expr) => match expr {
Expr::Path(p) if p.segments.len() == 1 && p.as_literal().is_some() => {
out.push('$');
out.push_str(&dump_path(p));
}
other => {
out.push_str("${");
out.push_str(&dump_expr(other));
out.push('}');
}
},
}
}
out
}
fn dump_path(path: &Path) -> String {
let mut out = String::new();
for (i, seg) in path.segments.iter().enumerate() {
if i > 0 {
out.push('.');
}
out.push_str(&dump_name(seg));
}
out
}
fn dump_expr(expr: &Expr) -> String {
match expr {
Expr::Num(n) => n.node.value.to_string(),
Expr::Str(s) => quote(&s.node),
Expr::Bool(b) => b.node.to_string(),
Expr::Path(p) => dump_path(p),
Expr::Call { callee, args, .. } => {
let mut out = format!("{}(", dump_path(callee));
for (i, a) in args.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
out.push_str(&dump_expr(a));
}
out.push(')');
out
}
Expr::Unary { operand, .. } => format!("(-{})", dump_expr(operand)),
Expr::Binary { op, lhs, rhs, .. } => {
format!("({} {} {})", dump_expr(lhs), op.as_str(), dump_expr(rhs))
}
Expr::List { items, .. } => {
let mut out = String::from("[");
for (i, item) in items.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
out.push_str(&dump_expr(item));
}
out.push(']');
out
}
Expr::Map { entries, .. } => {
let mut out = String::new();
dump_props(entries, &mut out);
out
}
}
}
fn quote(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 2);
out.push('"');
for c in s.chars() {
match c {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
c if (c as u32) < 0x20 => out.push_str(&format!("\\x{:02x}", c as u32)),
c => out.push(c),
}
}
out.push('"');
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::machine::lexer::{NumUnit, Radix};
fn num(value: u64) -> Expr {
Expr::Num(Spanned::new(
NumLit {
value,
digits: value,
radix: Radix::Dec,
unit: NumUnit::None,
},
Span::at(0),
))
}
#[test]
fn literal_expressions_evaluate_exactly() {
let e = Expr::Binary {
op: BinOp::Div,
lhs: alloc::boxed::Box::new(num(236_250_000)),
rhs: alloc::boxed::Box::new(num(11)),
span: Span::at(0),
};
let r = e.eval_rational().expect("literal");
assert_eq!(r.numerator(), 236_250_000);
assert_eq!(r.denominator(), 11);
}
#[test]
fn evaluation_refuses_names_and_zero_divisors() {
let name = Expr::Path(Path {
segments: alloc::vec![Name {
parts: alloc::vec![NamePart::Literal("master".to_string())],
span: Span::at(0),
}],
span: Span::at(0),
});
assert!(name.eval_rational().is_err());
let div0 = Expr::Binary {
op: BinOp::Div,
lhs: alloc::boxed::Box::new(num(1)),
rhs: alloc::boxed::Box::new(num(0)),
span: Span::at(0),
};
assert_eq!(
div0.eval_rational().expect_err("zero").message,
"division by zero"
);
}
#[test]
fn quoting_is_reversible_looking() {
assert_eq!(quote("a\"b\\c\n\t\r\u{1}"), "\"a\\\"b\\\\c\\n\\t\\r\\x01\"");
}
#[test]
fn frequency_units_scale() {
assert_eq!(FreqUnit::from_spelling("MHz"), Some(FreqUnit::MHz));
assert_eq!(FreqUnit::from_spelling("kHz"), Some(FreqUnit::KHz));
assert_eq!(FreqUnit::from_spelling("KHz"), Some(FreqUnit::KHz));
assert_eq!(FreqUnit::from_spelling("hz"), None);
let osc = OscDecl {
name: Name {
parts: alloc::vec![NamePart::Literal("x".to_string())],
span: Span::at(0),
},
freq: num(21),
unit: Spanned::new(FreqUnit::MHz, Span::at(0)),
span: Span::at(0),
};
assert_eq!(
osc.frequency_hz().expect("literal").to_integer(),
Some(21_000_000)
);
}
}