Rework error handling and main
parent
2ccc3e8ef1
commit
8af9855a27
|
@ -13,9 +13,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.71"
|
version = "1.0.72"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
|
checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ascii-canvas"
|
name = "ascii-canvas"
|
||||||
|
@ -207,9 +207,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lalrpop"
|
name = "lalrpop"
|
||||||
version = "0.19.9"
|
version = "0.20.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f34313ec00c2eb5c3c87ca6732ea02dcf3af99c3ff7a8fb622ffb99c9d860a87"
|
checksum = "da4081d44f4611b66c6dd725e6de3169f9f63905421e8626fcb86b6a898998b8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ascii-canvas",
|
"ascii-canvas",
|
||||||
"bit-set",
|
"bit-set",
|
||||||
|
@ -219,9 +219,9 @@ dependencies = [
|
||||||
"itertools",
|
"itertools",
|
||||||
"lalrpop-util",
|
"lalrpop-util",
|
||||||
"petgraph",
|
"petgraph",
|
||||||
"pico-args 0.4.2",
|
"pico-args",
|
||||||
"regex",
|
"regex",
|
||||||
"regex-syntax 0.6.29",
|
"regex-syntax",
|
||||||
"string_cache",
|
"string_cache",
|
||||||
"term",
|
"term",
|
||||||
"tiny-keccak",
|
"tiny-keccak",
|
||||||
|
@ -230,9 +230,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lalrpop-util"
|
name = "lalrpop-util"
|
||||||
version = "0.19.9"
|
version = "0.20.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e5c1f7869c94d214466c5fd432dfed12c379fd87786768d36455892d46b18edd"
|
checksum = "3f35c735096c0293d313e8f2a641627472b83d01b937177fe76e5e2708d31e0d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"regex",
|
"regex",
|
||||||
]
|
]
|
||||||
|
@ -281,8 +281,9 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"lalrpop",
|
"lalrpop",
|
||||||
"lalrpop-util",
|
"lalrpop-util",
|
||||||
"pico-args 0.5.0",
|
"pico-args",
|
||||||
"regex",
|
"regex",
|
||||||
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -339,12 +340,6 @@ dependencies = [
|
||||||
"siphasher",
|
"siphasher",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pico-args"
|
|
||||||
version = "0.4.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pico-args"
|
name = "pico-args"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
@ -359,18 +354,18 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.56"
|
version = "1.0.66"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
|
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.26"
|
version = "1.0.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
|
checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
@ -403,15 +398,9 @@ checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
"regex-syntax 0.7.1",
|
"regex-syntax",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex-syntax"
|
|
||||||
version = "0.6.29"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
|
@ -471,9 +460,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.15"
|
version = "2.0.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
|
checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -493,18 +482,18 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.40"
|
version = "1.0.43"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
|
checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "1.0.40"
|
version = "1.0.43"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
|
checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
|
@ -4,10 +4,11 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
lalrpop = "0.19.9"
|
lalrpop = "0.20.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.71"
|
anyhow = "1.0.72"
|
||||||
lalrpop-util = { version = "0.19.9", features = ["lexer"] }
|
lalrpop-util = { version = "0.20.0", features = ["lexer"] }
|
||||||
pico-args = { version = "0.5.0", features = ["eq-separator", "short-space-opt", "combined-flags"] }
|
pico-args = { version = "0.5.0", features = ["eq-separator", "short-space-opt", "combined-flags"] }
|
||||||
regex = "1"
|
regex = "1"
|
||||||
|
thiserror = "1.0.43"
|
||||||
|
|
59
src/cli.rs
59
src/cli.rs
|
@ -1,45 +1,60 @@
|
||||||
use std::path::PathBuf;
|
use std::{ffi::OsString, path::PathBuf};
|
||||||
|
|
||||||
use pico_args::Arguments;
|
use pico_args::Arguments;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Debug)]
|
pub const USAGE: &str = "
|
||||||
|
USAGE:
|
||||||
|
mul [-h|--help] (parse <FILE> | evaluate <FILE> | repl)
|
||||||
|
|
||||||
|
FLAGS:
|
||||||
|
-h, --help Prints help information (this text)
|
||||||
|
|
||||||
|
COMMANDS:
|
||||||
|
parse <FILE> Parse the given file and print a serialized AST to stdout
|
||||||
|
evaluate, eval <FILE> Evaluate the given file
|
||||||
|
repl Start the Read-Evaluate-Print-Loop
|
||||||
|
";
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
ArgsError(pico_args::Error),
|
#[error("Problem while parsing arguments, {0}")]
|
||||||
|
ArgsError(#[from] pico_args::Error),
|
||||||
|
#[error("Invalid command '{0}'\n{USAGE}")]
|
||||||
InvalidCommand(String),
|
InvalidCommand(String),
|
||||||
PrintUsage,
|
#[error("Unrecognized arguments {0:?}\n{USAGE}")]
|
||||||
}
|
UnrecognizedArguments(Vec<OsString>),
|
||||||
|
|
||||||
impl From<pico_args::Error> for Error {
|
|
||||||
fn from(value: pico_args::Error) -> Self {
|
|
||||||
Error::ArgsError(value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
Parse { source: PathBuf },
|
Parse { path: PathBuf },
|
||||||
Run { source: PathBuf },
|
Evaluate { path: PathBuf },
|
||||||
Repl,
|
Repl,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Command {
|
impl Command {
|
||||||
pub fn from_environment() -> Result<Self, Error> {
|
pub fn from_environment() -> Result<Option<Self>, Error> {
|
||||||
let mut args = Arguments::from_env();
|
let mut args = Arguments::from_env();
|
||||||
if args.contains(["-h", "--help"]) {
|
if args.contains(["-h", "--help"]) {
|
||||||
return Err(Error::PrintUsage);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
let cmd = match args.subcommand()?.as_deref() {
|
let cmd = match args.subcommand()?.as_deref() {
|
||||||
Some("parse") => {
|
Some("parse") => {
|
||||||
let source = args.free_from_str()?;
|
let path = args.free_from_str()?;
|
||||||
Command::Parse { source }
|
Command::Parse { path }
|
||||||
}
|
}
|
||||||
Some("run") => {
|
Some("evaluate") | Some("eval") => {
|
||||||
let source = args.free_from_str()?;
|
let path = args.free_from_str()?;
|
||||||
Command::Run { source }
|
Command::Evaluate { path }
|
||||||
}
|
}
|
||||||
Some("repl") => Command::Repl,
|
Some("repl") | None => Command::Repl,
|
||||||
Some(cmd) => return Err(Error::InvalidCommand(cmd.to_string())),
|
Some(cmd) => return Err(Error::InvalidCommand(cmd.to_string())),
|
||||||
None => return Err(Error::PrintUsage),
|
|
||||||
};
|
};
|
||||||
Ok(cmd)
|
let remaining = args.finish();
|
||||||
|
if remaining.is_empty() {
|
||||||
|
Ok(Some(cmd))
|
||||||
|
} else {
|
||||||
|
Err(Error::UnrecognizedArguments(remaining))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
74
src/main.rs
74
src/main.rs
|
@ -1,75 +1,57 @@
|
||||||
use std::fs::read_to_string;
|
use std::fs::read_to_string;
|
||||||
use std::io::{stdin, stdout, BufRead, Write};
|
use std::io::{stdin, stdout, BufRead, Write};
|
||||||
use std::path::Path;
|
|
||||||
use std::process::exit;
|
|
||||||
|
|
||||||
use mul::cli::{Command, Error};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use mul::phase::parse::{parse_program, parse_repl_line};
|
|
||||||
|
|
||||||
fn main() -> Result<(), Error> {
|
use mul::cli::{Command, USAGE};
|
||||||
let command = Command::from_environment()?;
|
use mul::phase::parse::{parse_interactive_entry, parse_program};
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
let Some(command) = Command::from_environment()? else {
|
||||||
|
eprintln!("mul\n{USAGE}");
|
||||||
|
return Ok(())
|
||||||
|
};
|
||||||
match command {
|
match command {
|
||||||
Command::Parse { source } => parse(source.as_path()),
|
Command::Parse { path } => {
|
||||||
Command::Run { source } => run(source.as_path()),
|
let source = read_to_string(&path)
|
||||||
|
.with_context(|| format!("failed to open {}", path.display()))?;
|
||||||
|
let program = parse_program(source.as_str())
|
||||||
|
.map_err(|e| anyhow!("{e}"))
|
||||||
|
.with_context(|| format!("failed to parse {}", path.display()))?;
|
||||||
|
println!("{program:#?}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Command::Evaluate { path } => {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
Command::Repl => repl(),
|
Command::Repl => repl(),
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse(file: &Path) {
|
fn repl() -> Result<()> {
|
||||||
let content = read_to_string(file).expect("Failed to read file");
|
|
||||||
let program = parse_program(content.as_str()).unwrap_or_else(|e| {
|
|
||||||
eprintln!("{e}");
|
|
||||||
exit(1);
|
|
||||||
});
|
|
||||||
println!("{program:#?}");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(file: &Path) {
|
|
||||||
let content = read_to_string(file).expect("Failed to read file");
|
|
||||||
let program = parse_program(content.as_str()).unwrap_or_else(|e| {
|
|
||||||
eprintln!("{e}");
|
|
||||||
exit(1);
|
|
||||||
});
|
|
||||||
// let v = interpreter.interpret(&program).unwrap_or_else(|e| {
|
|
||||||
// eprintln!("{e}");
|
|
||||||
// exit(1);
|
|
||||||
// });
|
|
||||||
// println!("{v:?}")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn repl() {
|
|
||||||
let mut stdin = stdin().lock();
|
let mut stdin = stdin().lock();
|
||||||
let mut stdout = stdout().lock();
|
let mut stdout = stdout().lock();
|
||||||
|
println!("welcome to M U L");
|
||||||
loop {
|
loop {
|
||||||
let mut line = String::new();
|
let mut line = String::new();
|
||||||
print!("> ");
|
print!("> ");
|
||||||
stdout.flush().unwrap();
|
stdout.flush()?;
|
||||||
stdin
|
stdin
|
||||||
.read_line(&mut line)
|
.read_line(&mut line)
|
||||||
.expect("Failed to read from stdin");
|
.context("Failed to read from stdin")?;
|
||||||
if line.trim().is_empty() {
|
if line.trim().is_empty() {
|
||||||
println!();
|
println!();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if !line.trim_end().ends_with(';') {
|
let parsed = match parse_interactive_entry(&line) {
|
||||||
line.push(';');
|
|
||||||
}
|
|
||||||
let parsed = match parse_repl_line(&line) {
|
|
||||||
Ok(parsed) => parsed,
|
Ok(parsed) => parsed,
|
||||||
Err(lalrpop_util::ParseError::UnrecognizedEOF { .. }) => break,
|
Err(lalrpop_util::ParseError::UnrecognizedEof { .. }) => break,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("{e}");
|
eprintln!("{e}");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
println!("{parsed:?}");
|
println!("{parsed:?}");
|
||||||
// match intepreter.interpret_statement(&parsed) {
|
|
||||||
// Ok(value) => println!("{value:?}"),
|
|
||||||
// Err(e) => {
|
|
||||||
// eprintln!("{e}");
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,7 @@
|
||||||
pub mod parse;
|
pub mod parse;
|
||||||
|
// |
|
||||||
|
// |
|
||||||
|
// syntax
|
||||||
|
// |
|
||||||
|
// v
|
||||||
|
pub mod evaluate;
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
use lalrpop_util::{lalrpop_mod, lexer::Token, ParseError};
|
use lalrpop_util::{lalrpop_mod, lexer::Token, ParseError};
|
||||||
|
|
||||||
use crate::syntax::{self, Line};
|
use crate::syntax::{InteractiveEntry, Program};
|
||||||
|
|
||||||
lalrpop_mod!(parser, "/phase/parse/parser.rs");
|
lalrpop_mod!(parser, "/phase/parse/parser.rs");
|
||||||
|
|
||||||
pub type Result<'input, T> = std::result::Result<T, ParseError<usize, Token<'input>, &'static str>>;
|
pub type Result<'input, T> = std::result::Result<T, ParseError<usize, Token<'input>, &'static str>>;
|
||||||
|
|
||||||
pub fn parse_program(input: &str) -> Result<syntax::Program> {
|
pub fn parse_program(input: &'_ str) -> Result<'_, Program> {
|
||||||
parser::ProgramParser::new().parse(input)
|
parser::ProgramParser::new().parse(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_repl_line(input: &str) -> Result<Line> {
|
pub fn parse_interactive_entry(input: &'_ str) -> Result<'_, InteractiveEntry> {
|
||||||
parser::LineParser::new().parse(input)
|
parser::InteractiveEntryParser::new().parse(input)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use crate::syntax::{
|
||||||
Block,
|
Block,
|
||||||
Expression,
|
Expression,
|
||||||
Item,
|
Item,
|
||||||
Line,
|
InteractiveEntry,
|
||||||
Name,
|
Name,
|
||||||
Program,
|
Program,
|
||||||
};
|
};
|
||||||
|
@ -12,10 +12,10 @@ grammar;
|
||||||
|
|
||||||
pub Program: Program = Item*;
|
pub Program: Program = Item*;
|
||||||
|
|
||||||
pub Line: Line = {
|
pub InteractiveEntry: InteractiveEntry = {
|
||||||
Item => Line::Item(<>),
|
Item => InteractiveEntry::Item(<>),
|
||||||
Binding => Line::Binding(<>),
|
Binding => InteractiveEntry::Binding(<>),
|
||||||
<Expression> ";"? => Line::Expression(<>),
|
<Expression> ";"? => InteractiveEntry::Expression(<>),
|
||||||
}
|
}
|
||||||
|
|
||||||
Item: Item = {
|
Item: Item = {
|
||||||
|
|
|
@ -1,14 +1,20 @@
|
||||||
|
//! Syntax elements.
|
||||||
|
|
||||||
|
/// Identifiers.
|
||||||
pub type Name = String;
|
pub type Name = String;
|
||||||
|
|
||||||
|
/// The rough equivalent of a module.
|
||||||
pub type Program = Vec<Item>;
|
pub type Program = Vec<Item>;
|
||||||
|
|
||||||
|
/// A fragment of code entered in, e.g., the REPL.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Line {
|
pub enum InteractiveEntry {
|
||||||
Item(Item),
|
Item(Item),
|
||||||
Binding(Binding),
|
Binding(Binding),
|
||||||
Expression(Expression),
|
Expression(Expression),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A top level definition.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Item {
|
pub enum Item {
|
||||||
Fn {
|
Fn {
|
||||||
|
@ -18,9 +24,11 @@ pub enum Item {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Syntactic mapping from an identifier to an expression.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Binding(pub Name, pub Expression);
|
pub struct Binding(pub Name, pub Expression);
|
||||||
|
|
||||||
|
/// Some individually addressable syntax node, either atomic or compound.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Expression {
|
pub enum Expression {
|
||||||
Variable(Name),
|
Variable(Name),
|
||||||
|
@ -37,6 +45,7 @@ pub enum Expression {
|
||||||
Integer(i64),
|
Integer(i64),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A sequence of bindings with a final result.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Block {
|
pub struct Block {
|
||||||
pub bindings: Vec<Binding>,
|
pub bindings: Vec<Binding>,
|
||||||
|
|
Loading…
Reference in New Issue