mirror of
https://github.com/fluencelabs/aquavm
synced 2025-04-24 14:52:15 +00:00
feat(cli)!: hopon virtual instruction in the beautifier (#840)
AIR beautifier may output virtual `hopon` instruction based on specific pattern generated by the Aqua compiler. The `air_beatifier::beatify` function has now an extra argument that determines if to perform the virtual instruction detection, giving more readable output. The `air-beautify-wasm` crate has behavior of the `beautify` function changed: the functions now extracts virtual instructions, so it is a drop-in replacement for previous version with new functionality. New exported function `beautify_raw` is added, that doesn't look for virtual instructions' patterns, formatting the code as is.
This commit is contained in:
parent
79d1c11a0e
commit
04bacb7039
@ -14,6 +14,8 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use super::r#virtual::try_hopon;
|
||||||
|
|
||||||
use air_parser::ast;
|
use air_parser::ast;
|
||||||
|
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
@ -82,25 +84,42 @@ pub enum BeautifyError {
|
|||||||
pub struct Beautifier<W: io::Write> {
|
pub struct Beautifier<W: io::Write> {
|
||||||
output: W,
|
output: W,
|
||||||
indent_step: usize,
|
indent_step: usize,
|
||||||
|
try_hopon: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W: io::Write> Beautifier<W> {
|
impl<W: io::Write> Beautifier<W> {
|
||||||
/// Beautifier for the output with default indentation step.
|
/// Beautifier for the output with default indentation step.
|
||||||
|
#[inline]
|
||||||
pub fn new(output: W) -> Self {
|
pub fn new(output: W) -> Self {
|
||||||
Self {
|
Self {
|
||||||
output,
|
output,
|
||||||
indent_step: DEFAULT_INDENT_STEP,
|
indent_step: DEFAULT_INDENT_STEP,
|
||||||
|
try_hopon: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Beautifier for the output with custom indentation step.
|
/// Beautifier for the output with custom indentation step.
|
||||||
|
#[inline]
|
||||||
pub fn new_with_indent(output: W, indent_step: usize) -> Self {
|
pub fn new_with_indent(output: W, indent_step: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
output,
|
output,
|
||||||
indent_step,
|
indent_step,
|
||||||
|
try_hopon: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
/// Enable all patterns in the emited code.
|
||||||
|
pub fn enable_all_patterns(self) -> Self {
|
||||||
|
self.enable_try_hopon()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn enable_try_hopon(mut self) -> Self {
|
||||||
|
self.try_hopon = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Unwrap the Beautifier into the underlying writer.
|
/// Unwrap the Beautifier into the underlying writer.
|
||||||
pub fn into_inner(self) -> W {
|
pub fn into_inner(self) -> W {
|
||||||
self.output
|
self.output
|
||||||
@ -253,6 +272,12 @@ impl<W: io::Write> Beautifier<W> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn beautify_new(&mut self, new: &ast::New<'_>, indent: usize) -> io::Result<()> {
|
fn beautify_new(&mut self, new: &ast::New<'_>, indent: usize) -> io::Result<()> {
|
||||||
|
if self.try_hopon {
|
||||||
|
if let Some(hop_on) = try_hopon(new) {
|
||||||
|
return self.beautify_simple(&hop_on, indent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// else
|
||||||
compound!(self, indent, new);
|
compound!(self, indent, new);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -26,14 +26,22 @@
|
|||||||
)]
|
)]
|
||||||
|
|
||||||
mod beautifier;
|
mod beautifier;
|
||||||
|
mod r#virtual;
|
||||||
|
|
||||||
pub use crate::beautifier::{Beautifier, BeautifyError, DEFAULT_INDENT_STEP};
|
pub use crate::beautifier::{Beautifier, BeautifyError, DEFAULT_INDENT_STEP};
|
||||||
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
/// Beautify the `air_script` with default settings to the `output`.
|
/// Beautify the `air_script` with default settings to the `output`.
|
||||||
pub fn beautify(air_script: &str, output: &mut impl io::Write) -> Result<(), BeautifyError> {
|
pub fn beautify(
|
||||||
|
air_script: &str,
|
||||||
|
output: &mut impl io::Write,
|
||||||
|
enable_patterns: bool,
|
||||||
|
) -> Result<(), BeautifyError> {
|
||||||
let mut beautifier = Beautifier::new(output);
|
let mut beautifier = Beautifier::new(output);
|
||||||
|
if enable_patterns {
|
||||||
|
beautifier = beautifier.enable_all_patterns();
|
||||||
|
}
|
||||||
beautifier.beautify(air_script)
|
beautifier.beautify(air_script)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -388,3 +388,106 @@ fn fail_error() {
|
|||||||
let output = beautify_to_string(script).unwrap();
|
let output = beautify_to_string(script).unwrap();
|
||||||
assert_eq!(output, "fail :error:\n");
|
assert_eq!(output, "fail :error:\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hopon_on() {
|
||||||
|
let script = r#"(new $ephemeral (new #ephemeral (canon "relay" $ephemeral #ephemeral)))"#;
|
||||||
|
|
||||||
|
let mut output = vec![];
|
||||||
|
let mut beautifier = Beautifier::new(&mut output).enable_all_patterns();
|
||||||
|
beautifier.beautify(script).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(String::from_utf8(output).unwrap(), "hopon \"relay\"\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hopon_off() {
|
||||||
|
let script = r#"(new $ephemeral (new #ephemeral (canon "relay" $ephemeral #ephemeral)))"#;
|
||||||
|
|
||||||
|
let mut output = vec![];
|
||||||
|
let mut beautifier = Beautifier::new(&mut output);
|
||||||
|
beautifier.beautify(script).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
String::from_utf8(output).unwrap(),
|
||||||
|
concat!(
|
||||||
|
"new $ephemeral:\n",
|
||||||
|
" new #ephemeral:\n",
|
||||||
|
" canon \"relay\" $ephemeral #ephemeral\n"
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hopon_canon_mismatch() {
|
||||||
|
let script = r#"(new $ephemeral (new #can (canon "relay" $ephemeral #ephemeral)))"#;
|
||||||
|
|
||||||
|
let mut output = vec![];
|
||||||
|
let mut beautifier = Beautifier::new(&mut output).enable_all_patterns();
|
||||||
|
beautifier.beautify(script).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
String::from_utf8(output).unwrap(),
|
||||||
|
concat!(
|
||||||
|
"new $ephemeral:\n",
|
||||||
|
" new #can:\n",
|
||||||
|
" canon \"relay\" $ephemeral #ephemeral\n"
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hopon_stream_mismatch() {
|
||||||
|
let script = r#"(new $stream (new #ephemeral (canon "relay" $ephemeral #ephemeral)))"#;
|
||||||
|
|
||||||
|
let mut output = vec![];
|
||||||
|
let mut beautifier = Beautifier::new(&mut output).enable_all_patterns();
|
||||||
|
beautifier.beautify(script).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
String::from_utf8(output).unwrap(),
|
||||||
|
concat!(
|
||||||
|
"new $stream:\n",
|
||||||
|
" new #ephemeral:\n",
|
||||||
|
" canon \"relay\" $ephemeral #ephemeral\n"
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hopon_nested() {
|
||||||
|
let script =
|
||||||
|
r#"(new $other (new $ephemeral (new #ephemeral (canon "relay" $ephemeral #ephemeral))) )"#;
|
||||||
|
|
||||||
|
let mut output = vec![];
|
||||||
|
let mut beautifier = Beautifier::new(&mut output).enable_all_patterns();
|
||||||
|
beautifier.beautify(script).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
String::from_utf8(output).unwrap(),
|
||||||
|
"new $other:\n hopon \"relay\"\n",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is bug that should be eventually fixed: it uses top-level #can
|
||||||
|
// instead of the nested one which disappeared
|
||||||
|
//
|
||||||
|
// the compiler doesn't generate such code, but it can be crafted manually
|
||||||
|
#[test]
|
||||||
|
fn hopon_shadowing() {
|
||||||
|
let script = r#"(new #can (new $ephemeral (new #can (canon #can.$.[0] $ephemeral #can))) )"#;
|
||||||
|
|
||||||
|
let mut output = vec![];
|
||||||
|
let mut beautifier = Beautifier::new(&mut output).enable_all_patterns();
|
||||||
|
beautifier.beautify(script).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
String::from_utf8(output).unwrap(),
|
||||||
|
concat!(
|
||||||
|
"new #can:\n",
|
||||||
|
" new $ephemeral:\n",
|
||||||
|
" new #can:\n",
|
||||||
|
" canon #can.$.[0] $ephemeral #can\n"
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -32,8 +32,17 @@ use crate::{beautify, beautify_to_string, BeautifyError};
|
|||||||
fn beautify_valid() {
|
fn beautify_valid() {
|
||||||
let air_script = "(seq (null) (null))";
|
let air_script = "(seq (null) (null))";
|
||||||
let mut buffer = vec![];
|
let mut buffer = vec![];
|
||||||
let res = beautify(air_script, &mut buffer);
|
let res = beautify(air_script, &mut buffer, false);
|
||||||
assert!(matches!(res, Ok(())));
|
assert!(res.is_ok());
|
||||||
|
assert_eq!(std::str::from_utf8(&buffer).unwrap(), "null\nnull\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn beautify_valid_with_patterns() {
|
||||||
|
let air_script = "(seq (null) (null))";
|
||||||
|
let mut buffer = vec![];
|
||||||
|
let res = beautify(air_script, &mut buffer, true);
|
||||||
|
assert!(res.is_ok());
|
||||||
assert_eq!(std::str::from_utf8(&buffer).unwrap(), "null\nnull\n");
|
assert_eq!(std::str::from_utf8(&buffer).unwrap(), "null\nnull\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,7 +50,7 @@ fn beautify_valid() {
|
|||||||
fn beautify_invalid() {
|
fn beautify_invalid() {
|
||||||
let air_script = "(seq (null))";
|
let air_script = "(seq (null))";
|
||||||
let mut buffer = vec![];
|
let mut buffer = vec![];
|
||||||
let res = beautify(air_script, &mut buffer);
|
let res = beautify(air_script, &mut buffer, false);
|
||||||
assert!(matches!(res, Err(BeautifyError::Parse(_))));
|
assert!(matches!(res, Err(BeautifyError::Parse(_))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,5 +65,5 @@ fn beautify_to_string_valid() {
|
|||||||
fn beautify_to_string_invalid() {
|
fn beautify_to_string_invalid() {
|
||||||
let air_script = "(seq (null))";
|
let air_script = "(seq (null))";
|
||||||
let res = beautify_to_string(air_script);
|
let res = beautify_to_string(air_script);
|
||||||
assert!(matches!(res, Err(_)));
|
assert!(res.is_err());
|
||||||
}
|
}
|
||||||
|
84
crates/beautifier/src/virtual.rs
Normal file
84
crates/beautifier/src/virtual.rs
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Fluence DAO
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use air_parser::ast;
|
||||||
|
|
||||||
|
use core::fmt;
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
/// A virtual `hopon` instruction.
|
||||||
|
pub(crate) struct HopOn<'i> {
|
||||||
|
pub peer_id: ast::ResolvableToPeerIdVariable<'i>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for HopOn<'_> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "hopon {}", self.peer_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to parse the `new` instruction and its nested elements as a virtual `hopon` instruction.
|
||||||
|
///
|
||||||
|
/// For example:
|
||||||
|
/// ```clojure
|
||||||
|
/// (new #uniq1_name
|
||||||
|
/// (new $uniq2_name
|
||||||
|
/// (canon peer_id $uniq2_name #uniq1_name)))
|
||||||
|
/// ```
|
||||||
|
/// is parsed as a virtual instruction
|
||||||
|
/// ```clojure
|
||||||
|
/// (hopon peer_id)
|
||||||
|
/// ```
|
||||||
|
pub(crate) fn try_hopon<'i>(root_new: &ast::New<'i>) -> Option<HopOn<'i>> {
|
||||||
|
let expected_stream_name = &root_new.argument;
|
||||||
|
|
||||||
|
if let (ast::Instruction::New(nested_new), ast::NewArgument::Stream(stream_name)) =
|
||||||
|
(&root_new.instruction, expected_stream_name)
|
||||||
|
{
|
||||||
|
let expected_nested_canon_name = &nested_new.argument;
|
||||||
|
|
||||||
|
if let (ast::Instruction::Canon(canon), ast::NewArgument::CanonStream(nested_canon_name)) =
|
||||||
|
(&nested_new.instruction, expected_nested_canon_name)
|
||||||
|
{
|
||||||
|
if canon.canon_stream.name == nested_canon_name.name
|
||||||
|
&& canon.stream.name == stream_name.name
|
||||||
|
// this condition handles case that is never generated by an Aqua compiler, but
|
||||||
|
// can be crafted manually
|
||||||
|
//
|
||||||
|
// see `hopon_shadowing` test for an example
|
||||||
|
&& !canon_shadows_peer_id(nested_canon_name.name, &canon.peer_id)
|
||||||
|
{
|
||||||
|
return Some(HopOn {
|
||||||
|
peer_id: canon.peer_id.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn canon_shadows_peer_id(canon_name: &str, peer_id: &ast::ResolvableToPeerIdVariable<'_>) -> bool {
|
||||||
|
use ast::ResolvableToPeerIdVariable::*;
|
||||||
|
match peer_id {
|
||||||
|
InitPeerId => false,
|
||||||
|
Literal(_) => false,
|
||||||
|
Scalar(_) => false,
|
||||||
|
ScalarWithLambda(_) => false,
|
||||||
|
CanonStreamMapWithLambda(_) => false,
|
||||||
|
CanonStreamWithLambda(canon_with_lambda) => canon_with_lambda.name == canon_name,
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,8 @@ This subcommand reads an AIR script from standard input and prints it in human-r
|
|||||||
|
|
||||||
It outputs to standard output or a file.
|
It outputs to standard output or a file.
|
||||||
|
|
||||||
|
With `--patterns` options, it tries to recognize certain patterns that Aqua compiler emits and outputs it as more human readable Aqua-like syntax. Currently only `hopon` syntax is recognized.
|
||||||
|
|
||||||
## `air run`
|
## `air run`
|
||||||
|
|
||||||
Alias: `air r`.
|
Alias: `air r`.
|
||||||
|
@ -25,6 +25,8 @@ use std::{io, path::PathBuf};
|
|||||||
pub(crate) struct Args {
|
pub(crate) struct Args {
|
||||||
#[clap(short, long, default_value_t = air_beautifier::DEFAULT_INDENT_STEP)]
|
#[clap(short, long, default_value_t = air_beautifier::DEFAULT_INDENT_STEP)]
|
||||||
indent_step: usize,
|
indent_step: usize,
|
||||||
|
#[clap(short, long, help = "Recognize virtual instruction patterns")]
|
||||||
|
patterns: bool,
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
output: Option<PathBuf>,
|
output: Option<PathBuf>,
|
||||||
input: Option<PathBuf>,
|
input: Option<PathBuf>,
|
||||||
@ -65,6 +67,12 @@ pub(crate) fn beautify(args: Args) -> Result<()> {
|
|||||||
let air_script = read_script(&args).context("failed to read the input")?;
|
let air_script = read_script(&args).context("failed to read the input")?;
|
||||||
let output = build_output(&args).context("failed to open the output")?;
|
let output = build_output(&args).context("failed to open the output")?;
|
||||||
|
|
||||||
Beautifier::new_with_indent(output, args.indent_step).beautify(&air_script)?;
|
let mut beautifier = Beautifier::new_with_indent(output, args.indent_step);
|
||||||
|
|
||||||
|
if args.patterns {
|
||||||
|
beautifier = beautifier.enable_all_patterns();
|
||||||
|
}
|
||||||
|
|
||||||
|
beautifier.beautify(&air_script)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,13 @@ use wasm_bindgen::prelude::*;
|
|||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn beautify(air_script: String) -> Result<String, JsError> {
|
pub fn beautify(air_script: String) -> Result<String, JsError> {
|
||||||
let mut output = vec![];
|
let mut output = vec![];
|
||||||
air_beautifier::beautify(&air_script, &mut output)?;
|
air_beautifier::beautify(&air_script, &mut output, true)?;
|
||||||
|
Ok(unsafe { String::from_utf8_unchecked(output) })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn beautify_raw(air_script: String) -> Result<String, JsError> {
|
||||||
|
let mut output = vec![];
|
||||||
|
air_beautifier::beautify(&air_script, &mut output, false)?;
|
||||||
Ok(unsafe { String::from_utf8_unchecked(output) })
|
Ok(unsafe { String::from_utf8_unchecked(output) })
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user