mirror of
https://github.com/fluencelabs/aquavm
synced 2025-04-24 23:02:16 +00:00
Basic implementation of if-else pattern
This commit is contained in:
parent
04bacb7039
commit
21f5985aa0
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -59,6 +59,7 @@ dependencies = [
|
||||
name = "air-beautifier"
|
||||
version = "0.4.3"
|
||||
dependencies = [
|
||||
"air-lambda-parser",
|
||||
"aquavm-air-parser",
|
||||
"itertools",
|
||||
"thiserror",
|
||||
|
@ -14,6 +14,7 @@ name = "air_beautifier"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
air-lambda-parser = { version = "0.1.0", path = "../air-lib/lambda/parser" }
|
||||
aquavm-air-parser = { version = "0.12.0", path = "../air-lib/air-parser" }
|
||||
itertools = "0.10.5"
|
||||
thiserror = "1.0.50"
|
||||
|
@ -14,7 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use super::r#virtual::try_hopon;
|
||||
use crate::r#virtual::try_hopon;
|
||||
use crate::r#virtual::try_if_else;
|
||||
|
||||
use air_parser::ast;
|
||||
|
||||
@ -85,6 +86,7 @@ pub struct Beautifier<W: io::Write> {
|
||||
output: W,
|
||||
indent_step: usize,
|
||||
try_hopon: bool,
|
||||
try_if_else: bool,
|
||||
}
|
||||
|
||||
impl<W: io::Write> Beautifier<W> {
|
||||
@ -95,6 +97,7 @@ impl<W: io::Write> Beautifier<W> {
|
||||
output,
|
||||
indent_step: DEFAULT_INDENT_STEP,
|
||||
try_hopon: false,
|
||||
try_if_else: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,13 +108,14 @@ impl<W: io::Write> Beautifier<W> {
|
||||
output,
|
||||
indent_step,
|
||||
try_hopon: false,
|
||||
try_if_else: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Enable all patterns in the emited code.
|
||||
pub fn enable_all_patterns(self) -> Self {
|
||||
self.enable_try_hopon()
|
||||
self.enable_try_hopon().enable_try_if_else()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@ -120,6 +124,12 @@ impl<W: io::Write> Beautifier<W> {
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn enable_try_if_else(mut self) -> Self {
|
||||
self.try_if_else = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Unwrap the Beautifier into the underlying writer.
|
||||
pub fn into_inner(self) -> W {
|
||||
self.output
|
||||
@ -277,6 +287,18 @@ impl<W: io::Write> Beautifier<W> {
|
||||
return self.beautify_simple(&hop_on, indent);
|
||||
}
|
||||
}
|
||||
if self.try_if_else {
|
||||
if let Some(if_else) = try_if_else(new) {
|
||||
multiline!(
|
||||
self, indent;
|
||||
"if {}:", if_else.condition;
|
||||
&if_else.then_body;
|
||||
"else:";
|
||||
&if_else.else_body
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
// else
|
||||
compound!(self, indent, new);
|
||||
Ok(())
|
||||
|
@ -24,6 +24,15 @@
|
||||
unused_unsafe,
|
||||
unreachable_patterns
|
||||
)]
|
||||
// needed for matching AST trees.
|
||||
// https://github.com/rust-lang/rust/issues/29641
|
||||
//
|
||||
// n.b. box_patterns are intended to be eventually replaced by deref_patterns:
|
||||
// https://github.com/rust-lang/rust/issues/29641
|
||||
//
|
||||
// one can get rid of it by splitting the match into parts;
|
||||
// or use https://crates.io/crates/match_deref
|
||||
#![feature(box_patterns)]
|
||||
|
||||
mod beautifier;
|
||||
mod r#virtual;
|
||||
|
308
crates/beautifier/src/tests/if_else.rs
Normal file
308
crates/beautifier/src/tests/if_else.rs
Normal file
@ -0,0 +1,308 @@
|
||||
/*
|
||||
* 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 crate::Beautifier;
|
||||
|
||||
#[test]
|
||||
fn if_else_nested() {
|
||||
let script = r#"
|
||||
(new my-var (new -if-else-error-
|
||||
(new -else-error-
|
||||
(new -if-error-
|
||||
(xor
|
||||
(match "a" "test"
|
||||
(ap 0 $result)
|
||||
)
|
||||
(seq
|
||||
(ap :error: -if-error-)
|
||||
(xor
|
||||
(match :error:.$.error_code 10001
|
||||
(ap 1 $result)
|
||||
)
|
||||
(seq
|
||||
(seq
|
||||
(ap :error: -else-error-)
|
||||
(xor
|
||||
(match :error:.$.error_code 10001
|
||||
(ap -if-error- -if-else-error-)
|
||||
)
|
||||
(ap -else-error- -if-else-error-)
|
||||
)
|
||||
)
|
||||
(fail -if-else-error-)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
) )
|
||||
"#;
|
||||
|
||||
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 my-var:\n",
|
||||
r#" if "a" == "test":"#,
|
||||
"\n",
|
||||
" ap 0 $result\n",
|
||||
" else:\n",
|
||||
" ap 1 $result\n",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_else_match_on() {
|
||||
let script = r#"
|
||||
(new -if-else-error-
|
||||
(new -else-error-
|
||||
(new -if-error-
|
||||
(xor
|
||||
(match "a" "test"
|
||||
(ap 0 $result)
|
||||
)
|
||||
(seq
|
||||
(ap :error: -if-error-)
|
||||
(xor
|
||||
(match :error:.$.error_code 10001
|
||||
(ap 1 $result)
|
||||
)
|
||||
(seq
|
||||
(seq
|
||||
(ap :error: -else-error-)
|
||||
(xor
|
||||
(match :error:.$.error_code 10001
|
||||
(ap -if-error- -if-else-error-)
|
||||
)
|
||||
(ap -else-error- -if-else-error-)
|
||||
)
|
||||
)
|
||||
(fail -if-else-error-)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
"#;
|
||||
|
||||
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!(
|
||||
r#"if "a" == "test":"#,
|
||||
"\n",
|
||||
" ap 0 $result\n",
|
||||
"else:\n",
|
||||
" ap 1 $result\n",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_else_match_off() {
|
||||
let script = r#"
|
||||
(new -if-else-error-
|
||||
(new -else-error-
|
||||
(new -if-error-
|
||||
(xor
|
||||
(match "a" "test"
|
||||
(ap 0 $result)
|
||||
)
|
||||
(seq
|
||||
(ap :error: -if-error-)
|
||||
(xor
|
||||
(match :error:.$.error_code 10001
|
||||
(ap 1 $result)
|
||||
)
|
||||
(seq
|
||||
(seq
|
||||
(ap :error: -else-error-)
|
||||
(xor
|
||||
(match :error:.$.error_code 10001
|
||||
(ap -if-error- -if-else-error-)
|
||||
)
|
||||
(ap -else-error- -if-else-error-)
|
||||
)
|
||||
)
|
||||
(fail -if-else-error-)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
"#;
|
||||
|
||||
let mut output = vec![];
|
||||
let mut beautifier = Beautifier::new(&mut output);
|
||||
beautifier.beautify(script).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
String::from_utf8(output).unwrap(),
|
||||
concat!(
|
||||
"new -if-else-error-:\n",
|
||||
" new -else-error-:\n",
|
||||
" new -if-error-:\n",
|
||||
" try:\n",
|
||||
r#" match "a" "test":"#,
|
||||
"\n",
|
||||
" ap 0 $result\n",
|
||||
" catch:\n",
|
||||
" ap :error: -if-error-\n",
|
||||
" try:\n",
|
||||
" match :error:.$.error_code 10001:\n",
|
||||
" ap 1 $result\n",
|
||||
" catch:\n",
|
||||
" ap :error: -else-error-\n",
|
||||
" try:\n",
|
||||
" match :error:.$.error_code 10001:\n",
|
||||
" ap -if-error- -if-else-error-\n",
|
||||
" catch:\n",
|
||||
" ap -else-error- -if-else-error-\n",
|
||||
" fail -if-else-error-\n",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_else_mismatch_on() {
|
||||
let script = r#"
|
||||
(new -if-else-error-
|
||||
(new -else-error-
|
||||
(new -if-error-
|
||||
(xor
|
||||
(mismatch "a" "test"
|
||||
(ap 0 $result)
|
||||
)
|
||||
(seq
|
||||
(ap :error: -if-error-)
|
||||
(xor
|
||||
(match :error:.$.error_code 10001
|
||||
(ap 1 $result)
|
||||
)
|
||||
(seq
|
||||
(seq
|
||||
(ap :error: -else-error-)
|
||||
(xor
|
||||
(match :error:.$.error_code 10001
|
||||
(ap -if-error- -if-else-error-)
|
||||
)
|
||||
(ap -else-error- -if-else-error-)
|
||||
)
|
||||
)
|
||||
(fail -if-else-error-)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
"#;
|
||||
|
||||
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!(
|
||||
r#"if "a" != "test":"#,
|
||||
"\n",
|
||||
" ap 0 $result\n",
|
||||
"else:\n",
|
||||
" ap 1 $result\n",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_else_mismatch_off() {
|
||||
let script = r#"
|
||||
(new -if-else-error-
|
||||
(new -else-error-
|
||||
(new -if-error-
|
||||
(xor
|
||||
(mismatch "a" "test"
|
||||
(ap 0 $result)
|
||||
)
|
||||
(seq
|
||||
(ap :error: -if-error-)
|
||||
(xor
|
||||
(match :error:.$.error_code 10001
|
||||
(ap 1 $result)
|
||||
)
|
||||
(seq
|
||||
(seq
|
||||
(ap :error: -else-error-)
|
||||
(xor
|
||||
(match :error:.$.error_code 10001
|
||||
(ap -if-error- -if-else-error-)
|
||||
)
|
||||
(ap -else-error- -if-else-error-)
|
||||
)
|
||||
)
|
||||
(fail -if-else-error-)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
"#;
|
||||
|
||||
let mut output = vec![];
|
||||
let mut beautifier = Beautifier::new(&mut output);
|
||||
beautifier.beautify(script).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
String::from_utf8(output).unwrap(),
|
||||
concat!(
|
||||
"new -if-else-error-:\n",
|
||||
" new -else-error-:\n",
|
||||
" new -if-error-:\n",
|
||||
" try:\n",
|
||||
r#" mismatch "a" "test":"#,
|
||||
"\n",
|
||||
" ap 0 $result\n",
|
||||
" catch:\n",
|
||||
" ap :error: -if-error-\n",
|
||||
" try:\n",
|
||||
" match :error:.$.error_code 10001:\n",
|
||||
" ap 1 $result\n",
|
||||
" catch:\n",
|
||||
" ap :error: -else-error-\n",
|
||||
" try:\n",
|
||||
" match :error:.$.error_code 10001:\n",
|
||||
" ap -if-error- -if-else-error-\n",
|
||||
" catch:\n",
|
||||
" ap -else-error- -if-else-error-\n",
|
||||
" fail -if-else-error-\n",
|
||||
),
|
||||
);
|
||||
}
|
@ -25,6 +25,7 @@
|
||||
)]
|
||||
|
||||
mod beautifier;
|
||||
mod if_else;
|
||||
|
||||
use crate::{beautify, beautify_to_string, BeautifyError};
|
||||
|
||||
|
@ -14,17 +14,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use air_parser::ast;
|
||||
mod if_else;
|
||||
|
||||
use core::fmt;
|
||||
use std::fmt::Display;
|
||||
use air_parser::ast;
|
||||
pub(crate) use if_else::try_if_else;
|
||||
use std::fmt;
|
||||
|
||||
/// A virtual `hopon` instruction.
|
||||
pub(crate) struct HopOn<'i> {
|
||||
pub peer_id: ast::ResolvableToPeerIdVariable<'i>,
|
||||
}
|
||||
|
||||
impl Display for HopOn<'_> {
|
||||
impl fmt::Display for HopOn<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "hopon {}", self.peer_id)
|
||||
}
|
||||
|
239
crates/beautifier/src/virtual/if_else.rs
Normal file
239
crates/beautifier/src/virtual/if_else.rs
Normal file
@ -0,0 +1,239 @@
|
||||
/*
|
||||
* 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 std::fmt;
|
||||
|
||||
use air_parser::ast;
|
||||
|
||||
/// A virtual 'if-else' instruction.
|
||||
pub(crate) struct IfElse<'i> {
|
||||
pub(crate) condition: Condition<'i>,
|
||||
pub(crate) then_body: &'i ast::Instruction<'i>,
|
||||
pub(crate) else_body: &'i ast::Instruction<'i>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum Condition<'i> {
|
||||
Match {
|
||||
left_value: &'i ast::ImmutableValue<'i>,
|
||||
right_value: &'i ast::ImmutableValue<'i>,
|
||||
},
|
||||
Mismatch {
|
||||
left_value: &'i ast::ImmutableValue<'i>,
|
||||
right_value: &'i ast::ImmutableValue<'i>,
|
||||
},
|
||||
}
|
||||
|
||||
impl fmt::Display for Condition<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Condition::Match {
|
||||
left_value,
|
||||
right_value,
|
||||
} => write!(f, "{left_value} == {right_value}"),
|
||||
Condition::Mismatch {
|
||||
left_value,
|
||||
right_value,
|
||||
} => write!(f, "{left_value} != {right_value}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to parse an instruction and its nested elements as a virtual `if else` instruction.
|
||||
///
|
||||
/// For example:
|
||||
/// ```clojure
|
||||
/// (todo)
|
||||
/// ```
|
||||
/// is parsed as a virtual instruction
|
||||
/// ```python
|
||||
/// if expr1 == expr2:
|
||||
/// st1
|
||||
/// else:
|
||||
/// st2
|
||||
/// ```
|
||||
pub(crate) fn try_if_else<'i>(root_new: &'i ast::New<'i>) -> Option<IfElse<'i>> {
|
||||
use ast::Instruction::New;
|
||||
|
||||
let root_scalar = if let ast::NewArgument::Scalar(root_scalar) = &root_new.argument {
|
||||
root_scalar
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let nested1_new = if let New(netsted1_new) = &root_new.instruction {
|
||||
netsted1_new
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let nested1_scalar = if let ast::NewArgument::Scalar(nested1_scalar) = &nested1_new.argument {
|
||||
nested1_scalar
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let nested2_new = if let New(netsted2_new) = &nested1_new.instruction {
|
||||
netsted2_new
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let nested2_scalar = if let ast::NewArgument::Scalar(nested2_scalar) = &nested2_new.argument {
|
||||
nested2_scalar
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
if let ast::Instruction::Xor(box ast::Xor(
|
||||
expected_match_or_mismatch,
|
||||
ast::Instruction::Seq(error_handling),
|
||||
)) = &nested2_new.instruction
|
||||
{
|
||||
let (condition, then_body) = match expected_match_or_mismatch {
|
||||
ast::Instruction::Match(match_) => {
|
||||
let condition = Condition::Match {
|
||||
left_value: &match_.left_value,
|
||||
right_value: &match_.right_value,
|
||||
};
|
||||
let then_branch = &match_.instruction;
|
||||
(condition, then_branch)
|
||||
}
|
||||
ast::Instruction::MisMatch(mismatch) => {
|
||||
let condition = Condition::Mismatch {
|
||||
left_value: &mismatch.left_value,
|
||||
right_value: &mismatch.right_value,
|
||||
};
|
||||
let then_branch = &mismatch.instruction;
|
||||
(condition, then_branch)
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
if let Some(else_body) =
|
||||
validate_error_handling(error_handling, root_scalar, nested1_scalar, nested2_scalar)
|
||||
{
|
||||
// todo!("check free variables");
|
||||
Some(IfElse {
|
||||
condition,
|
||||
then_body,
|
||||
else_body,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_error_handling<'i>(
|
||||
root: &'i ast::Seq<'i>,
|
||||
if_else_error: &ast::Scalar<'i>,
|
||||
else_error: &ast::Scalar<'i>,
|
||||
if_error: &ast::Scalar<'i>,
|
||||
) -> Option<&'i ast::Instruction<'i>> {
|
||||
if validate_error_handling_ap(&root.0, if_error) {
|
||||
validate_error_handling_xor(&root.1, if_else_error, else_error, if_error)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_error_handling_xor<'i>(
|
||||
instruction: &'i ast::Instruction<'i>,
|
||||
if_else_error: &ast::Scalar<'i>,
|
||||
else_error: &ast::Scalar<'i>,
|
||||
if_error: &ast::Scalar<'i>,
|
||||
) -> Option<&'i ast::Instruction<'i>> {
|
||||
if let ast::Instruction::Xor(xor) = instruction {
|
||||
if !validate_error_handling_xor_second(&xor.1, if_else_error, else_error, if_error) {
|
||||
return None;
|
||||
}
|
||||
if let ast::Instruction::Match(match_) = &xor.0 {
|
||||
// check arguments: match :error:.$.error_code 10001
|
||||
let lambda =
|
||||
air_lambda_parser::parse(".$.error_code").expect("invalid internal lambda");
|
||||
let expected_left =
|
||||
ast::ImmutableValue::Error(ast::InstructionErrorAST::new(Some(lambda)));
|
||||
let expected_right = ast::ImmutableValue::Number(ast::Number::Int(10001));
|
||||
if match_.left_value == expected_left && match_.right_value == expected_right {
|
||||
return Some(&match_.instruction);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn validate_error_handling_ap(
|
||||
instruction: &ast::Instruction<'_>,
|
||||
if_error: &ast::Scalar<'_>,
|
||||
) -> bool {
|
||||
match instruction {
|
||||
ast::Instruction::Ap(ap) => {
|
||||
let expected_argument = ast::ApArgument::Error(ast::InstructionErrorAST { lens: None });
|
||||
let result_matches = match &ap.result {
|
||||
ast::ApResult::Scalar(scalar) => scalar.name == if_error.name,
|
||||
ast::ApResult::Stream(_) => false,
|
||||
};
|
||||
result_matches && ap.argument == expected_argument
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_error_handling_xor_second(
|
||||
else_instruction: &ast::Instruction<'_>,
|
||||
if_else_error: &ast::Scalar<'_>,
|
||||
else_error: &ast::Scalar<'_>,
|
||||
if_error: &ast::Scalar<'_>,
|
||||
) -> bool {
|
||||
let air_script = format!(
|
||||
// the new instruction are needed to pass a parser validator; they are removed
|
||||
r#"(new {if_else_error}
|
||||
(new {else_error}
|
||||
(new {if_error}
|
||||
; real code
|
||||
(seq
|
||||
(seq
|
||||
(ap :error: {else_error})
|
||||
(xor
|
||||
(match :error:.$.error_code 10001
|
||||
(ap {if_error} {if_else_error})
|
||||
)
|
||||
(ap {else_error} {if_else_error})
|
||||
)
|
||||
)
|
||||
(fail {if_else_error})
|
||||
))))"#
|
||||
);
|
||||
let expected_tree = air_parser::parse(&air_script).expect("invalid internal AIR");
|
||||
let expected_tree = pop_new_from_tree(expected_tree, 3);
|
||||
|
||||
else_instruction.to_string() == expected_tree.to_string()
|
||||
}
|
||||
|
||||
fn pop_new_from_tree(mut expected_tree: ast::Instruction<'_>, arg: usize) -> ast::Instruction<'_> {
|
||||
for _ in 0..arg {
|
||||
match expected_tree {
|
||||
ast::Instruction::New(new) => {
|
||||
expected_tree = new.instruction;
|
||||
}
|
||||
_ => panic!("expected new, got: {expected_tree}"),
|
||||
}
|
||||
}
|
||||
expected_tree
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user