Skip to content

Commit e3db7d7

Browse files
authored
Feat(kcl-vet): add ast builder for kcl-vet. (#224)
* add invalid test cases * add test case for invalid json/yaml * fix merge conflicts * Feat(kcl-vet): add ast builder for kcl-vet. add ast expr builder for kcl-vet from json/yaml Value. issue #67 * add some comments * add some more test cases * add test cases for no schema name * add test case for unsupported u64 * add test cases for yaml with tag * rm useless test case
1 parent 251d5e5 commit e3db7d7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+5019
-6
lines changed

kclvm/tools/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pub mod lint;
33
pub mod printer;
44
pub mod query;
55
mod util;
6+
pub mod vet;
67

78
#[macro_use]
89
extern crate kclvm_error;

kclvm/tools/src/util/loader.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ use anyhow::{bail, Context, Result};
55
pub(crate) trait Loader<T> {
66
fn load(&self) -> Result<T>;
77
}
8-
pub(crate) enum LoaderKind {
8+
9+
/// Types of verifiable files currently supported by KCL-Vet,
10+
/// currently only YAML files and Json files are supported.
11+
#[derive(Clone, Copy)]
12+
pub enum LoaderKind {
913
YAML,
1014
JSON,
1115
}
@@ -41,6 +45,10 @@ impl DataLoader {
4145
pub(crate) fn get_data(&self) -> &str {
4246
&self.content
4347
}
48+
49+
pub(crate) fn get_kind(&self) -> &LoaderKind {
50+
&self.kind
51+
}
4452
}
4553

4654
impl Loader<serde_json::Value> for DataLoader {
Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,2 @@
1-
{
2-
"name": "John Doe",
3-
"city": "London"
1+
"name": "John Doe",
42
invalid
5-

kclvm/tools/src/util/tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ websites:
221221
panic!("unreachable")
222222
}
223223
Err(err) => {
224-
assert_eq!(format!("{:?}", err), "Failed to String '{\n \"name\": \"John Doe\",\n \"city\": \"London\"\ninvalid\n\n' to Yaml\n\nCaused by:\n did not find expected ',' or '}' at line 4 column 1, while parsing a flow mapping");
224+
assert_eq!(format!("{:?}", err), "Failed to String '\"name\": \"John Doe\",\ninvalid\n' to Yaml\n\nCaused by:\n did not find expected key at line 1 column 19, while parsing a block mapping");
225225
}
226226
}
227227
}
Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
use kclvm_ast::{
2+
ast::{
3+
ConfigEntry, ConfigEntryOperation, ConfigExpr, Expr, ExprContext, Identifier, ListExpr,
4+
NameConstant, NameConstantLit, Node, NodeRef, NumberLit, NumberLitValue, SchemaExpr,
5+
StringLit,
6+
},
7+
node_ref,
8+
};
9+
10+
use crate::util::loader::{DataLoader, Loader, LoaderKind};
11+
use anyhow::{bail, Context, Result};
12+
13+
trait ExprGenerator<T> {
14+
fn generate(&self, value: &T) -> Result<NodeRef<Expr>>;
15+
}
16+
17+
/// `ExprBuilder` will generate ast expr from Json/Yaml.
18+
/// `Object` in Json and `Mapping` in Yaml is mapped to `Schema Expr`.
19+
/// You should set `schema_name` for `Schema Expr` before using `ExprBuilder`.
20+
pub(crate) struct ExprBuilder {
21+
schema_name: Option<String>,
22+
loader: DataLoader,
23+
}
24+
25+
impl ExprBuilder {
26+
pub(crate) fn new_with_file_path(
27+
schema_name: Option<String>,
28+
kind: LoaderKind,
29+
file_path: String,
30+
) -> Result<Self> {
31+
let loader = DataLoader::new_with_file_path(kind, &file_path)
32+
.with_context(|| format!("Failed to Load '{}'", file_path))?;
33+
34+
Ok(Self {
35+
schema_name,
36+
loader,
37+
})
38+
}
39+
40+
pub(crate) fn new_with_str(
41+
schema_name: Option<String>,
42+
kind: LoaderKind,
43+
content: String,
44+
) -> Result<Self> {
45+
let loader = DataLoader::new_with_str(kind, &content)
46+
.with_context(|| format!("Failed to Parse String '{}'", content))?;
47+
48+
Ok(Self {
49+
schema_name,
50+
loader,
51+
})
52+
}
53+
54+
/// Generate ast expr from Json/Yaml depends on `LoaderKind`.
55+
pub(crate) fn build(&self) -> Result<NodeRef<Expr>> {
56+
match self.loader.get_kind() {
57+
LoaderKind::JSON => {
58+
let value = <DataLoader as Loader<serde_json::Value>>::load(&self.loader)
59+
.with_context(|| format!("Failed to Load JSON"))?;
60+
Ok(self
61+
.generate(&value)
62+
.with_context(|| format!("Failed to Load JSON"))?)
63+
}
64+
LoaderKind::YAML => {
65+
let value = <DataLoader as Loader<serde_yaml::Value>>::load(&self.loader)
66+
.with_context(|| format!("Failed to Load YAML"))?;
67+
Ok(self
68+
.generate(&value)
69+
.with_context(|| format!("Failed to Load YAML"))?)
70+
}
71+
}
72+
}
73+
}
74+
75+
impl ExprGenerator<serde_yaml::Value> for ExprBuilder {
76+
fn generate(&self, value: &serde_yaml::Value) -> Result<NodeRef<Expr>> {
77+
match value {
78+
serde_yaml::Value::Null => Ok(node_ref!(Expr::NameConstantLit(NameConstantLit {
79+
value: NameConstant::None,
80+
}))),
81+
serde_yaml::Value::Bool(j_bool) => {
82+
let name_const = match NameConstant::try_from(*j_bool) {
83+
Ok(nc) => nc,
84+
Err(_) => {
85+
bail!("Failed to Load Validated File")
86+
}
87+
};
88+
89+
Ok(node_ref!(Expr::NameConstantLit(NameConstantLit {
90+
value: name_const
91+
})))
92+
}
93+
serde_yaml::Value::Number(j_num) => {
94+
if j_num.is_f64() {
95+
let number_lit = match j_num.as_f64() {
96+
Some(num_f64) => num_f64,
97+
None => {
98+
bail!("Failed to Load Validated File")
99+
}
100+
};
101+
102+
Ok(node_ref!(Expr::NumberLit(NumberLit {
103+
binary_suffix: None,
104+
value: NumberLitValue::Float(number_lit)
105+
})))
106+
} else if j_num.is_i64() {
107+
let number_lit = match j_num.as_i64() {
108+
Some(j_num) => j_num,
109+
None => {
110+
bail!("Failed to Load Validated File")
111+
}
112+
};
113+
114+
Ok(node_ref!(Expr::NumberLit(NumberLit {
115+
binary_suffix: None,
116+
value: NumberLitValue::Int(number_lit)
117+
})))
118+
} else {
119+
bail!("Failed to Load Validated File, Unsupported Unsigned 64");
120+
}
121+
}
122+
serde_yaml::Value::String(j_string) => {
123+
let str_lit = match StringLit::try_from(j_string.to_string()) {
124+
Ok(s) => s,
125+
Err(_) => {
126+
bail!("Failed to Load Validated File")
127+
}
128+
};
129+
Ok(node_ref!(Expr::StringLit(str_lit)))
130+
}
131+
serde_yaml::Value::Sequence(j_arr) => {
132+
let mut j_arr_ast_nodes: Vec<NodeRef<Expr>> = Vec::new();
133+
for j_arr_item in j_arr {
134+
j_arr_ast_nodes.push(
135+
self.generate(j_arr_item)
136+
.with_context(|| format!("Failed to Load Validated File"))?,
137+
);
138+
}
139+
Ok(node_ref!(Expr::List(ListExpr {
140+
ctx: ExprContext::Load,
141+
elts: j_arr_ast_nodes
142+
})))
143+
}
144+
serde_yaml::Value::Mapping(j_map) => {
145+
let mut config_entries: Vec<NodeRef<ConfigEntry>> = Vec::new();
146+
147+
for (k, v) in j_map.iter() {
148+
let k = self
149+
.generate(k)
150+
.with_context(|| format!("Failed to Load Validated File"))?;
151+
let v = self
152+
.generate(v)
153+
.with_context(|| format!("Failed to Load Validated File"))?;
154+
155+
let config_entry = node_ref!(ConfigEntry {
156+
key: Some(k),
157+
value: v,
158+
operation: ConfigEntryOperation::Union,
159+
insert_index: -1
160+
});
161+
config_entries.push(config_entry);
162+
}
163+
164+
let config_expr = node_ref!(Expr::Config(ConfigExpr {
165+
items: config_entries
166+
}));
167+
168+
match &self.schema_name {
169+
Some(s_name) => {
170+
let iden = node_ref!(Identifier {
171+
names: vec![s_name.to_string()],
172+
pkgpath: String::new(),
173+
ctx: ExprContext::Load
174+
});
175+
Ok(node_ref!(Expr::Schema(SchemaExpr {
176+
name: iden,
177+
config: config_expr,
178+
args: vec![],
179+
kwargs: vec![]
180+
})))
181+
}
182+
None => Ok(config_expr),
183+
}
184+
}
185+
serde_yaml::Value::Tagged(_) => {
186+
bail!("Failed to Load Validated File, Unsupported Yaml Tagged.")
187+
}
188+
}
189+
}
190+
}
191+
192+
impl ExprGenerator<serde_json::Value> for ExprBuilder {
193+
fn generate(&self, value: &serde_json::Value) -> Result<NodeRef<Expr>> {
194+
match value {
195+
serde_json::Value::Null => Ok(node_ref!(Expr::NameConstantLit(NameConstantLit {
196+
value: NameConstant::None,
197+
}))),
198+
serde_json::Value::Bool(j_bool) => {
199+
let name_const = match NameConstant::try_from(*j_bool) {
200+
Ok(nc) => nc,
201+
Err(_) => {
202+
bail!("Failed to Load Validated File")
203+
}
204+
};
205+
206+
Ok(node_ref!(Expr::NameConstantLit(NameConstantLit {
207+
value: name_const
208+
})))
209+
}
210+
serde_json::Value::Number(j_num) => {
211+
if j_num.is_f64() {
212+
let number_lit = match j_num.as_f64() {
213+
Some(num_f64) => num_f64,
214+
None => {
215+
bail!("Failed to Load Validated File")
216+
}
217+
};
218+
219+
Ok(node_ref!(Expr::NumberLit(NumberLit {
220+
binary_suffix: None,
221+
value: NumberLitValue::Float(number_lit)
222+
})))
223+
} else if j_num.is_i64() {
224+
let number_lit = match j_num.as_i64() {
225+
Some(j_num) => j_num,
226+
None => {
227+
bail!("Failed to Load Validated File")
228+
}
229+
};
230+
231+
Ok(node_ref!(Expr::NumberLit(NumberLit {
232+
binary_suffix: None,
233+
value: NumberLitValue::Int(number_lit)
234+
})))
235+
} else {
236+
bail!("Failed to Load Validated File, Unsupported Unsigned 64");
237+
}
238+
}
239+
serde_json::Value::String(j_string) => {
240+
let str_lit = match StringLit::try_from(j_string.to_string()) {
241+
Ok(s) => s,
242+
Err(_) => {
243+
bail!("Failed to Load Validated File")
244+
}
245+
};
246+
247+
Ok(node_ref!(Expr::StringLit(str_lit)))
248+
}
249+
serde_json::Value::Array(j_arr) => {
250+
let mut j_arr_ast_nodes: Vec<NodeRef<Expr>> = Vec::new();
251+
for j_arr_item in j_arr {
252+
j_arr_ast_nodes.push(
253+
self.generate(j_arr_item)
254+
.with_context(|| format!("Failed to Load Validated File"))?,
255+
);
256+
}
257+
Ok(node_ref!(Expr::List(ListExpr {
258+
ctx: ExprContext::Load,
259+
elts: j_arr_ast_nodes
260+
})))
261+
}
262+
serde_json::Value::Object(j_map) => {
263+
let mut config_entries: Vec<NodeRef<ConfigEntry>> = Vec::new();
264+
265+
for (k, v) in j_map.iter() {
266+
let k = match StringLit::try_from(k.to_string()) {
267+
Ok(s) => s,
268+
Err(_) => {
269+
bail!("Failed to Load Validated File")
270+
}
271+
};
272+
let v = self
273+
.generate(v)
274+
.with_context(|| format!("Failed to Load Validated File"))?;
275+
276+
let config_entry = node_ref!(ConfigEntry {
277+
key: Some(node_ref!(Expr::StringLit(k))),
278+
value: v,
279+
operation: ConfigEntryOperation::Union,
280+
insert_index: -1
281+
});
282+
config_entries.push(config_entry);
283+
}
284+
285+
let config_expr = node_ref!(Expr::Config(ConfigExpr {
286+
items: config_entries
287+
}));
288+
289+
match &self.schema_name {
290+
Some(s_name) => {
291+
let iden = node_ref!(Identifier {
292+
names: vec![s_name.to_string()],
293+
pkgpath: String::new(),
294+
ctx: ExprContext::Load
295+
});
296+
Ok(node_ref!(Expr::Schema(SchemaExpr {
297+
name: iden,
298+
config: config_expr,
299+
args: vec![],
300+
kwargs: vec![]
301+
})))
302+
}
303+
None => Ok(config_expr),
304+
}
305+
}
306+
}
307+
}
308+
}

kclvm/tools/src/vet/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
pub mod expr_builder;
2+
3+
#[cfg(test)]
4+
mod tests;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
languages:
2+
- Ruby
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "John Doe",
3+
"city": "London"
4+
invalid
5+
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"u64_value": 9223372036854775808
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"u64_value": 9223372036854775808
3+
}

0 commit comments

Comments
 (0)