mirror of
https://github.com/fluencelabs/aquavm
synced 2024-12-04 15:20:16 +00:00
air-beautifier
lib and air-beautify
binary (#266)
The `air-beautify` util takes an AIR script input with Lisp-like syntax and transforms it into experimental indentation-based syntax. Closing #184.
This commit is contained in:
parent
3c23ab735c
commit
1c0ff2c979
107
Cargo.lock
generated
107
Cargo.lock
generated
@ -46,6 +46,24 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "air-beautifier"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"air-parser",
|
||||
"itertools 0.10.3",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "air-beautify"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"air-beautifier",
|
||||
"anyhow",
|
||||
"clap 3.1.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "air-execution-info-collector"
|
||||
version = "0.1.0"
|
||||
@ -376,10 +394,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"textwrap",
|
||||
"textwrap 0.11.0",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags",
|
||||
"clap_derive",
|
||||
"clap_lex",
|
||||
"indexmap",
|
||||
"lazy_static",
|
||||
"strsim",
|
||||
"termcolor",
|
||||
"textwrap 0.15.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "3.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25320346e922cffe59c0bbc5410c8d8784509efb321488971081313cb1e1a33c"
|
||||
dependencies = [
|
||||
"heck 0.4.0",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213"
|
||||
dependencies = [
|
||||
"os_str_bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cloudabi"
|
||||
version = "0.0.3"
|
||||
@ -498,7 +555,7 @@ checksum = "ab327ed7354547cc2ef43cbe20ef68b988e70b4b593cbd66a2a61733123a3d23"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"cast",
|
||||
"clap",
|
||||
"clap 2.34.0",
|
||||
"criterion-plot",
|
||||
"csv",
|
||||
"itertools 0.10.3",
|
||||
@ -959,6 +1016,12 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
@ -1518,6 +1581,12 @@ version = "11.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
|
||||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "6.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "029d8d0b2f198229de29dca79676f2738ff952edf3fde542eb8bf94d8c21b435"
|
||||
|
||||
[[package]]
|
||||
name = "output_vt100"
|
||||
version = "0.1.3"
|
||||
@ -1710,6 +1779,30 @@ dependencies = [
|
||||
"output_vt100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-hack"
|
||||
version = "0.5.19"
|
||||
@ -2061,7 +2154,7 @@ version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"heck 0.3.3",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
@ -2119,6 +2212,12 @@ dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.30"
|
||||
@ -2294,7 +2393,7 @@ version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7c2bb690b44cb1b0fdcc54d4998d21f8bdaf706b93775425e440b174f39ad16"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"heck 0.3.3",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
|
@ -13,7 +13,9 @@ members = [
|
||||
"crates/air-lib/polyplets",
|
||||
"crates/air-lib/test-utils",
|
||||
"crates/air-lib/trace-handler",
|
||||
"crates/beautifier",
|
||||
"crates/data-store",
|
||||
"tools/cli/air-beautify",
|
||||
]
|
||||
|
||||
exclude = [
|
||||
|
19
crates/beautifier/Cargo.toml
Normal file
19
crates/beautifier/Cargo.toml
Normal file
@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "air-beautifier"
|
||||
version = "0.1.0"
|
||||
description = "AIR human-readable format transformer library"
|
||||
authors = ["Fluence Labs"]
|
||||
edition = "2018"
|
||||
license = "Apache-2.0"
|
||||
repository = "https://github.com/fluencelabs/aquavm/tree/master/crates/beautifier"
|
||||
publish = false
|
||||
keywords = ["fluence", "air", "beautifier"]
|
||||
|
||||
[lib]
|
||||
name = "air_beautifier"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
air-parser = { path = "../air-lib/air-parser" }
|
||||
itertools = "0.10.3"
|
||||
thiserror = "1.0.30"
|
220
crates/beautifier/src/beautifier.rs
Normal file
220
crates/beautifier/src/beautifier.rs
Normal file
@ -0,0 +1,220 @@
|
||||
/*
|
||||
* Copyright 2022 Fluence Labs Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#![deny(
|
||||
dead_code,
|
||||
nonstandard_style,
|
||||
unused_imports,
|
||||
unused_mut,
|
||||
unused_variables,
|
||||
unused_unsafe,
|
||||
unreachable_patterns
|
||||
)]
|
||||
|
||||
use air_parser::ast;
|
||||
|
||||
use std::fmt::Display;
|
||||
use std::io;
|
||||
|
||||
pub const DEFAULT_INDENT_STEP: usize = 4;
|
||||
|
||||
macro_rules! multiline {
|
||||
($beautifier:expr, $indent:expr $(; $fmt1:literal $(, $arg1:expr)*; $nest:expr)+) => ({
|
||||
let indent_step = $beautifier.indent_step;
|
||||
$({
|
||||
let out = &mut $beautifier.output;
|
||||
$crate::beautifier::fmt_indent(out, $indent)?;
|
||||
writeln!(out, $fmt1 $(, $arg1)*)?;
|
||||
}
|
||||
$crate::beautifier::Beautifier::beautify_walker($beautifier, $nest, $indent + indent_step)?;
|
||||
)+
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
macro_rules! compound {
|
||||
($beautifier:expr, $indent:expr, $instruction:expr) => ({
|
||||
multiline!(
|
||||
$beautifier, $indent;
|
||||
"{}:", $instruction;
|
||||
&$instruction.instruction
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
fn fmt_indent(output: &mut impl io::Write, indent: usize) -> io::Result<()> {
|
||||
write!(output, "{:indent$}", "", indent = indent)
|
||||
}
|
||||
|
||||
struct CallArgs<'ctx, 'i>(&'ctx [ast::Value<'i>]);
|
||||
|
||||
impl<'ctx, 'i> Display for CallArgs<'ctx, 'i> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
use itertools::Itertools as _;
|
||||
|
||||
f.write_fmt(format_args!("{}", self.0.iter().format(", ")))
|
||||
}
|
||||
}
|
||||
|
||||
struct CallTriplet<'ctx, 'i>(&'ctx ast::Triplet<'i>);
|
||||
|
||||
impl<'ctx, 'i> Display for CallTriplet<'ctx, 'i> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_fmt(format_args!(
|
||||
"{} ({}, {})",
|
||||
self.0.peer_pk, self.0.service_id, self.0.function_name
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Error produced by the Beautifier.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum BeautifyError {
|
||||
#[error("{0}")]
|
||||
Parse(String),
|
||||
#[error(transparent)]
|
||||
Io(#[from] io::Error),
|
||||
}
|
||||
|
||||
/// AIR beautifier.
|
||||
pub struct Beautifier<W: io::Write> {
|
||||
output: W,
|
||||
indent_step: usize,
|
||||
}
|
||||
|
||||
impl<W: io::Write> Beautifier<W> {
|
||||
/// Beautifier for the output with default indentation step.
|
||||
pub fn new(output: W) -> Self {
|
||||
Self {
|
||||
output,
|
||||
indent_step: DEFAULT_INDENT_STEP,
|
||||
}
|
||||
}
|
||||
|
||||
/// Beautifier for the output with custom indentation step.
|
||||
pub fn new_with_indent(output: W, indent_step: usize) -> Self {
|
||||
Self {
|
||||
output,
|
||||
indent_step,
|
||||
}
|
||||
}
|
||||
|
||||
/// Unwrap the Beautifier into the underlying writer.
|
||||
pub fn into_inner(self) -> W {
|
||||
self.output
|
||||
}
|
||||
|
||||
/// Emit beautified code for the `air_script`.
|
||||
pub fn beautify(&mut self, air_script: &str) -> Result<(), BeautifyError> {
|
||||
let tree = air_parser::parse(air_script).map_err(BeautifyError::Parse)?;
|
||||
self.beautify_ast(tree)
|
||||
}
|
||||
|
||||
/// Emit beautified code for the `ast`.
|
||||
pub fn beautify_ast<'i>(
|
||||
&mut self,
|
||||
ast: impl AsRef<ast::Instruction<'i>>,
|
||||
) -> Result<(), BeautifyError> {
|
||||
Ok(self.beautify_walker(ast.as_ref(), 0)?)
|
||||
}
|
||||
|
||||
fn beautify_walker(&mut self, node: &ast::Instruction, indent: usize) -> io::Result<()> {
|
||||
match node {
|
||||
ast::Instruction::Call(call) => self.beautify_call(call, indent),
|
||||
ast::Instruction::Ap(ap) => self.beautify_simple(ap, indent),
|
||||
ast::Instruction::Seq(seq) => self.beautify_seq(seq, indent),
|
||||
ast::Instruction::Par(par) => self.beautify_par(par, indent),
|
||||
ast::Instruction::Xor(xor) => self.beautify_xor(xor, indent),
|
||||
ast::Instruction::Match(match_) => self.beautify_match(match_, indent),
|
||||
ast::Instruction::MisMatch(mismatch) => self.beautify_mismatch(mismatch, indent),
|
||||
ast::Instruction::Fail(fail) => self.beautify_simple(fail, indent),
|
||||
ast::Instruction::FoldScalar(fold_scalar) => {
|
||||
self.beautify_fold_scalar(fold_scalar, indent)
|
||||
}
|
||||
ast::Instruction::FoldStream(fold_stream) => {
|
||||
self.beautify_fold_stream(fold_stream, indent)
|
||||
}
|
||||
ast::Instruction::New(new) => self.beautify_new(new, indent),
|
||||
ast::Instruction::Next(next) => self.beautify_simple(next, indent),
|
||||
ast::Instruction::Null(null) => self.beautify_simple(null, indent),
|
||||
ast::Instruction::Error => self.beautify_simple("error", indent),
|
||||
}
|
||||
}
|
||||
|
||||
fn beautify_call(&mut self, call: &ast::Call, indent: usize) -> io::Result<()> {
|
||||
fmt_indent(&mut self.output, indent)?;
|
||||
match &call.output {
|
||||
ast::CallOutputValue::Variable(v) => write!(&mut self.output, "{} <- ", v)?,
|
||||
ast::CallOutputValue::None => {}
|
||||
}
|
||||
writeln!(
|
||||
&mut self.output,
|
||||
"call {} [{}]",
|
||||
CallTriplet(&call.triplet),
|
||||
CallArgs(call.args.as_slice())
|
||||
)
|
||||
}
|
||||
|
||||
fn beautify_simple(&mut self, instruction: impl Display, indent: usize) -> io::Result<()> {
|
||||
fmt_indent(&mut self.output, indent)?;
|
||||
writeln!(&mut self.output, "{}", instruction)
|
||||
}
|
||||
|
||||
fn beautify_seq(&mut self, seq: &ast::Seq, indent: usize) -> io::Result<()> {
|
||||
self.beautify_walker(&seq.0, indent)?;
|
||||
self.beautify_walker(&seq.1, indent)
|
||||
}
|
||||
|
||||
fn beautify_par(&mut self, par: &ast::Par, indent: usize) -> io::Result<()> {
|
||||
multiline!(
|
||||
self, indent;
|
||||
"par:";
|
||||
&par.0;
|
||||
"|"; // TODO: SHOULD BE UNINDENTED AS PER SPEC; OR WE MAY CHANGE THE SPEC
|
||||
&par.1
|
||||
)
|
||||
}
|
||||
|
||||
fn beautify_xor(&mut self, xor: &ast::Xor, indent: usize) -> io::Result<()> {
|
||||
multiline!(
|
||||
self, indent;
|
||||
"try:";
|
||||
&xor.0;
|
||||
"catch:";
|
||||
&xor.1
|
||||
)
|
||||
}
|
||||
|
||||
fn beautify_match(&mut self, match_: &ast::Match, indent: usize) -> io::Result<()> {
|
||||
compound!(self, indent, match_)
|
||||
}
|
||||
|
||||
fn beautify_mismatch(&mut self, mismatch: &ast::MisMatch, indent: usize) -> io::Result<()> {
|
||||
compound!(self, indent, mismatch)
|
||||
}
|
||||
|
||||
fn beautify_fold_scalar(&mut self, fold: &ast::FoldScalar, indent: usize) -> io::Result<()> {
|
||||
compound!(self, indent, fold)
|
||||
}
|
||||
|
||||
fn beautify_fold_stream(&mut self, fold: &ast::FoldStream, indent: usize) -> io::Result<()> {
|
||||
compound!(self, indent, fold)
|
||||
}
|
||||
|
||||
fn beautify_new(&mut self, new: &ast::New, indent: usize) -> io::Result<()> {
|
||||
compound!(self, indent, new)
|
||||
}
|
||||
}
|
52
crates/beautifier/src/lib.rs
Normal file
52
crates/beautifier/src/lib.rs
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2022 Fluence Labs Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#![deny(
|
||||
dead_code,
|
||||
nonstandard_style,
|
||||
unused_imports,
|
||||
unused_mut,
|
||||
unused_variables,
|
||||
unused_unsafe,
|
||||
unreachable_patterns
|
||||
)]
|
||||
|
||||
mod beautifier;
|
||||
|
||||
pub use crate::beautifier::{Beautifier, BeautifyError, DEFAULT_INDENT_STEP};
|
||||
|
||||
use std::io;
|
||||
|
||||
/// Beautify the `air_script` with default settings to the `output`.
|
||||
pub fn beautify(air_script: &str, output: &mut impl io::Write) -> Result<(), BeautifyError> {
|
||||
let mut beautifier = Beautifier::new(output);
|
||||
beautifier.beautify(air_script)
|
||||
}
|
||||
|
||||
/// Beautify the `air_script` to a string with default settings.
|
||||
/// Return error on parsing error.
|
||||
pub fn beautify_to_string(air_script: &str) -> Result<String, String> {
|
||||
let ast = air_parser::parse(air_script)?;
|
||||
let mut buffer = vec![];
|
||||
let mut beautifier = Beautifier::new(&mut buffer);
|
||||
|
||||
beautifier.beautify_ast(&ast).unwrap();
|
||||
// Safety: safe because Beautifier produces valid utf8 strings
|
||||
Ok(unsafe { String::from_utf8_unchecked(buffer) })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
351
crates/beautifier/src/tests/beautifier.rs
Normal file
351
crates/beautifier/src/tests/beautifier.rs
Normal file
@ -0,0 +1,351 @@
|
||||
/*
|
||||
* Copyright 2022 Fluence Labs Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#![deny(
|
||||
dead_code,
|
||||
nonstandard_style,
|
||||
unused_imports,
|
||||
unused_mut,
|
||||
unused_variables,
|
||||
unused_unsafe,
|
||||
unreachable_patterns
|
||||
)]
|
||||
|
||||
use crate::{beautify_to_string, Beautifier};
|
||||
|
||||
#[test]
|
||||
fn ap_with_literal() {
|
||||
let script = r#"(ap "some_string" $stream)"#;
|
||||
let output = beautify_to_string(script).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
output,
|
||||
r#"ap "some_string" $stream
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ap_with_number() {
|
||||
let script = "(ap -100 $stream)";
|
||||
let output = beautify_to_string(script).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
output,
|
||||
r#"ap -100 $stream
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ap_with_bool() {
|
||||
let script = "(ap true $stream)";
|
||||
let output = beautify_to_string(script).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
output,
|
||||
r#"ap true $stream
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ap_with_last_error() {
|
||||
let script = "(ap %last_error%.$.message! $stream)";
|
||||
let output = beautify_to_string(script).unwrap();
|
||||
|
||||
assert_eq!(output, "ap %last_error%.$.message $stream\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ap_with_empty_array() {
|
||||
let script = "(ap [] $stream)";
|
||||
let output = beautify_to_string(script).unwrap();
|
||||
|
||||
assert_eq!(output, "ap [] $stream\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ap_with_init_peer_id() {
|
||||
let script = "(ap %init_peer_id% $stream)";
|
||||
let output = beautify_to_string(script).unwrap();
|
||||
|
||||
assert_eq!(output, "ap %init_peer_id% $stream\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ap_with_timestamp() {
|
||||
let script = "(ap %timestamp% $stream)";
|
||||
let output = beautify_to_string(script).unwrap();
|
||||
|
||||
assert_eq!(output, "ap %timestamp% $stream\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ap_with_ttl() {
|
||||
let script = r#"
|
||||
(ap %ttl% $stream)
|
||||
"#;
|
||||
let output = beautify_to_string(script).unwrap();
|
||||
|
||||
assert_eq!(output, "ap %ttl% $stream\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seq() {
|
||||
let script = "(seq (null) (null))";
|
||||
let output = beautify_to_string(script).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
output,
|
||||
"null
|
||||
null
|
||||
"
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seq_nested_pre() {
|
||||
let script = "(seq (seq (null) (null)) (null))";
|
||||
let output = beautify_to_string(script).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
output,
|
||||
"null
|
||||
null
|
||||
null
|
||||
"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seq_nested_post() {
|
||||
let script = "(seq (null) (seq (null) (null)))";
|
||||
let output = beautify_to_string(script).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
output,
|
||||
"null
|
||||
null
|
||||
null
|
||||
"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn par() {
|
||||
let script = "(par (null) (null))";
|
||||
let output = beautify_to_string(script).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
output,
|
||||
"par:
|
||||
null
|
||||
|
|
||||
null
|
||||
"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn match_() {
|
||||
let script = r#"(seq
|
||||
(seq
|
||||
(call "a" ("" "") [] a)
|
||||
(call "b" ("" "") [] b))
|
||||
(match a b (null)))"#;
|
||||
let output = beautify_to_string(script).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
output,
|
||||
r#"a <- call "a" ("", "") []
|
||||
b <- call "b" ("", "") []
|
||||
match a b:
|
||||
null
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mismatch() {
|
||||
let script = r#"(seq
|
||||
(seq
|
||||
(call "a" ("" "") [] a)
|
||||
(call "b" ("" "") [] b))
|
||||
(mismatch a b (null)))"#;
|
||||
let output = beautify_to_string(script).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
output,
|
||||
r#"a <- call "a" ("", "") []
|
||||
b <- call "b" ("", "") []
|
||||
mismatch a b:
|
||||
null
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fail_last_error() {
|
||||
let script = "(fail %last_error%)";
|
||||
let output = beautify_to_string(script).unwrap();
|
||||
|
||||
assert_eq!(output, "fail %last_error%\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fail_expr() {
|
||||
let script = "(fail var)";
|
||||
let output = beautify_to_string(script).unwrap();
|
||||
|
||||
assert_eq!(output, "fail var\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fail_common() {
|
||||
let script = r#"(fail 123 "Message")"#;
|
||||
let output = beautify_to_string(script).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
output,
|
||||
r#"fail 123 "Message"
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fold_scalar() {
|
||||
let script = r#"(seq (call "it" ("" "") [] var) (fold var i (null)))"#;
|
||||
let output = beautify_to_string(script).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
output,
|
||||
r#"var <- call "it" ("", "") []
|
||||
fold var i:
|
||||
null
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fold_stream() {
|
||||
let script = r#"(seq (call "it" ("" "") [] $var) (fold $var i (null)))"#;
|
||||
let output = beautify_to_string(script).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
output,
|
||||
r#"$var <- call "it" ("", "") []
|
||||
fold $var i:
|
||||
null
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_var() {
|
||||
let script = "(call \"{0}\" (\"a\" \"b\") [\"stream_1\" \"stream_2\"] streamvar)";
|
||||
let output = beautify_to_string(script).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
output,
|
||||
"streamvar <- call \"{0}\" (\"a\", \"b\") [\"stream_1\", \"stream_2\"]\n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_novar() {
|
||||
let script = r#"(call "{0}" ("a" "b") ["stream_1" "stream_2"])"#;
|
||||
let output = beautify_to_string(script).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
output,
|
||||
r#"call "{0}" ("a", "b") ["stream_1", "stream_2"]
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_noargs() {
|
||||
let script = r#"(call "{0}" ("a" "b") [])"#;
|
||||
let output = beautify_to_string(script).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
output,
|
||||
r#"call "{0}" ("a", "b") []
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn next() {
|
||||
let script = r#"(seq (call "{0}" ("a" "b") ["stream_1"] j) (fold j i (next i)))"#;
|
||||
let output = beautify_to_string(script).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
output,
|
||||
r#"j <- call "{0}" ("a", "b") ["stream_1"]
|
||||
fold j i:
|
||||
next i
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new() {
|
||||
let script = "(new var (seq (null) (null)))";
|
||||
let output = beautify_to_string(script).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
output,
|
||||
"new var:
|
||||
null
|
||||
null
|
||||
"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn null() {
|
||||
let script = "(null)";
|
||||
let output = beautify_to_string(script).unwrap();
|
||||
|
||||
assert_eq!(output, "null\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_indent_step() {
|
||||
let mut output = vec![];
|
||||
let mut beautifier = Beautifier::new_with_indent(&mut output, 2);
|
||||
let script = "(new var1 (new var (seq (null) (null))))";
|
||||
beautifier.beautify(script).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
String::from_utf8(output).unwrap(),
|
||||
"new var1:
|
||||
new var:
|
||||
null
|
||||
null
|
||||
"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deeply_nested() {
|
||||
let script = include_str!("deeply_nested.air");
|
||||
let output = beautify_to_string(script).unwrap();
|
||||
let expected = include_str!("deeply_nested_expected.txt");
|
||||
assert_eq!(output, expected);
|
||||
}
|
26
crates/beautifier/src/tests/deeply_nested.air
Normal file
26
crates/beautifier/src/tests/deeply_nested.air
Normal file
@ -0,0 +1,26 @@
|
||||
(xor
|
||||
(seq
|
||||
(seq
|
||||
(call "me" ("" "") [] var)
|
||||
(ap var.$.value data))
|
||||
(par
|
||||
(par
|
||||
(call "other1" ("set" "") [data])
|
||||
(call "other2" ("insert" "") [data]))
|
||||
(seq
|
||||
(ap "key" key)
|
||||
(seq
|
||||
(call "other3" ("" "") [key data] $values)
|
||||
(fold $values i
|
||||
(seq
|
||||
(xor
|
||||
(seq
|
||||
(call "other2" ("insert" "") [i] j)
|
||||
(call "other3" ("report" "") [j]))
|
||||
(fail 3 "fail to fold"))
|
||||
(next i)))))))
|
||||
(par
|
||||
(seq
|
||||
(call "other1" ("report" "error") [%last_error%] rep)
|
||||
(call "other2" ("report" "error") [%last_error% rep]))
|
||||
(call "other3" ("report" "error") [%last_error%])))
|
24
crates/beautifier/src/tests/deeply_nested_expected.txt
Normal file
24
crates/beautifier/src/tests/deeply_nested_expected.txt
Normal file
@ -0,0 +1,24 @@
|
||||
try:
|
||||
var <- call "me" ("", "") []
|
||||
ap var.$.value data
|
||||
par:
|
||||
par:
|
||||
call "other1" ("set", "") [data]
|
||||
|
|
||||
call "other2" ("insert", "") [data]
|
||||
|
|
||||
ap "key" key
|
||||
$values <- call "other3" ("", "") [key, data]
|
||||
fold $values i:
|
||||
try:
|
||||
j <- call "other2" ("insert", "") [i]
|
||||
call "other3" ("report", "") [j]
|
||||
catch:
|
||||
fail 3 "fail to fold"
|
||||
next i
|
||||
catch:
|
||||
par:
|
||||
rep <- call "other1" ("report", "error") [%last_error%]
|
||||
call "other2" ("report", "error") [%last_error%, rep]
|
||||
|
|
||||
call "other3" ("report", "error") [%last_error%]
|
60
crates/beautifier/src/tests/mod.rs
Normal file
60
crates/beautifier/src/tests/mod.rs
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2022 Fluence Labs Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#![deny(
|
||||
dead_code,
|
||||
nonstandard_style,
|
||||
unused_imports,
|
||||
unused_mut,
|
||||
unused_variables,
|
||||
unused_unsafe,
|
||||
unreachable_patterns
|
||||
)]
|
||||
|
||||
mod beautifier;
|
||||
|
||||
use crate::{beautify, beautify_to_string, BeautifyError};
|
||||
|
||||
#[test]
|
||||
fn beautify_valid() {
|
||||
let air_script = "(seq (null) (null))";
|
||||
let mut buffer = vec![];
|
||||
let res = beautify(air_script, &mut buffer);
|
||||
assert!(matches!(res, Ok(())));
|
||||
assert_eq!(std::str::from_utf8(&buffer).unwrap(), "null\nnull\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn beautify_invalid() {
|
||||
let air_script = "(seq (null))";
|
||||
let mut buffer = vec![];
|
||||
let res = beautify(air_script, &mut buffer);
|
||||
assert!(matches!(res, Err(BeautifyError::Parse(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn beautify_to_string_valid() {
|
||||
let air_script = "(seq (null) (null))";
|
||||
let res = beautify_to_string(air_script).unwrap();
|
||||
assert_eq!(res, "null\nnull\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn beautify_to_string_invalid() {
|
||||
let air_script = "(seq (null))";
|
||||
let res = beautify_to_string(air_script);
|
||||
assert!(matches!(res, Err(_)));
|
||||
}
|
14
tools/cli/air-beautify/Cargo.toml
Normal file
14
tools/cli/air-beautify/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "air-beautify"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "AIR human-readable format transformer binary"
|
||||
authors = ["Fluence Labs"]
|
||||
license = "Apache-2.0"
|
||||
publish = false
|
||||
keywords = ["fluence", "air", "beautifier"]
|
||||
|
||||
[dependencies]
|
||||
air-beautifier = { path = "../../../crates/beautifier" }
|
||||
clap = { version = "3.1.18", features = ["derive"] }
|
||||
anyhow = "1.0.56"
|
80
tools/cli/air-beautify/src/main.rs
Normal file
80
tools/cli/air-beautify/src/main.rs
Normal file
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2022 Fluence Labs Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#![deny(
|
||||
dead_code,
|
||||
nonstandard_style,
|
||||
unused_imports,
|
||||
unused_mut,
|
||||
unused_variables,
|
||||
unused_unsafe,
|
||||
unreachable_patterns
|
||||
)]
|
||||
|
||||
use air_beautifier::Beautifier;
|
||||
use anyhow::{Context, Result};
|
||||
use clap::Parser;
|
||||
|
||||
use std::{io, path::PathBuf};
|
||||
|
||||
#[derive(Parser)]
|
||||
struct Args {
|
||||
#[clap(short, long, default_value_t = air_beautifier::DEFAULT_INDENT_STEP)]
|
||||
indent_step: usize,
|
||||
#[clap(short, long)]
|
||||
output: Option<PathBuf>,
|
||||
input: Option<PathBuf>,
|
||||
}
|
||||
|
||||
fn read_script(args: &Args) -> Result<String> {
|
||||
use std::io::Read;
|
||||
|
||||
let air_script = match &args.input {
|
||||
Some(in_path) => std::fs::read_to_string(in_path)?,
|
||||
None => {
|
||||
let mut buffer = String::new();
|
||||
let mut stdin = io::stdin().lock();
|
||||
|
||||
stdin.read_to_string(&mut buffer)?;
|
||||
buffer
|
||||
}
|
||||
};
|
||||
|
||||
Ok(air_script)
|
||||
}
|
||||
|
||||
fn build_output(args: &Args) -> Result<Box<dyn io::Write>> {
|
||||
let output: Box<dyn io::Write> = match &args.output {
|
||||
Some(out_path) => {
|
||||
let file = std::fs::File::create(out_path)?;
|
||||
Box::new(file)
|
||||
}
|
||||
None => {
|
||||
let stdout = io::stdout().lock();
|
||||
Box::new(stdout)
|
||||
}
|
||||
};
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args = Args::parse();
|
||||
let air_script = read_script(&args).context("failed to read the input")?;
|
||||
let output = build_output(&args).context("failed to open the output")?;
|
||||
|
||||
Beautifier::new_with_indent(output, args.indent_step).beautify(&air_script)?;
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in New Issue
Block a user