mirror of
https://github.com/fluencelabs/aquavm
synced 2025-07-31 05:52:00 +00:00
686 lines
21 KiB
Rust
686 lines
21 KiB
Rust
/*
|
|
* Copyright 2021 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.
|
|
*/
|
|
|
|
use super::dsl::*;
|
|
use super::parse;
|
|
use crate::ast::*;
|
|
use crate::parser::ParserError;
|
|
|
|
use air_lambda_ast::{LambdaAST, ValueAccessor};
|
|
use fstrings::f;
|
|
use fstrings::format_args_f;
|
|
use lalrpop_util::ParseError;
|
|
|
|
use std::rc::Rc;
|
|
|
|
#[test]
|
|
fn parse_json_path() {
|
|
let source_code = r#"
|
|
(call peer_id.$.a! ("service_id" "function_name") ["hello" name] $void)
|
|
"#;
|
|
|
|
let instruction = parse(source_code);
|
|
let expected = call(
|
|
CallInstrValue::Variable(VariableWithLambda::from_raw_value_path(
|
|
"peer_id",
|
|
vec![ValueAccessor::FieldAccessByName { field_name: "a" }],
|
|
15,
|
|
)),
|
|
CallInstrValue::Literal("service_id"),
|
|
CallInstrValue::Literal("function_name"),
|
|
Rc::new(vec![
|
|
Value::Literal("hello"),
|
|
Value::Variable(VariableWithLambda::scalar("name", 68)),
|
|
]),
|
|
CallOutputValue::Stream(Stream::new("$void", 74)),
|
|
);
|
|
assert_eq!(instruction, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_empty_array() {
|
|
let source_code = r#"
|
|
(call peer_id (service_id "function_name") ["" [] arg])
|
|
"#;
|
|
|
|
let actual = parse(source_code);
|
|
let expected = call(
|
|
CallInstrValue::Variable(VariableWithLambda::scalar("peer_id", 15)),
|
|
CallInstrValue::Variable(VariableWithLambda::scalar("service_id", 24)),
|
|
CallInstrValue::Literal("function_name"),
|
|
Rc::new(vec![
|
|
Value::Literal(""),
|
|
Value::EmptyArray,
|
|
Value::Variable(VariableWithLambda::scalar("arg", 59)),
|
|
]),
|
|
CallOutputValue::None,
|
|
);
|
|
|
|
assert_eq!(actual, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_empty_array_2() {
|
|
let source_code = r#"
|
|
(call peer_id ("service_id" "function_name") [k [] []])
|
|
"#;
|
|
|
|
let actual = parse(source_code);
|
|
let expected = call(
|
|
CallInstrValue::Variable(VariableWithLambda::scalar("peer_id", 15)),
|
|
CallInstrValue::Literal("service_id"),
|
|
CallInstrValue::Literal("function_name"),
|
|
Rc::new(vec![
|
|
Value::Variable(VariableWithLambda::scalar("k", 55)),
|
|
Value::EmptyArray,
|
|
Value::EmptyArray,
|
|
]),
|
|
CallOutputValue::None,
|
|
);
|
|
|
|
assert_eq!(actual, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_undefined_variable() {
|
|
let source_code = r#"
|
|
(call id.$.a ("" "f") ["hello" name] $void)
|
|
"#;
|
|
|
|
let lexer = crate::AIRLexer::new(source_code);
|
|
|
|
let parser = crate::AIRParser::new();
|
|
let mut errors = Vec::new();
|
|
let mut validator = crate::parser::VariableValidator::new();
|
|
parser
|
|
.parse(source_code, &mut errors, &mut validator, lexer)
|
|
.expect("parser shouldn't fail");
|
|
|
|
let errors = validator.finalize();
|
|
|
|
assert_eq!(errors.len(), 2);
|
|
for i in 0..2 {
|
|
let error = &errors[i].error;
|
|
let parser_error = match error {
|
|
ParseError::User { error } => error,
|
|
_ => panic!("unexpected error type"),
|
|
};
|
|
|
|
assert!(matches!(
|
|
parser_error,
|
|
ParserError::UndefinedVariable { .. }
|
|
));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn parse_undefined_stream_without_json_path() {
|
|
let source_code = r#"
|
|
(call "" ("" "") [$stream])
|
|
"#;
|
|
|
|
let lexer = crate::AIRLexer::new(source_code);
|
|
|
|
let parser = crate::AIRParser::new();
|
|
let mut errors = Vec::new();
|
|
let mut validator = crate::parser::VariableValidator::new();
|
|
parser
|
|
.parse(source_code, &mut errors, &mut validator, lexer)
|
|
.expect("parser shouldn't fail");
|
|
|
|
let errors = validator.finalize();
|
|
|
|
assert!(errors.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn parse_undefined_stream_with_lambda() {
|
|
let source_code = r#"
|
|
(call "" ("" "") [$stream.$.json_path])
|
|
"#;
|
|
|
|
let lexer = crate::AIRLexer::new(source_code);
|
|
|
|
let parser = crate::AIRParser::new();
|
|
let mut errors = Vec::new();
|
|
let mut validator = crate::parser::VariableValidator::new();
|
|
parser
|
|
.parse(source_code, &mut errors, &mut validator, lexer)
|
|
.expect("parser shouldn't fail");
|
|
|
|
let errors = validator.finalize();
|
|
|
|
assert_eq!(errors.len(), 1);
|
|
let error = &errors[0].error;
|
|
let parser_error = match error {
|
|
ParseError::User { error } => error,
|
|
_ => panic!("unexpected error type"),
|
|
};
|
|
|
|
assert!(matches!(
|
|
parser_error,
|
|
ParserError::UndefinedVariable { .. }
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn parse_lambda_complex() {
|
|
let source_code = r#"
|
|
(seq
|
|
(call m.$.[1]! ("service_id" "function_name") [] void)
|
|
(call m.$.abc[0].cde[1][0].cde[1]! ("service_id" "function_name") [] void)
|
|
)
|
|
"#;
|
|
let instruction = parse(source_code);
|
|
let expected = seq(
|
|
call(
|
|
CallInstrValue::Variable(VariableWithLambda::from_raw_value_path(
|
|
"m",
|
|
vec![ValueAccessor::ArrayAccess { idx: 1 }],
|
|
32,
|
|
)),
|
|
CallInstrValue::Literal("service_id"),
|
|
CallInstrValue::Literal("function_name"),
|
|
Rc::new(vec![]),
|
|
CallOutputValue::Scalar(Scalar::new("void", 75)),
|
|
),
|
|
call(
|
|
CallInstrValue::Variable(VariableWithLambda::from_raw_value_path(
|
|
"m",
|
|
vec![
|
|
ValueAccessor::FieldAccessByName { field_name: "abc" },
|
|
ValueAccessor::ArrayAccess { idx: 0 },
|
|
ValueAccessor::FieldAccessByName { field_name: "cde" },
|
|
ValueAccessor::ArrayAccess { idx: 1 },
|
|
ValueAccessor::ArrayAccess { idx: 0 },
|
|
ValueAccessor::FieldAccessByName { field_name: "cde" },
|
|
ValueAccessor::ArrayAccess { idx: 1 },
|
|
],
|
|
99,
|
|
)),
|
|
CallInstrValue::Literal("service_id"),
|
|
CallInstrValue::Literal("function_name"),
|
|
Rc::new(vec![]),
|
|
CallOutputValue::Scalar(Scalar::new("void", 162)),
|
|
),
|
|
);
|
|
assert_eq!(instruction, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_lambda_with_scalars_complex() {
|
|
let source_code = r#"
|
|
(seq
|
|
(call m.$.[1].[scalar_1].[scalar_2]! ("service_id" "function_name") [] void)
|
|
(call m.$.abc[0].[scalar_2].cde[1][0][scalar_3].cde[1]! ("service_id" "function_name") [] void)
|
|
)
|
|
"#;
|
|
let instruction = parse(source_code);
|
|
let expected = seq(
|
|
call(
|
|
CallInstrValue::Variable(VariableWithLambda::from_raw_value_path(
|
|
"m",
|
|
vec![
|
|
ValueAccessor::ArrayAccess { idx: 1 },
|
|
ValueAccessor::FieldAccessByScalar {
|
|
scalar_name: "scalar_1",
|
|
},
|
|
ValueAccessor::FieldAccessByScalar {
|
|
scalar_name: "scalar_2",
|
|
},
|
|
],
|
|
32,
|
|
)),
|
|
CallInstrValue::Literal("service_id"),
|
|
CallInstrValue::Literal("function_name"),
|
|
Rc::new(vec![]),
|
|
CallOutputValue::Scalar(Scalar::new("void", 97)),
|
|
),
|
|
call(
|
|
CallInstrValue::Variable(VariableWithLambda::from_raw_value_path(
|
|
"m",
|
|
vec![
|
|
ValueAccessor::FieldAccessByName { field_name: "abc" },
|
|
ValueAccessor::ArrayAccess { idx: 0 },
|
|
ValueAccessor::FieldAccessByScalar {
|
|
scalar_name: "scalar_2",
|
|
},
|
|
ValueAccessor::FieldAccessByName { field_name: "cde" },
|
|
ValueAccessor::ArrayAccess { idx: 1 },
|
|
ValueAccessor::ArrayAccess { idx: 0 },
|
|
ValueAccessor::FieldAccessByScalar {
|
|
scalar_name: "scalar_3",
|
|
},
|
|
ValueAccessor::FieldAccessByName { field_name: "cde" },
|
|
ValueAccessor::ArrayAccess { idx: 1 },
|
|
],
|
|
121,
|
|
)),
|
|
CallInstrValue::Literal("service_id"),
|
|
CallInstrValue::Literal("function_name"),
|
|
Rc::new(vec![]),
|
|
CallOutputValue::Scalar(Scalar::new("void", 205)),
|
|
),
|
|
);
|
|
assert_eq!(instruction, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn json_path_square_braces() {
|
|
let source_code = r#"
|
|
(call u.$.peer_id! ("return" "") [u.$[1].cde[0][0].abc u.$.name] $void)
|
|
"#;
|
|
let instruction = parse(source_code);
|
|
let expected = call(
|
|
CallInstrValue::Variable(VariableWithLambda::from_raw_value_path(
|
|
"u",
|
|
vec![ValueAccessor::FieldAccessByName {
|
|
field_name: "peer_id",
|
|
}],
|
|
15,
|
|
)),
|
|
CallInstrValue::Literal("return"),
|
|
CallInstrValue::Literal(""),
|
|
Rc::new(vec![
|
|
Value::Variable(VariableWithLambda::from_raw_value_path(
|
|
"u",
|
|
vec![
|
|
ValueAccessor::ArrayAccess { idx: 1 },
|
|
ValueAccessor::FieldAccessByName { field_name: "cde" },
|
|
ValueAccessor::ArrayAccess { idx: 0 },
|
|
ValueAccessor::ArrayAccess { idx: 0 },
|
|
ValueAccessor::FieldAccessByName { field_name: "abc" },
|
|
],
|
|
43,
|
|
)),
|
|
Value::Variable(VariableWithLambda::from_raw_value_path(
|
|
"u",
|
|
vec![ValueAccessor::FieldAccessByName { field_name: "name" }],
|
|
64,
|
|
)),
|
|
]),
|
|
CallOutputValue::Stream(Stream::new("$void", 74)),
|
|
);
|
|
|
|
assert_eq!(instruction, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_init_peer_id() {
|
|
let peer_id = "some_peer_id";
|
|
let source_code = format!(
|
|
r#"
|
|
(seq
|
|
(call "{}" ("local_service_id" "local_fn_name") [])
|
|
(call %init_peer_id% ("service_id" "fn_name") [])
|
|
)"#,
|
|
peer_id
|
|
);
|
|
|
|
let instruction = parse(&source_code);
|
|
let expected = seq(
|
|
call(
|
|
CallInstrValue::Literal(peer_id),
|
|
CallInstrValue::Literal("local_service_id"),
|
|
CallInstrValue::Literal("local_fn_name"),
|
|
Rc::new(vec![]),
|
|
CallOutputValue::None,
|
|
),
|
|
call(
|
|
CallInstrValue::InitPeerId,
|
|
CallInstrValue::Literal("service_id"),
|
|
CallInstrValue::Literal("fn_name"),
|
|
Rc::new(vec![]),
|
|
CallOutputValue::None,
|
|
),
|
|
);
|
|
|
|
assert_eq!(instruction, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_timestamp() {
|
|
let source_code = r#"
|
|
(call "peer_id" ("service_id" "fn_name") [%timestamp%])
|
|
"#;
|
|
|
|
let instruction = parse(source_code);
|
|
let expected = call(
|
|
CallInstrValue::Literal("peer_id"),
|
|
CallInstrValue::Literal("service_id"),
|
|
CallInstrValue::Literal("fn_name"),
|
|
Rc::new(vec![Value::Timestamp]),
|
|
CallOutputValue::None,
|
|
);
|
|
|
|
assert_eq!(instruction, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_ttl() {
|
|
let source_code = r#"
|
|
(call "peer_id" ("service_id" "fn_name") [%ttl%])
|
|
"#;
|
|
|
|
let instruction = parse(source_code);
|
|
let expected = call(
|
|
CallInstrValue::Literal("peer_id"),
|
|
CallInstrValue::Literal("service_id"),
|
|
CallInstrValue::Literal("fn_name"),
|
|
Rc::new(vec![Value::TTL]),
|
|
CallOutputValue::None,
|
|
);
|
|
|
|
assert_eq!(instruction, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_last_error() {
|
|
let source_code = format!(
|
|
r#"
|
|
(seq
|
|
(call %init_peer_id% ("service_id" "fn_name") [%last_error%])
|
|
(null)
|
|
)"#,
|
|
);
|
|
|
|
let instruction = parse(&source_code);
|
|
let expected = seq(
|
|
call(
|
|
CallInstrValue::InitPeerId,
|
|
CallInstrValue::Literal("service_id"),
|
|
CallInstrValue::Literal("fn_name"),
|
|
Rc::new(vec![Value::LastError(None)]),
|
|
CallOutputValue::None,
|
|
),
|
|
null(),
|
|
);
|
|
|
|
assert_eq!(instruction, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn canon_stream_in_args() {
|
|
let service_id = "service_id";
|
|
let function_name = "function_name";
|
|
let canon_stream = "#canon_stream";
|
|
let source_code = f!(r#"
|
|
(call %init_peer_id% ("{service_id}" "{function_name}") [{canon_stream}])
|
|
"#);
|
|
|
|
let instruction = parse(&source_code);
|
|
let expected = call(
|
|
CallInstrValue::InitPeerId,
|
|
CallInstrValue::Literal(service_id),
|
|
CallInstrValue::Literal(function_name),
|
|
Rc::new(vec![Value::Variable(VariableWithLambda::canon_stream(
|
|
canon_stream,
|
|
66,
|
|
))]),
|
|
CallOutputValue::None,
|
|
);
|
|
|
|
assert_eq!(instruction, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn canon_stream_in_triplet() {
|
|
let service_id = "service_id";
|
|
let function_name = "function_name";
|
|
let canon_stream = "#canon_stream";
|
|
let source_code = f!(r#"
|
|
(call {canon_stream} ("{service_id}" "{function_name}") [])
|
|
"#);
|
|
|
|
let instruction = parse(&source_code);
|
|
let expected = call(
|
|
CallInstrValue::Variable(VariableWithLambda::canon_stream(canon_stream, 19)),
|
|
CallInstrValue::Literal(service_id),
|
|
CallInstrValue::Literal(function_name),
|
|
Rc::new(vec![]),
|
|
CallOutputValue::None,
|
|
);
|
|
|
|
assert_eq!(instruction, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn canon_stream_with_lambda_in_triplet() {
|
|
let service_id = "service_id";
|
|
let function_name = "function_name";
|
|
let canon_stream = "#canon_stream";
|
|
let canon_stream_lambda = ".$.[0].path!";
|
|
let source_code = f!(r#"
|
|
(call {canon_stream}{canon_stream_lambda} ("{service_id}" "{function_name}") [])
|
|
"#);
|
|
|
|
let instruction = parse(&source_code);
|
|
let expected = call(
|
|
CallInstrValue::Variable(VariableWithLambda::canon_stream_wl(
|
|
canon_stream,
|
|
LambdaAST::try_from_accessors(vec![
|
|
ValueAccessor::ArrayAccess { idx: 0 },
|
|
ValueAccessor::FieldAccessByName { field_name: "path" },
|
|
])
|
|
.unwrap(),
|
|
19,
|
|
)),
|
|
CallInstrValue::Literal(service_id),
|
|
CallInstrValue::Literal(function_name),
|
|
Rc::new(vec![]),
|
|
CallOutputValue::None,
|
|
);
|
|
|
|
assert_eq!(instruction, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn seq_par_call() {
|
|
let peer_id = "some_peer_id";
|
|
let source_code = f!(r#"
|
|
(seq
|
|
(par
|
|
(call "{peer_id}" ("local_service_id" "local_fn_name") [] result_1)
|
|
(call "{peer_id}" ("service_id" "fn_name") [] g)
|
|
)
|
|
(call "{peer_id}" ("local_service_id" "local_fn_name") [] result_2)
|
|
)"#);
|
|
|
|
let instruction = parse(&source_code);
|
|
let expected = seq(
|
|
par(
|
|
call(
|
|
CallInstrValue::Literal(peer_id),
|
|
CallInstrValue::Literal("local_service_id"),
|
|
CallInstrValue::Literal("local_fn_name"),
|
|
Rc::new(vec![]),
|
|
CallOutputValue::Scalar(Scalar::new("result_1", 108)),
|
|
),
|
|
call(
|
|
CallInstrValue::Literal(peer_id),
|
|
CallInstrValue::Literal("service_id"),
|
|
CallInstrValue::Literal("fn_name"),
|
|
Rc::new(vec![]),
|
|
CallOutputValue::Scalar(Scalar::new("g", 183)),
|
|
),
|
|
),
|
|
call(
|
|
CallInstrValue::Literal(peer_id),
|
|
CallInstrValue::Literal("local_service_id"),
|
|
CallInstrValue::Literal("local_fn_name"),
|
|
Rc::new(vec![]),
|
|
CallOutputValue::Scalar(Scalar::new("result_2", 273)),
|
|
),
|
|
);
|
|
|
|
assert_eq!(instruction, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn seq_with_empty_and_dash() {
|
|
let source_code = r#"
|
|
(seq
|
|
(seq
|
|
(seq
|
|
(call "set_variables" ("" "") ["module-bytes"] module-bytes)
|
|
(call "set_variables" ("" "") ["module_config"] module_config)
|
|
)
|
|
(call "set_variables" ("" "") ["blueprint"] blueprint)
|
|
)
|
|
(seq
|
|
(call "A" ("add_module" "") [module-bytes module_config] module)
|
|
(seq
|
|
(call "A" ("add_blueprint" "") [blueprint] blueprint_id)
|
|
(seq
|
|
(call "A" ("create" "") [blueprint_id] service_id)
|
|
(call "remote_peer_id" ("" "") [service_id] client_result)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
"#;
|
|
|
|
let instruction = parse(source_code);
|
|
let expected = seq(
|
|
seq(
|
|
seq(
|
|
call(
|
|
CallInstrValue::Literal("set_variables"),
|
|
CallInstrValue::Literal(""),
|
|
CallInstrValue::Literal(""),
|
|
Rc::new(vec![Value::Literal("module-bytes")]),
|
|
CallOutputValue::Scalar(Scalar::new("module-bytes", 119)),
|
|
),
|
|
call(
|
|
CallInstrValue::Literal("set_variables"),
|
|
CallInstrValue::Literal(""),
|
|
CallInstrValue::Literal(""),
|
|
Rc::new(vec![Value::Literal("module_config")]),
|
|
CallOutputValue::Scalar(Scalar::new("module_config", 201)),
|
|
),
|
|
),
|
|
call(
|
|
CallInstrValue::Literal("set_variables"),
|
|
CallInstrValue::Literal(""),
|
|
CallInstrValue::Literal(""),
|
|
Rc::new(vec![Value::Literal("blueprint")]),
|
|
CallOutputValue::Scalar(Scalar::new("blueprint", 294)),
|
|
),
|
|
),
|
|
seq(
|
|
call(
|
|
CallInstrValue::Literal("A"),
|
|
CallInstrValue::Literal("add_module"),
|
|
CallInstrValue::Literal(""),
|
|
Rc::new(vec![
|
|
Value::Variable(VariableWithLambda::scalar("module-bytes", 381)),
|
|
Value::Variable(VariableWithLambda::scalar("module_config", 394)),
|
|
]),
|
|
CallOutputValue::Scalar(Scalar::new("module", 409)),
|
|
),
|
|
seq(
|
|
Instruction::Call(Call {
|
|
triplet: Triplet {
|
|
peer_pk: CallInstrValue::Literal("A"),
|
|
service_id: CallInstrValue::Literal("add_blueprint"),
|
|
function_name: CallInstrValue::Literal(""),
|
|
},
|
|
args: Rc::new(vec![Value::Variable(VariableWithLambda::scalar(
|
|
"blueprint",
|
|
490,
|
|
))]),
|
|
output: CallOutputValue::Scalar(Scalar::new("blueprint_id", 501)),
|
|
}),
|
|
seq(
|
|
call(
|
|
CallInstrValue::Literal("A"),
|
|
CallInstrValue::Literal("create"),
|
|
CallInstrValue::Literal(""),
|
|
Rc::new(vec![Value::Variable(VariableWithLambda::scalar(
|
|
"blueprint_id",
|
|
589,
|
|
))]),
|
|
CallOutputValue::Scalar(Scalar::new("service_id", 603)),
|
|
),
|
|
call(
|
|
CallInstrValue::Literal("remote_peer_id"),
|
|
CallInstrValue::Literal(""),
|
|
CallInstrValue::Literal(""),
|
|
Rc::new(vec![Value::Variable(VariableWithLambda::scalar(
|
|
"service_id",
|
|
671,
|
|
))]),
|
|
CallOutputValue::Scalar(Scalar::new("client_result", 683)),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
assert_eq!(instruction, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn no_output() {
|
|
let source_code = r#"
|
|
(call peer (service fname) [])
|
|
"#;
|
|
|
|
let actual = parse(source_code);
|
|
|
|
let expected = call(
|
|
CallInstrValue::Variable(VariableWithLambda::scalar("peer", 15)),
|
|
CallInstrValue::Variable(VariableWithLambda::scalar("service", 21)),
|
|
CallInstrValue::Variable(VariableWithLambda::scalar("fname", 29)),
|
|
Rc::new(vec![]),
|
|
CallOutputValue::None,
|
|
);
|
|
assert_eq!(actual, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn not_defined_scalar_in_lambda() {
|
|
let source_code = r#"
|
|
(seq
|
|
(call "" ("" "") [] value)
|
|
(call "" ("" "") [value.$.[not_defined_scalar]])
|
|
)
|
|
"#;
|
|
|
|
let lexer = crate::AIRLexer::new(source_code);
|
|
|
|
let parser = crate::AIRParser::new();
|
|
let mut errors = Vec::new();
|
|
let mut validator = crate::parser::VariableValidator::new();
|
|
parser
|
|
.parse(source_code, &mut errors, &mut validator, lexer)
|
|
.expect("parser shouldn't fail");
|
|
|
|
let errors = validator.finalize();
|
|
|
|
assert_eq!(errors.len(), 1);
|
|
let error = &errors[0].error;
|
|
let parser_error = match error {
|
|
ParseError::User { error } => error,
|
|
_ => panic!("unexpected error type"),
|
|
};
|
|
|
|
assert!(matches!(
|
|
parser_error,
|
|
ParserError::UndefinedVariable { .. }
|
|
));
|
|
}
|