stuff
This commit is contained in:
+106
@@ -0,0 +1,106 @@
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Span {
|
||||
pub file: usize,
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
}
|
||||
|
||||
pub struct Spanned<T> {
|
||||
pub inner: T,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
impl<T> std::ops::Deref for Spanned<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::DerefMut for Spanned<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CompilerMsg {
|
||||
pub spans: Vec<Span>,
|
||||
pub msg: String,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct CompilerOutput {
|
||||
pub errors: Vec<CompilerMsg>,
|
||||
pub files: Vec<String>,
|
||||
}
|
||||
|
||||
impl CompilerOutput {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
pub fn error(&mut self, msg: impl Into<CompilerMsg>) {
|
||||
self.errors.push(msg.into());
|
||||
}
|
||||
pub fn write(&self, w: &mut impl std::io::Write) {
|
||||
let files: Vec<_> = self
|
||||
.files
|
||||
.iter()
|
||||
.map(|path| std::fs::read_to_string(path).unwrap())
|
||||
.collect();
|
||||
for error in &self.errors {
|
||||
writeln!(w, "Error: {}", error.msg).unwrap();
|
||||
for span in &error.spans {
|
||||
span.write(w, &files[span.file]).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Span {
|
||||
pub fn write(&self, w: &mut impl std::io::Write, text: &str) -> std::io::Result<()> {
|
||||
let mut line_start = 0;
|
||||
let mut found = false;
|
||||
let mut line = 1;
|
||||
let mut spans = Vec::new();
|
||||
for (i, c) in text.char_indices() {
|
||||
if i == self.start {
|
||||
found = true;
|
||||
}
|
||||
if i == self.end {
|
||||
found = true;
|
||||
}
|
||||
if c == '\n' {
|
||||
if found {
|
||||
spans.push((line, line_start..i));
|
||||
}
|
||||
line_start = i + 1;
|
||||
line += 1;
|
||||
found = false;
|
||||
}
|
||||
}
|
||||
let start_chars = text[self.start..].lines().next().unwrap().len();
|
||||
let underline = "\x1b[4:3m";
|
||||
let underline_color = "\x1b[58;5;1m";
|
||||
let end = "\x1b[0m";
|
||||
if let [(line, range)] = &spans[..] {
|
||||
writeln!(
|
||||
w,
|
||||
" {line:3} | {}{underline}{underline_color}{}{end}{}",
|
||||
&text[range.start..self.start],
|
||||
&text[self.start..=self.end],
|
||||
&text[(self.end + 1)..range.end]
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for CompilerMsg {
|
||||
fn from(value: String) -> Self {
|
||||
Self {
|
||||
spans: Vec::new(),
|
||||
msg: value.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
-8
@@ -1,3 +1,6 @@
|
||||
use crate::{io::CompilerOutput, parser::Nodes};
|
||||
|
||||
mod io;
|
||||
mod parser;
|
||||
|
||||
fn main() {
|
||||
@@ -6,12 +9,11 @@ fn main() {
|
||||
println!("file expected");
|
||||
return;
|
||||
};
|
||||
let code = match std::fs::read_to_string(path) {
|
||||
Ok(code) => code,
|
||||
Err(err) => {
|
||||
println!("Failed to read input file: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
parser::parse(&code);
|
||||
let mut output = CompilerOutput::new();
|
||||
let nodes = Nodes::parse_root(&path, &mut output);
|
||||
if let Some((nodes, root)) = nodes {
|
||||
nodes.format(&mut std::io::stdout(), root).unwrap();
|
||||
println!();
|
||||
}
|
||||
output.write(&mut std::io::stdout());
|
||||
}
|
||||
|
||||
+31
-16
@@ -1,9 +1,7 @@
|
||||
use crate::parser::error::ParseError;
|
||||
pub use span::*;
|
||||
use crate::io::{CompilerMsg, Span, Spanned};
|
||||
use std::iter::Peekable;
|
||||
pub use token::*;
|
||||
|
||||
mod span;
|
||||
mod token;
|
||||
|
||||
pub struct Cursor<'a> {
|
||||
@@ -12,10 +10,14 @@ pub struct Cursor<'a> {
|
||||
}
|
||||
|
||||
impl<'a> Cursor<'a> {
|
||||
pub fn new(text: &'a str) -> Self {
|
||||
pub fn new(text: &'a str, file: usize) -> Self {
|
||||
Self {
|
||||
span: Span { first: 0, last: 0 },
|
||||
tokens: Tokens::new(text).peekable(),
|
||||
span: Span {
|
||||
start: 0,
|
||||
end: 0,
|
||||
file,
|
||||
},
|
||||
tokens: Tokens::new(text, file).peekable(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,14 +32,14 @@ impl<'a> Cursor<'a> {
|
||||
self.tokens.peek().map(|inst| &inst.inner)
|
||||
}
|
||||
|
||||
pub fn expect_next(&mut self) -> Result<Token, ParseError> {
|
||||
self.next().ok_or_else(|| ParseError {
|
||||
pub fn expect_next(&mut self) -> Result<Token, CompilerMsg> {
|
||||
self.next().ok_or_else(|| CompilerMsg {
|
||||
spans: Vec::new(),
|
||||
msg: "unexpected end of file".to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn expect(&mut self, token: Token) -> Result<Token, ParseError> {
|
||||
pub fn expect(&mut self, token: Token) -> Result<Token, CompilerMsg> {
|
||||
let next = self.expect_next()?;
|
||||
if next == token {
|
||||
Ok(next)
|
||||
@@ -46,7 +48,7 @@ impl<'a> Cursor<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_ident(&mut self) -> Result<String, ParseError> {
|
||||
pub fn expect_ident(&mut self) -> Result<String, CompilerMsg> {
|
||||
let next = self.expect_next()?;
|
||||
if let Token::Ident(s) = next {
|
||||
Ok(s)
|
||||
@@ -55,8 +57,8 @@ impl<'a> Cursor<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unexpected<T>(&self, token: Token, expected: &str) -> Result<T, ParseError> {
|
||||
Err(ParseError::unexpected_token(
|
||||
pub fn unexpected<T>(&self, token: Token, expected: &str) -> Result<T, CompilerMsg> {
|
||||
Err(CompilerMsg::unexpected_token(
|
||||
Spanned {
|
||||
inner: token,
|
||||
span: self.span,
|
||||
@@ -65,11 +67,24 @@ impl<'a> Cursor<'a> {
|
||||
))
|
||||
}
|
||||
|
||||
pub fn peek_first(&mut self) -> usize {
|
||||
self.tokens.peek().map(|i| i.span.first).unwrap_or(0)
|
||||
pub fn peek_start(&mut self) -> usize {
|
||||
self.tokens.peek().map(|i| i.span.start).unwrap_or(0)
|
||||
}
|
||||
|
||||
pub fn cur_last(&mut self) -> usize {
|
||||
self.span.last
|
||||
pub fn cur_end(&mut self) -> usize {
|
||||
self.span.end
|
||||
}
|
||||
|
||||
pub fn file(&mut self) -> usize {
|
||||
self.span.file
|
||||
}
|
||||
}
|
||||
|
||||
impl CompilerMsg {
|
||||
pub fn unexpected_token(inst: TokenInst, expected: &str) -> Self {
|
||||
Self {
|
||||
spans: vec![inst.span],
|
||||
msg: format!("Unexpected token '{}'; expected {expected}", inst.inner),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Span {
|
||||
pub first: usize,
|
||||
pub last: usize,
|
||||
}
|
||||
|
||||
pub struct Spanned<T> {
|
||||
pub inner: T,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
impl<T> std::ops::Deref for Spanned<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::DerefMut for Spanned<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,12 +39,14 @@ impl From<Lit> for Token {
|
||||
pub type TokenInst = Spanned<Token>;
|
||||
|
||||
pub struct Tokens<'a> {
|
||||
file: usize,
|
||||
text: Peekable<CharIndices<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> Tokens<'a> {
|
||||
pub fn new(code: &'a str) -> Self {
|
||||
pub fn new(code: &'a str, file: usize) -> Self {
|
||||
Self {
|
||||
file,
|
||||
text: code.char_indices().peekable(),
|
||||
}
|
||||
}
|
||||
@@ -55,7 +57,11 @@ impl Iterator for Tokens<'_> {
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let (i, c) = self.text.next()?;
|
||||
let mut span = Span { first: i, last: i };
|
||||
let mut span = Span {
|
||||
start: i,
|
||||
end: i,
|
||||
file: self.file,
|
||||
};
|
||||
Some(Spanned {
|
||||
inner: match c {
|
||||
'=' => Token::Equal,
|
||||
@@ -78,7 +84,7 @@ impl Iterator for Tokens<'_> {
|
||||
&& c.is_alphanumeric()
|
||||
{
|
||||
s.push(*c);
|
||||
span.last = *i;
|
||||
span.end = *i;
|
||||
self.text.next();
|
||||
}
|
||||
Lit::Number(s).into()
|
||||
@@ -89,7 +95,7 @@ impl Iterator for Tokens<'_> {
|
||||
&& !matches!(c, '"')
|
||||
{
|
||||
s.push(*c);
|
||||
span.last = *i;
|
||||
span.end = *i;
|
||||
self.text.next();
|
||||
}
|
||||
self.text.next();
|
||||
@@ -104,7 +110,7 @@ impl Iterator for Tokens<'_> {
|
||||
)
|
||||
{
|
||||
s.push(*c);
|
||||
span.last = *i;
|
||||
span.end = *i;
|
||||
self.text.next();
|
||||
}
|
||||
match s.as_str() {
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
use crate::parser::cursor::{Span, TokenInst};
|
||||
|
||||
pub struct ParseError {
|
||||
pub spans: Vec<Span>,
|
||||
pub msg: String,
|
||||
}
|
||||
|
||||
impl ParseError {
|
||||
pub fn unexpected_token(inst: TokenInst, expected: &str) -> Self {
|
||||
Self {
|
||||
spans: vec![inst.span],
|
||||
msg: format!("Unexpected token {}; expected {expected}", inst.inner),
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -1,5 +1,4 @@
|
||||
mod cursor;
|
||||
mod error;
|
||||
mod node;
|
||||
|
||||
pub fn parse(code: &str) {}
|
||||
pub use node::*;
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use crate::parser::{
|
||||
cursor::{Cursor, Span, Token},
|
||||
error::ParseError,
|
||||
};
|
||||
|
||||
pub trait Parsable: Sized {
|
||||
fn parse(ctx: &mut ParseCtx) -> Result<Self, ParseError>;
|
||||
fn vec(nodes: &mut Nodes) -> &mut NodeVec<Self>;
|
||||
}
|
||||
|
||||
pub struct ParseCtx<'a> {
|
||||
cursor: Cursor<'a>,
|
||||
nodes: Nodes,
|
||||
}
|
||||
|
||||
pub struct Nodes {
|
||||
statements: NodeVec<Statement>,
|
||||
exprs: NodeVec<Expr>,
|
||||
idents: NodeVec<Ident>,
|
||||
}
|
||||
|
||||
pub struct NodeVec<N> {
|
||||
vec: Vec<N>,
|
||||
spans: Vec<Span>,
|
||||
}
|
||||
|
||||
impl<N> NodeVec<N> {
|
||||
pub fn add(&mut self, v: N, span: Span) -> Id<N> {
|
||||
let id = self.vec.len();
|
||||
self.vec.push(v);
|
||||
self.spans.push(span);
|
||||
Id {
|
||||
id,
|
||||
_pd: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Statement {
|
||||
Let(Id<Ident>, Id<Expr>),
|
||||
}
|
||||
|
||||
pub enum Expr {
|
||||
Ident(Id<Ident>),
|
||||
Negate(Id<Expr>),
|
||||
Assign(Id<Expr>, Id<Expr>),
|
||||
}
|
||||
|
||||
pub struct Id<T> {
|
||||
id: usize,
|
||||
_pd: PhantomData<T>,
|
||||
}
|
||||
|
||||
pub struct Ident {
|
||||
inner: String,
|
||||
}
|
||||
|
||||
impl Parsable for Expr {
|
||||
fn parse(ctx: &mut ParseCtx) -> Result<Self, ParseError> {
|
||||
Ok(match ctx.cursor.expect_next()? {
|
||||
Token::Dash => Self::Negate(ctx.parse()?),
|
||||
Token::Ident(s) => Self::Ident(ctx.ident(s)),
|
||||
other => return ctx.cursor.unexpected(other, "an expression"),
|
||||
})
|
||||
}
|
||||
|
||||
fn vec(nodes: &mut Nodes) -> &mut NodeVec<Self> {
|
||||
&mut nodes.exprs
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseCtx<'_> {
|
||||
pub fn parse<P: Parsable>(&mut self) -> Result<Id<P>, ParseError> {
|
||||
let first = self.cursor.peek_first();
|
||||
P::parse(self).map(|r| {
|
||||
let last = self.cursor.cur_last();
|
||||
P::vec(&mut self.nodes).add(r, Span { first, last })
|
||||
})
|
||||
}
|
||||
|
||||
pub fn ident(&mut self, s: String) -> Id<Ident> {
|
||||
let span = self.cursor.span;
|
||||
self.nodes.idents.add(Ident { inner: s }, span)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
use crate::{
|
||||
io::CompilerMsg,
|
||||
parser::{
|
||||
cursor::{Lit, Token},
|
||||
*,
|
||||
},
|
||||
};
|
||||
|
||||
pub enum Statement {
|
||||
Let(Id<Ident>, Id<Expr>),
|
||||
}
|
||||
|
||||
pub enum Expr {
|
||||
Ident(Id<Ident>),
|
||||
Lit(Id<Lit>),
|
||||
Negate(Id<Expr>),
|
||||
Assign(Id<Expr>, Id<Expr>),
|
||||
}
|
||||
|
||||
pub struct Ident {
|
||||
pub inner: String,
|
||||
}
|
||||
|
||||
impl FmtNode for Ident {
|
||||
fn format(&self, w: &mut impl std::io::Write, _: &Nodes) -> std::io::Result<()> {
|
||||
write!(w, "{}", self.inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl Parsable for Expr {
|
||||
fn parse(ctx: &mut ParseCtx) -> Result<Self, CompilerMsg> {
|
||||
let e1 = match ctx.cursor.expect_next()? {
|
||||
Token::Dash => Self::Negate(ctx.parse()?),
|
||||
Token::Ident(s) => Self::Ident(ctx.ident(s)),
|
||||
Token::Lit(l) => Self::Lit(ctx.lit(l)),
|
||||
other => return ctx.cursor.unexpected(other, "an expression"),
|
||||
};
|
||||
let Some(next) = ctx.cursor.peek() else {
|
||||
return Ok(e1);
|
||||
};
|
||||
Ok(match next {
|
||||
Token::Equal => {
|
||||
let e1 = ctx.push_adv(e1);
|
||||
let e2: Id<Expr> = ctx.parse()?;
|
||||
Expr::Assign(e1, e2)
|
||||
}
|
||||
_ => e1,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FmtNode for Expr {
|
||||
fn format(&self, w: &mut impl std::io::Write, nodes: &Nodes) -> std::io::Result<()> {
|
||||
match *self {
|
||||
Expr::Ident(id) => nodes.format(w, id),
|
||||
Expr::Lit(id) => nodes.format(w, id),
|
||||
Expr::Negate(id) => {
|
||||
write!(w, "-")?;
|
||||
nodes.format(w, id)
|
||||
}
|
||||
Expr::Assign(id1, id2) => {
|
||||
nodes.format(w, id1)?;
|
||||
write!(w, " = ")?;
|
||||
nodes.format(w, id2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FmtNode for Lit {
|
||||
fn format(&self, w: &mut impl std::io::Write, _: &Nodes) -> std::io::Result<()> {
|
||||
match self {
|
||||
Lit::Number(v) => write!(w, "{v}"),
|
||||
Lit::Bool(v) => write!(w, "{v}"),
|
||||
Lit::String(v) => write!(w, "{v}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
use crate::{
|
||||
io::{CompilerMsg, CompilerOutput, Span},
|
||||
parser::cursor::{Cursor, Lit},
|
||||
};
|
||||
use std::{marker::PhantomData, ops::Index};
|
||||
|
||||
mod expr;
|
||||
pub use expr::*;
|
||||
|
||||
pub trait Parsable: Sized + Node {
|
||||
fn parse(ctx: &mut ParseCtx) -> Result<Self, CompilerMsg>;
|
||||
}
|
||||
|
||||
pub struct ParseCtx<'a> {
|
||||
start: usize,
|
||||
cursor: Cursor<'a>,
|
||||
nodes: Nodes,
|
||||
}
|
||||
|
||||
def_nodes!(
|
||||
exprs: Expr,
|
||||
idents: Ident,
|
||||
statements: Statement,
|
||||
lits: Lit,
|
||||
);
|
||||
|
||||
impl Nodes {
|
||||
pub fn parse_root(path: &str, output: &mut CompilerOutput) -> Option<(Self, Id<Expr>)> {
|
||||
let root_code = match std::fs::read_to_string(path) {
|
||||
Ok(code) => code,
|
||||
Err(err) => {
|
||||
output.error(format!("Failed to read input file: {err}"));
|
||||
return None;
|
||||
}
|
||||
};
|
||||
output.files.push(path.to_string());
|
||||
let nodes = Self::default();
|
||||
let mut ctx = ParseCtx {
|
||||
start: 0,
|
||||
nodes,
|
||||
cursor: Cursor::new(&root_code, 0),
|
||||
};
|
||||
let root = match ctx.parse::<Expr>() {
|
||||
Ok(expr) => expr,
|
||||
Err(msg) => {
|
||||
output.error(msg);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
Some((ctx.nodes, root))
|
||||
}
|
||||
|
||||
pub fn format<N: FmtNode>(
|
||||
&self,
|
||||
w: &mut impl std::io::Write,
|
||||
id: Id<N>,
|
||||
) -> std::io::Result<()> {
|
||||
self[id].format(w, self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: Node> Index<Id<N>> for Nodes {
|
||||
type Output = N;
|
||||
|
||||
fn index(&self, index: Id<N>) -> &Self::Output {
|
||||
&N::vec(self).vec[index.id]
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: Node> Index<&Id<N>> for Nodes {
|
||||
type Output = N;
|
||||
|
||||
fn index(&self, index: &Id<N>) -> &Self::Output {
|
||||
&N::vec(self).vec[index.id]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NodeVec<N> {
|
||||
vec: Vec<N>,
|
||||
spans: Vec<Span>,
|
||||
}
|
||||
|
||||
impl<N> NodeVec<N> {
|
||||
pub fn add(&mut self, v: N, span: Span) -> Id<N> {
|
||||
let id = self.vec.len();
|
||||
self.vec.push(v);
|
||||
self.spans.push(span);
|
||||
Id {
|
||||
id,
|
||||
_pd: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<N> Default for NodeVec<N> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
vec: Default::default(),
|
||||
spans: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Id<T> {
|
||||
id: usize,
|
||||
_pd: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> Clone for Id<T> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Copy for Id<T> {}
|
||||
|
||||
pub trait Node: Sized {
|
||||
fn vec(nodes: &Nodes) -> &NodeVec<Self>;
|
||||
fn vec_mut(nodes: &mut Nodes) -> &mut NodeVec<Self>;
|
||||
}
|
||||
|
||||
impl ParseCtx<'_> {
|
||||
pub fn parse<P: Parsable>(&mut self) -> Result<Id<P>, CompilerMsg> {
|
||||
let old_start = self.start;
|
||||
self.start = self.cursor.peek_start();
|
||||
let res = P::parse(self).map(|r| self.push(r));
|
||||
self.start = old_start;
|
||||
res
|
||||
}
|
||||
|
||||
pub fn ident(&mut self, s: String) -> Id<Ident> {
|
||||
let span = self.cursor.span;
|
||||
self.nodes.idents.add(Ident { inner: s }, span)
|
||||
}
|
||||
pub fn lit(&mut self, lit: Lit) -> Id<Lit> {
|
||||
let span = self.cursor.span;
|
||||
self.nodes.lits.add(lit, span)
|
||||
}
|
||||
pub fn push_adv<N: Node>(&mut self, node: N) -> Id<N> {
|
||||
let res = self.push(node);
|
||||
self.cursor.next();
|
||||
res
|
||||
}
|
||||
pub fn push<N: Node>(&mut self, node: N) -> Id<N> {
|
||||
let end = self.cursor.cur_end();
|
||||
N::vec_mut(&mut self.nodes).add(
|
||||
node,
|
||||
Span {
|
||||
file: self.cursor.file(),
|
||||
start: self.start,
|
||||
end,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! def_nodes {
|
||||
($($field:ident: $ty:ident,)*) => {
|
||||
#[derive(Default)]
|
||||
pub struct Nodes {
|
||||
$($field: NodeVec<$ty>,)*
|
||||
}
|
||||
|
||||
$(impl Node for $ty {
|
||||
fn vec(nodes: &Nodes) -> &NodeVec<Self> {
|
||||
&nodes.$field
|
||||
}
|
||||
fn vec_mut(nodes: &mut Nodes) -> &mut NodeVec<Self> {
|
||||
&mut nodes.$field
|
||||
}
|
||||
})*
|
||||
};
|
||||
}
|
||||
use def_nodes;
|
||||
|
||||
pub trait FmtNode: Node {
|
||||
fn format(&self, w: &mut impl std::io::Write, nodes: &Nodes) -> std::io::Result<()>;
|
||||
}
|
||||
Reference in New Issue
Block a user