work
This commit is contained in:
73
src/parser/v3/nodes/body.rs
Normal file
73
src/parser/v3/nodes/body.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
use std::fmt::{Debug, Write};
|
||||
|
||||
use super::{
|
||||
token::Symbol, Node, NodeParsable, Parsable, ParseResult, ParserError,
|
||||
ParserErrors, Statement, TokenCursor,
|
||||
};
|
||||
use crate::util::Padder;
|
||||
|
||||
pub struct Body {
|
||||
pub statements: Vec<Node<Statement>>,
|
||||
}
|
||||
|
||||
impl Parsable for Body {
|
||||
fn parse(cursor: &mut TokenCursor, errors: &mut ParserErrors) -> ParseResult<Self> {
|
||||
let mut statements = Vec::new();
|
||||
cursor.expect_sym(Symbol::OpenCurly)?;
|
||||
if cursor.expect_peek()?.is_symbol(Symbol::CloseCurly) {
|
||||
cursor.next();
|
||||
return ParseResult::Ok(Self { statements });
|
||||
}
|
||||
let mut expect_semi = false;
|
||||
let mut recover = false;
|
||||
loop {
|
||||
let Some(next) = cursor.peek() else {
|
||||
recover = true;
|
||||
errors.add(ParserError::unexpected_end());
|
||||
break;
|
||||
};
|
||||
if next.is_symbol(Symbol::CloseCurly) {
|
||||
cursor.next();
|
||||
break;
|
||||
}
|
||||
if next.is_symbol(Symbol::Semicolon) {
|
||||
cursor.next();
|
||||
expect_semi = false;
|
||||
continue;
|
||||
} else if expect_semi {
|
||||
errors.add(ParserError {
|
||||
msg: "expected ';'".to_string(),
|
||||
spans: vec![cursor.next_pos().char_span()],
|
||||
});
|
||||
}
|
||||
let res = Statement::parse_node(cursor, errors);
|
||||
statements.push(res.node);
|
||||
expect_semi = true;
|
||||
if res.recover {
|
||||
cursor.seek_syms(&[Symbol::Semicolon, Symbol::CloseCurly]);
|
||||
if cursor.peek().is_none() {
|
||||
recover = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ParseResult::from_recover(Self { statements }, recover)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Body {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if self.statements.first().is_some() {
|
||||
f.write_str("{\n ")?;
|
||||
let mut padder = Padder::new(f);
|
||||
for s in &self.statements {
|
||||
// they don't expose wrap_buf :grief:
|
||||
padder.write_str(&format!("{s:?}\n"))?;
|
||||
}
|
||||
f.write_char('}')?;
|
||||
} else {
|
||||
f.write_str("{}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
156
src/parser/v3/nodes/expr.rs
Normal file
156
src/parser/v3/nodes/expr.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
use std::fmt::{Debug, Write};
|
||||
|
||||
use super::{
|
||||
BinaryOperator, Body, Ident, Literal, Node, NodeParsable, Parsable, ParseResult, ParserError,
|
||||
ParserErrors, Symbol, TokenCursor, UnaryOperator,
|
||||
};
|
||||
|
||||
type BoxNode = Node<Box<Expr>>;
|
||||
|
||||
pub enum Expr {
|
||||
Lit(Node<Literal>),
|
||||
Ident(Node<Ident>),
|
||||
BinaryOp(BinaryOperator, BoxNode, BoxNode),
|
||||
UnaryOp(UnaryOperator, BoxNode),
|
||||
Block(Node<Body>),
|
||||
Call(BoxNode, Vec<Node<Expr>>),
|
||||
Group(BoxNode),
|
||||
}
|
||||
|
||||
impl Parsable for Expr {
|
||||
fn parse(cursor: &mut TokenCursor, errors: &mut ParserErrors) -> ParseResult<Self> {
|
||||
let start = cursor.next_pos();
|
||||
let next = cursor.expect_peek()?;
|
||||
let mut e1 = if next.is_symbol(Symbol::OpenParen) {
|
||||
cursor.next();
|
||||
if cursor.expect_peek()?.is_symbol(Symbol::CloseParen) {
|
||||
cursor.next();
|
||||
return ParseResult::Ok(Expr::Lit(Node::new(
|
||||
Literal::Unit,
|
||||
cursor.next_pos().char_span(),
|
||||
)));
|
||||
}
|
||||
let res = Node::parse(cursor, errors);
|
||||
if res.recover {
|
||||
cursor.seek_sym(Symbol::CloseParen);
|
||||
}
|
||||
cursor.expect_sym(Symbol::CloseParen)?;
|
||||
Self::Group(res.node.bx())
|
||||
} else if next.is_symbol(Symbol::OpenCurly) {
|
||||
Self::Block(Body::parse_node(cursor, errors)?)
|
||||
} else if let Some(op) = UnaryOperator::from_token(next) {
|
||||
cursor.next();
|
||||
return Node::parse(cursor, errors).map(|n| {
|
||||
let n = n.bx();
|
||||
if let Some(box Self::BinaryOp(op2, n1, n2)) = n.inner {
|
||||
let span = start.to(n1.span.end);
|
||||
Self::BinaryOp(op2, Node::new(Self::UnaryOp(op, n1), span).bx(), n2)
|
||||
} else {
|
||||
Self::UnaryOp(op, n)
|
||||
}
|
||||
});
|
||||
} else if let Some(val) = Node::maybe_parse(cursor, errors) {
|
||||
Self::Lit(val)
|
||||
} else {
|
||||
let res = Node::parse(cursor, &mut ParserErrors::new());
|
||||
if res.node.is_some() {
|
||||
Self::Ident(res.node)
|
||||
} else {
|
||||
let next = cursor.expect_peek()?;
|
||||
return ParseResult::Err(ParserError::unexpected_token(next, "an expression"));
|
||||
}
|
||||
};
|
||||
let Some(mut next) = cursor.peek() else {
|
||||
return ParseResult::Ok(e1);
|
||||
};
|
||||
while next.is_symbol(Symbol::OpenParen) {
|
||||
cursor.next();
|
||||
let mut args = Vec::new();
|
||||
loop {
|
||||
let next = cursor.expect_peek()?;
|
||||
if next.is_symbol(Symbol::CloseParen) {
|
||||
break;
|
||||
}
|
||||
let res = Node::<Expr>::parse(cursor, errors);
|
||||
args.push(res.node);
|
||||
if res.recover {
|
||||
cursor.seek_syms(&[Symbol::CloseParen, Symbol::Comma]);
|
||||
}
|
||||
let next = cursor.expect_peek()?;
|
||||
if !next.is_symbol(Symbol::Comma) {
|
||||
break;
|
||||
}
|
||||
cursor.next();
|
||||
}
|
||||
cursor.expect_sym(Symbol::CloseParen)?;
|
||||
let end = cursor.prev_end();
|
||||
e1 = Self::Call(Node::new(Box::new(e1), start.to(end)), args);
|
||||
let Some(next2) = cursor.peek() else {
|
||||
return ParseResult::Ok(e1);
|
||||
};
|
||||
next = next2
|
||||
}
|
||||
let end = cursor.prev_end();
|
||||
let mut recover = false;
|
||||
let res = if let Some(mut op) = BinaryOperator::from_token(&next.token) {
|
||||
cursor.next();
|
||||
let mut n1 = Node::new(e1, start.to(end)).bx();
|
||||
let res = Node::parse(cursor, errors);
|
||||
let mut n2 = res.node.bx();
|
||||
recover = res.recover;
|
||||
if let Some(box Self::BinaryOp(op2, _, _)) = n2.as_ref() {
|
||||
if op.presedence() > op2.presedence() {
|
||||
let Some(box Self::BinaryOp(op2, n21, n22)) = n2.inner else {
|
||||
unreachable!();
|
||||
};
|
||||
let end = n21.span.end;
|
||||
n1 = Node::new(Self::BinaryOp(op, n1, n21), start.to(end)).bx();
|
||||
op = op2;
|
||||
n2 = n22;
|
||||
}
|
||||
}
|
||||
Self::BinaryOp(op, n1, n2)
|
||||
} else {
|
||||
e1
|
||||
};
|
||||
ParseResult::from_recover(res, recover)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Expr {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Expr::Lit(c) => c.fmt(f)?,
|
||||
Expr::Ident(n) => n.fmt(f)?,
|
||||
Expr::Block(b) => b.fmt(f)?,
|
||||
Expr::BinaryOp(op, e1, e2) => {
|
||||
write!(f, "({:?}", *e1)?;
|
||||
if op.pad() {
|
||||
write!(f, " {} ", op.str())?;
|
||||
} else {
|
||||
write!(f, "{}", op.str())?;
|
||||
}
|
||||
write!(f, "{:?})", *e2)?;
|
||||
}
|
||||
Expr::Call(n, args) => {
|
||||
n.fmt(f)?;
|
||||
f.write_char('(')?;
|
||||
if let Some(a) = args.first() {
|
||||
a.fmt(f)?;
|
||||
}
|
||||
for arg in args.iter().skip(1) {
|
||||
f.write_str(", ")?;
|
||||
arg.fmt(f)?;
|
||||
}
|
||||
f.write_char(')')?;
|
||||
}
|
||||
Expr::UnaryOp(op, e) => {
|
||||
write!(f, "(")?;
|
||||
write!(f, "{}", op.str())?;
|
||||
write!(f, "{:?})", *e)?;
|
||||
}
|
||||
Expr::Group(inner) => inner.fmt(f)?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
28
src/parser/v3/nodes/func.rs
Normal file
28
src/parser/v3/nodes/func.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
use super::{Body, Ident, Keyword, Node, Parsable, ParseResult, ParserErrors, Symbol, TokenCursor};
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub struct Function {
|
||||
pub name: Node<Ident>,
|
||||
pub body: Node<Body>,
|
||||
}
|
||||
|
||||
impl Parsable for Function {
|
||||
fn parse(cursor: &mut TokenCursor, errors: &mut ParserErrors) -> ParseResult<Self> {
|
||||
cursor.expect_kw(Keyword::Fn)?;
|
||||
let name = Node::parse(cursor, errors)?;
|
||||
cursor.expect_sym(Symbol::OpenParen)?;
|
||||
cursor.expect_sym(Symbol::CloseParen)?;
|
||||
Node::parse(cursor, errors).map(|body| Self { name, body })
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Function {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str("fn ")?;
|
||||
self.name.fmt(f)?;
|
||||
f.write_str("() ")?;
|
||||
self.body.fmt(f)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
29
src/parser/v3/nodes/ident.rs
Normal file
29
src/parser/v3/nodes/ident.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use std::fmt::Debug;
|
||||
use super::{Parsable, ParseResult, ParserError, Token};
|
||||
|
||||
pub struct Ident(String);
|
||||
|
||||
impl Ident {
|
||||
pub fn val(&self) -> &String {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Parsable for Ident {
|
||||
fn parse(cursor: &mut super::TokenCursor, errors: &mut super::ParserErrors) -> ParseResult<Self> {
|
||||
let next = cursor.expect_peek()?;
|
||||
let Token::Ident(name) = &next.token else {
|
||||
return ParseResult::Err(ParserError::unexpected_token(next, "an identifier"));
|
||||
};
|
||||
let name = name.to_string();
|
||||
cursor.next();
|
||||
ParseResult::Ok(Self(name))
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Ident {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
114
src/parser/v3/nodes/lit.rs
Normal file
114
src/parser/v3/nodes/lit.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
use super::{
|
||||
CharCursor, MaybeParsable, ParserError, ParserErrors, Symbol, Token, TokenCursor,
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub enum Literal {
|
||||
String(String),
|
||||
Char(char),
|
||||
Number(Number),
|
||||
Unit,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct Number {
|
||||
pub whole: String,
|
||||
pub decimal: Option<String>,
|
||||
pub ty: Option<String>,
|
||||
}
|
||||
|
||||
impl MaybeParsable for Literal {
|
||||
fn maybe_parse(
|
||||
cursor: &mut TokenCursor,
|
||||
_: &mut ParserErrors,
|
||||
) -> Result<Option<Self>, ParserError> {
|
||||
let inst = cursor.expect_peek()?;
|
||||
let mut res = match &inst.token {
|
||||
Token::Symbol(Symbol::SingleQuote) => {
|
||||
let chars = cursor.chars();
|
||||
let c = chars.expect_next()?;
|
||||
chars.expect('\'')?;
|
||||
Self::Char(c)
|
||||
}
|
||||
Token::Symbol(Symbol::DoubleQuote) => Self::String(string_from(cursor.chars())?),
|
||||
Token::Ident(text) => {
|
||||
let first = text.chars().next().unwrap();
|
||||
if first.is_ascii_digit() {
|
||||
Self::Number(Number {
|
||||
whole: text.to_string(),
|
||||
decimal: None,
|
||||
ty: None,
|
||||
})
|
||||
} else {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
_ => return Ok(None),
|
||||
};
|
||||
cursor.next();
|
||||
if let (Some(next), Self::Number(num)) = (cursor.peek(), &mut res) {
|
||||
if next.token.is_symbol(Symbol::Dot) {
|
||||
cursor.next();
|
||||
if let Some(next) = cursor.peek() {
|
||||
if let Token::Ident(i) = &next.token {
|
||||
if i.chars().next().unwrap().is_ascii_digit() {
|
||||
num.decimal = Some(i.to_string());
|
||||
cursor.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Some(res))
|
||||
}
|
||||
}
|
||||
pub fn string_from(cursor: &mut CharCursor) -> Result<String, ParserError> {
|
||||
let mut str = String::new();
|
||||
loop {
|
||||
let c = cursor.expect_next()?;
|
||||
if c == '"' {
|
||||
return Ok(str);
|
||||
}
|
||||
str.push(match c {
|
||||
'\\' => {
|
||||
let next = cursor.expect_next()?;
|
||||
match next {
|
||||
'"' => '"',
|
||||
'\'' => '\'',
|
||||
't' => '\t',
|
||||
'n' => '\n',
|
||||
'0' => '\0',
|
||||
_ => {
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => c,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Literal {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::String(str) => str.fmt(f),
|
||||
Self::Char(c) => c.fmt(f),
|
||||
Self::Number(n) => n.fmt(f),
|
||||
Self::Unit => f.write_str("()"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Number {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.whole)?;
|
||||
if let Some(d) = &self.decimal {
|
||||
write!(f, ".{}", d)?;
|
||||
}
|
||||
if let Some(ty) = &self.ty {
|
||||
write!(f, "T{}", ty)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
19
src/parser/v3/nodes/mod.rs
Normal file
19
src/parser/v3/nodes/mod.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
mod body;
|
||||
mod expr;
|
||||
mod func;
|
||||
mod module;
|
||||
mod op;
|
||||
mod statement;
|
||||
mod lit;
|
||||
mod ident;
|
||||
|
||||
pub use body::*;
|
||||
pub use expr::*;
|
||||
pub use func::*;
|
||||
pub use module::*;
|
||||
pub use op::*;
|
||||
pub use statement::*;
|
||||
pub use lit::*;
|
||||
pub use ident::*;
|
||||
|
||||
use super::*;
|
||||
35
src/parser/v3/nodes/module.rs
Normal file
35
src/parser/v3/nodes/module.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use super::{
|
||||
Function, Keyword, Node, Parsable, ParseResult, ParserError, ParserErrors, TokenCursor,
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub struct Module {
|
||||
pub functions: Vec<Node<Function>>,
|
||||
}
|
||||
|
||||
impl Parsable for Module {
|
||||
fn parse(cursor: &mut TokenCursor, errors: &mut ParserErrors) -> ParseResult<Self> {
|
||||
let mut functions = Vec::new();
|
||||
loop {
|
||||
let Some(next) = cursor.peek() else {
|
||||
return ParseResult::Ok(Self { functions });
|
||||
};
|
||||
if next.is_keyword(Keyword::Fn) {
|
||||
let res = Node::parse(cursor, errors);
|
||||
functions.push(res.node);
|
||||
if res.recover {
|
||||
return ParseResult::Recover(Self { functions });
|
||||
}
|
||||
} else {
|
||||
errors.add(ParserError::unexpected_token(next, "fn"));
|
||||
cursor.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Module {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.functions.fmt(f)
|
||||
}
|
||||
}
|
||||
96
src/parser/v3/nodes/op.rs
Normal file
96
src/parser/v3/nodes/op.rs
Normal file
@@ -0,0 +1,96 @@
|
||||
use super::{Symbol, Token};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum BinaryOperator {
|
||||
Add,
|
||||
Sub,
|
||||
Mul,
|
||||
Div,
|
||||
LessThan,
|
||||
GreaterThan,
|
||||
Access,
|
||||
Assign,
|
||||
}
|
||||
|
||||
impl BinaryOperator {
|
||||
pub fn presedence(&self) -> u32 {
|
||||
match self {
|
||||
Self::Assign => 0,
|
||||
Self::LessThan => 1,
|
||||
Self::GreaterThan => 1,
|
||||
Self::Add => 2,
|
||||
Self::Sub => 3,
|
||||
Self::Mul => 4,
|
||||
Self::Div => 5,
|
||||
Self::Access => 6,
|
||||
}
|
||||
}
|
||||
pub fn str(&self) -> &str {
|
||||
match self {
|
||||
Self::Add => "+",
|
||||
Self::Sub => "-",
|
||||
Self::Mul => "*",
|
||||
Self::Div => "/",
|
||||
Self::LessThan => "<",
|
||||
Self::GreaterThan => ">",
|
||||
Self::Access => ".",
|
||||
Self::Assign => "=",
|
||||
}
|
||||
}
|
||||
pub fn from_token(token: &Token) -> Option<Self> {
|
||||
let Token::Symbol(symbol) = token else {
|
||||
return None;
|
||||
};
|
||||
Some(match symbol {
|
||||
Symbol::OpenAngle => Self::LessThan,
|
||||
Symbol::CloseAngle => Self::GreaterThan,
|
||||
Symbol::Plus => Self::Add,
|
||||
Symbol::Minus => Self::Sub,
|
||||
Symbol::Asterisk => Self::Mul,
|
||||
Symbol::Slash => Self::Div,
|
||||
Symbol::Dot => Self::Access,
|
||||
Symbol::Equals => Self::Assign,
|
||||
_ => {
|
||||
return None;
|
||||
}
|
||||
})
|
||||
}
|
||||
pub fn pad(&self) -> bool {
|
||||
match self {
|
||||
Self::Add => true,
|
||||
Self::Sub => true,
|
||||
Self::Mul => true,
|
||||
Self::Div => true,
|
||||
Self::LessThan => true,
|
||||
Self::GreaterThan => true,
|
||||
Self::Access => false,
|
||||
Self::Assign => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum UnaryOperator {
|
||||
Not,
|
||||
Ref,
|
||||
}
|
||||
|
||||
impl UnaryOperator {
|
||||
pub fn str(&self) -> &str {
|
||||
match self {
|
||||
Self::Not => "!",
|
||||
Self::Ref => "&",
|
||||
}
|
||||
}
|
||||
pub fn from_token(token: &Token) -> Option<Self> {
|
||||
let Token::Symbol(symbol) = token else {
|
||||
return None;
|
||||
};
|
||||
Some(match symbol {
|
||||
Symbol::Ampersand => Self::Ref,
|
||||
Symbol::Bang => Self::Not,
|
||||
_ => {
|
||||
return None;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
53
src/parser/v3/nodes/statement.rs
Normal file
53
src/parser/v3/nodes/statement.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
use super::{
|
||||
Expr, Ident, Keyword, Node, Parsable, ParseResult, ParserErrors, Symbol, Token, TokenCursor,
|
||||
};
|
||||
use std::fmt::{Debug, Write};
|
||||
|
||||
pub enum Statement {
|
||||
Let(Node<Ident>, Node<Expr>),
|
||||
Return(Node<Expr>),
|
||||
Expr(Node<Expr>),
|
||||
}
|
||||
|
||||
impl Parsable for Statement {
|
||||
fn parse(cursor: &mut TokenCursor, errors: &mut ParserErrors) -> ParseResult<Self> {
|
||||
let next = cursor.expect_peek()?;
|
||||
match next.token {
|
||||
Token::Keyword(Keyword::Let) => {
|
||||
cursor.next();
|
||||
let name = Node::parse(cursor, errors)?;
|
||||
cursor.expect_sym(Symbol::Equals)?;
|
||||
Node::parse(cursor, errors).map(|expr| Self::Let(name, expr))
|
||||
}
|
||||
Token::Keyword(Keyword::Return) => {
|
||||
cursor.next();
|
||||
Node::parse(cursor, errors).map(Self::Return)
|
||||
}
|
||||
_ => Node::parse(cursor, errors).map(Self::Expr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Statement {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Statement::Let(n, e) => {
|
||||
f.write_str("let ")?;
|
||||
n.fmt(f);
|
||||
f.write_str(" = ")?;
|
||||
e.fmt(f)?;
|
||||
f.write_char(';')?;
|
||||
}
|
||||
Statement::Return(e) => {
|
||||
f.write_str("return ")?;
|
||||
e.fmt(f)?;
|
||||
f.write_char(';')?;
|
||||
}
|
||||
Statement::Expr(e) => {
|
||||
e.fmt(f)?;
|
||||
f.write_char(';')?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user