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:
Ivan Boldyrev 2022-05-20 17:07:22 +03:00 committed by GitHub
parent 3c23ab735c
commit 1c0ff2c979
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 951 additions and 4 deletions

107
Cargo.lock generated
View File

@ -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",

View File

@ -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 = [

View 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"

View 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)
}
}

View 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;

View 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);
}

View 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%])))

View 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%]

View 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(_)));
}

View 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"

View 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(())
}