From e57c8a8f9e5a8214ec1bec8edc79133561ef8ddd Mon Sep 17 00:00:00 2001 From: peefy Date: Mon, 1 Apr 2024 16:51:52 +0800 Subject: [PATCH 1/3] feat: type convension for the evaluator Signed-off-by: peefy --- kclvm/compiler/src/codegen/llvm/node.rs | 6 +- kclvm/evaluator/src/calculation.rs | 196 ++++++--------- kclvm/evaluator/src/lib.rs | 7 +- kclvm/evaluator/src/node.rs | 79 +++--- kclvm/evaluator/src/proxy.rs | 51 +++- kclvm/evaluator/src/rule.rs | 21 +- kclvm/evaluator/src/schema.rs | 193 +++++++++++++- kclvm/evaluator/src/scope.rs | 74 +++++- kclvm/evaluator/src/ty.rs | 197 +++++++++++++++ kclvm/evaluator/src/union.rs | 318 ++++++++++++++++++++++++ kclvm/evaluator/src/value.rs | 5 - kclvm/runtime/src/value/val_clone.rs | 2 +- kclvm/runtime/src/value/val_func.rs | 2 +- kclvm/runtime/src/value/val_schema.rs | 49 ---- kclvm/runtime/src/value/val_type.rs | 2 +- kclvm/runtime/src/value/val_union.rs | 10 +- 16 files changed, 953 insertions(+), 259 deletions(-) create mode 100644 kclvm/evaluator/src/ty.rs create mode 100644 kclvm/evaluator/src/union.rs diff --git a/kclvm/compiler/src/codegen/llvm/node.rs b/kclvm/compiler/src/codegen/llvm/node.rs index 1adfbf7e4..9ee100eab 100644 --- a/kclvm/compiler/src/codegen/llvm/node.rs +++ b/kclvm/compiler/src/codegen/llvm/node.rs @@ -1047,7 +1047,7 @@ impl<'ctx> TypedResultWalker<'ctx> for LLVMCodeGenContext<'ctx> { // Schema function closures let list_value = self.list_values(&[ // is_sub_schema - self.bool_value(false), + self.bool_value(true), schema_config_meta, schema_config, schema_value, @@ -2827,9 +2827,7 @@ impl<'ctx> LLVMCodeGenContext<'ctx> { }; // Store a local variable for every entry key. let key = match &optional_name { - Some(name) if !self.local_vars.borrow().contains(name) => { - self.string_value(name) - } + Some(name) if !self.is_local_var(name) => self.string_value(name), _ => self.walk_expr(key)?, }; self.dict_insert_with_key_value( diff --git a/kclvm/evaluator/src/calculation.rs b/kclvm/evaluator/src/calculation.rs index 90b5ed24a..39d3b9f4c 100644 --- a/kclvm/evaluator/src/calculation.rs +++ b/kclvm/evaluator/src/calculation.rs @@ -1,8 +1,10 @@ /* Calculation methods */ use kclvm_ast::ast; -use kclvm_runtime::{type_pack_and_check, ConfigEntryOperationKind, UnionOptions, Value, ValueRef}; +use kclvm_runtime::{ConfigEntryOperationKind, DictValue, UnionOptions, Value, ValueRef}; +use crate::ty::{resolve_schema, type_pack_and_check}; +use crate::union::union_entry; use crate::Evaluator; impl<'ctx> Evaluator<'ctx> { @@ -59,7 +61,16 @@ impl<'ctx> Evaluator<'ctx> { /// lhs | rhs #[inline] pub(crate) fn bit_or(&self, lhs: ValueRef, rhs: ValueRef) -> ValueRef { - lhs.bin_bit_or(&mut self.runtime_ctx.borrow_mut(), &rhs) + if let (Value::int_value(a), Value::int_value(b)) = (&*lhs.rc.borrow(), &*rhs.rc.borrow()) { + return ValueRef::int(*a | *b); + }; + union_entry( + self, + &mut lhs.deep_copy(), + &rhs, + true, + &UnionOptions::default(), + ) } /// lhs ^ rhs #[inline] @@ -109,11 +120,7 @@ impl<'ctx> Evaluator<'ctx> { /// lhs as rhs #[inline] pub(crate) fn r#as(&self, lhs: ValueRef, rhs: ValueRef) -> ValueRef { - type_pack_and_check( - &mut self.runtime_ctx.borrow_mut(), - &lhs, - vec![&rhs.as_str()], - ) + type_pack_and_check(self, &lhs, vec![&rhs.as_str()]) } /// lhs is rhs #[inline] @@ -138,11 +145,6 @@ impl<'ctx> Evaluator<'ctx> { } impl<'ctx> Evaluator<'ctx> { - /// Value subscript a[b] - #[inline] - pub(crate) fn _value_subscript(&self, value: &ValueRef, item: &ValueRef) -> ValueRef { - value.bin_subscr(item) - } /// Value is truth function, return i1 value. #[inline] pub(crate) fn value_is_truthy(&self, value: &ValueRef) -> bool { @@ -165,44 +167,22 @@ impl<'ctx> Evaluator<'ctx> { idempotent_check: false, config_resolve: true, }; - let ctx = &mut self.runtime_ctx.borrow_mut(); if rhs.is_config() { let dict = rhs.as_dict_ref(); for k in dict.values.keys() { let entry = rhs.dict_get_entry(k).unwrap(); - lhs.union_entry(ctx, &entry, true, &opts); + union_entry(self, lhs, &entry, true, &opts); // Has type annotation if let Some(ty) = attr_map.get(k) { let value = lhs.dict_get_value(k).unwrap(); - lhs.dict_update_key_value(k, type_pack_and_check(ctx, &value, vec![ty])); + lhs.dict_update_key_value(k, type_pack_and_check(self, &value, vec![ty])); } } lhs.clone() } else { - lhs.union_entry(ctx, rhs, true, &opts) + union_entry(self, lhs, rhs, true, &opts) } } - // List get the item using the index. - #[inline] - pub(crate) fn _list_get(&self, list: &ValueRef, index: ValueRef) -> ValueRef { - list.list_get(index.as_int() as isize).unwrap() - } - // List set the item using the index. - #[inline] - pub(crate) fn _list_set(&self, list: &mut ValueRef, index: ValueRef, value: &ValueRef) { - list.list_set(index.as_int() as usize, value) - } - // List slice. - #[inline] - pub(crate) fn _list_slice( - &self, - list: &ValueRef, - start: &ValueRef, - stop: &ValueRef, - step: &ValueRef, - ) -> ValueRef { - list.list_slice(start, stop, step) - } /// Append a item into the list. #[inline] pub(crate) fn list_append(&self, list: &mut ValueRef, item: &ValueRef) { @@ -213,61 +193,10 @@ impl<'ctx> Evaluator<'ctx> { pub(crate) fn list_append_unpack(&self, list: &mut ValueRef, item: &ValueRef) { list.list_append_unpack(item) } - /// Runtime list value pop - #[inline] - pub(crate) fn _list_pop(&self, list: &mut ValueRef) -> Option { - list.list_pop() - } - /// Runtime list pop the first value - #[inline] - pub(crate) fn _list_pop_first(&self, list: &mut ValueRef) -> Option { - list.list_pop_first() - } - /// List clear value. - #[inline] - pub(crate) fn _list_clear(&self, list: &mut ValueRef) { - list.list_clear() - } - /// Return number of occurrences of the list value. - #[inline] - pub(crate) fn _list_count(&self, list: &ValueRef, item: &ValueRef) -> ValueRef { - ValueRef::int(list.list_count(item) as i64) - } - /// Return first index of the list value. Panic if the value is not present. - #[inline] - pub(crate) fn _list_find(&self, list: &ValueRef, item: &ValueRef) -> isize { - list.list_find(item) - } - /// Insert object before index of the list value. - #[inline] - pub(crate) fn _list_insert(&self, list: &mut ValueRef, index: &ValueRef, value: &ValueRef) { - list.list_insert_at(index.as_int() as usize, value) - } - /// List length. - #[inline] - pub(crate) fn _list_len(&self, list: &ValueRef) -> usize { - list.len() - } - /// Dict get the value of the key. - #[inline] - pub(crate) fn _dict_get(&self, dict: &ValueRef, key: &ValueRef) -> ValueRef { - dict.dict_get(key).unwrap() - } #[inline] pub(crate) fn dict_get_value(&self, dict: &ValueRef, key: &str) -> ValueRef { - dict.dict_get_value(key).unwrap() + dict.dict_get_value(key).unwrap_or(self.undefined_value()) } - /// Dict clear value. - #[inline] - pub(crate) fn _dict_clear(&self, dict: &mut ValueRef) { - dict.dict_clear() - } - /// Dict length. - #[inline] - pub(crate) fn _dict_len(&self, dict: &ValueRef) -> usize { - dict.len() - } - /// Insert a dict entry including key, value, op and insert_index into the dict, /// and the type of key is `&str` #[inline] @@ -284,13 +213,7 @@ impl<'ctx> Evaluator<'ctx> { ast::ConfigEntryOperation::Override => ConfigEntryOperationKind::Override, ast::ConfigEntryOperation::Insert => ConfigEntryOperationKind::Insert, }; - dict.dict_insert( - &mut self.runtime_ctx.borrow_mut(), - key, - value, - op, - insert_index, - ); + self.dict_merge_key_value_pair(dict, key, value, op, insert_index, false); } /// Insert a dict entry including key, value, op and insert_index into the dict, @@ -317,26 +240,10 @@ impl<'ctx> Evaluator<'ctx> { } }; if attr_map.contains_key(key) { - let v = type_pack_and_check( - &mut self.runtime_ctx.borrow_mut(), - value, - vec![attr_map.get(key).unwrap()], - ); - dict.dict_merge( - &mut self.runtime_ctx.borrow_mut(), - key, - &v, - op, - insert_index, - ); + let v = type_pack_and_check(self, value, vec![attr_map.get(key).unwrap()]); + self.dict_merge_key_value_pair(dict, key, &v, op, insert_index, false); } else { - dict.dict_merge( - &mut self.runtime_ctx.borrow_mut(), - key, - value, - op, - insert_index, - ); + self.dict_merge_key_value_pair(dict, key, value, op, insert_index, false); } } @@ -349,12 +256,67 @@ impl<'ctx> Evaluator<'ctx> { /// Insert an entry including key and value into the dict, and merge the original entry. #[inline] pub(crate) fn dict_insert_merge_value(&self, dict: &mut ValueRef, key: &str, value: &ValueRef) { - dict.dict_insert( - &mut self.runtime_ctx.borrow_mut(), + self.dict_merge_key_value_pair( + dict, key, value, ConfigEntryOperationKind::Union, -1, + false, ); } + + /// Set dict key with the value. When the dict is a schema and resolve schema validations. + pub(crate) fn dict_set_value(&self, p: &mut ValueRef, key: &str, val: &ValueRef) { + if p.is_config() { + p.dict_update_key_value(key, val.clone()); + if p.is_schema() { + let schema: ValueRef; + { + let schema_value = p.as_schema(); + let mut config_keys = schema_value.config_keys.clone(); + config_keys.push(key.to_string()); + schema = resolve_schema(self, p, &config_keys); + } + p.schema_update_with_schema(&schema); + } + } else { + panic!( + "failed to update the dict. An iterable of key-value pairs was expected, but got {}. Check if the syntax for updating the dictionary with the attribute '{}' is correct", + p.type_str(), + key + ); + } + } + + /// Private dict merge key value pair with the idempotent check option + pub(crate) fn dict_merge_key_value_pair( + &self, + p: &mut ValueRef, + key: &str, + v: &ValueRef, + op: ConfigEntryOperationKind, + insert_index: i32, + idempotent_check: bool, + ) { + if p.is_config() { + let mut dict: DictValue = Default::default(); + dict.values.insert(key.to_string(), v.clone()); + dict.ops.insert(key.to_string(), op); + dict.insert_indexs.insert(key.to_string(), insert_index); + union_entry( + self, + p, + &ValueRef::from(Value::dict_value(Box::new(dict))), + true, + &UnionOptions { + config_resolve: false, + idempotent_check, + ..Default::default() + }, + ); + } else { + panic!("invalid dict insert value: {}", p.type_str()) + } + } } diff --git a/kclvm/evaluator/src/lib.rs b/kclvm/evaluator/src/lib.rs index 6653c8a53..873aed937 100644 --- a/kclvm/evaluator/src/lib.rs +++ b/kclvm/evaluator/src/lib.rs @@ -14,12 +14,15 @@ mod proxy; mod rule; mod schema; mod scope; +mod ty; +mod union; mod value; extern crate kclvm_error; use context::EvaluatorContext; -use generational_arena::Arena; +use generational_arena::{Arena, Index}; +use indexmap::IndexMap; use proxy::{Frame, Proxy}; use std::panic::RefUnwindSafe; use std::rc::Rc; @@ -46,6 +49,7 @@ pub struct Evaluator<'ctx> { pub program: &'ctx ast::Program, pub ctx: RefCell, pub frames: RefCell>>, + pub schemas: RefCell>, pub runtime_ctx: Rc>, } @@ -67,6 +71,7 @@ impl<'ctx> Evaluator<'ctx> { runtime_ctx, program, frames: RefCell::new(Arena::new()), + schemas: RefCell::new(IndexMap::new()), } } diff --git a/kclvm/evaluator/src/node.rs b/kclvm/evaluator/src/node.rs index cb2f4484e..1b45c6f38 100644 --- a/kclvm/evaluator/src/node.rs +++ b/kclvm/evaluator/src/node.rs @@ -10,8 +10,8 @@ use kclvm_ast::walker::TypedResultWalker; use kclvm_runtime::val_func::invoke_function; use kclvm_runtime::walker::walk_value_mut; use kclvm_runtime::{ - schema_assert, schema_runtime_type, type_pack_and_check, ApiFunc, ConfigEntryOperationKind, - DecoratorValue, RuntimeErrorType, UnionOptions, ValueRef, PKG_PATH_PREFIX, + schema_assert, schema_runtime_type, ApiFunc, ConfigEntryOperationKind, DecoratorValue, + RuntimeErrorType, UnionOptions, ValueRef, PKG_PATH_PREFIX, }; use kclvm_sema::{builtin, plugin}; @@ -20,6 +20,8 @@ use crate::lazy::EvalBodyRange; use crate::proxy::Proxy; use crate::rule::{rule_body, rule_check, RuleCaller, RuleEvalContext}; use crate::schema::{schema_body, schema_check, SchemaCaller, SchemaEvalContext}; +use crate::ty::type_pack_and_check; +use crate::union::union_entry; use crate::{error as kcl_error, GLOBAL_LEVEL, INNER_LEVEL}; use crate::{EvalResult, Evaluator}; @@ -108,7 +110,7 @@ impl<'ctx> TypedResultWalker<'ctx> for Evaluator<'ctx> { None, ) .unwrap_or(self.undefined_value()); - let value = org_value.bin_bit_or(&mut self.runtime_ctx.borrow_mut(), &value); + let value = self.bit_or(org_value, value); // Store the identifier value self.walk_identifier_with_ctx( &unification_stmt.target.node, @@ -139,11 +141,7 @@ impl<'ctx> TypedResultWalker<'ctx> for Evaluator<'ctx> { // Runtime type cast if exists the type annotation. if let Some(ty) = &assign_stmt.ty { let is_in_schema = self.is_in_schema() || self.is_in_schema_expr(); - value = type_pack_and_check( - &mut self.runtime_ctx.borrow_mut(), - &value, - vec![&ty.node.to_string()], - ); + value = type_pack_and_check(self, &value, vec![&ty.node.to_string()]); // Schema required attribute validating. if !is_in_schema { walk_value_mut(&value, &mut |value: &ValueRef| { @@ -282,7 +280,7 @@ impl<'ctx> TypedResultWalker<'ctx> for Evaluator<'ctx> { fn walk_schema_stmt(&self, schema_stmt: &'ctx ast::SchemaStmt) -> Self::Result { let body = Arc::new(schema_body); let check = Arc::new(schema_check); - let proxy = SchemaCaller { + let caller = SchemaCaller { ctx: Rc::new(RefCell::new(SchemaEvalContext::new_with_node( schema_stmt.clone(), ))), @@ -290,7 +288,7 @@ impl<'ctx> TypedResultWalker<'ctx> for Evaluator<'ctx> { check, }; // Add function to the global state - let index = self.add_schema(proxy); + let index = self.add_schema(caller); let runtime_type = schema_runtime_type(&schema_stmt.name.node, &self.current_pkgpath()); let function = self.proxy_function_value_with_type(index, &runtime_type); // Store or add the variable in the scope @@ -298,13 +296,14 @@ impl<'ctx> TypedResultWalker<'ctx> for Evaluator<'ctx> { if !self.store_variable(name, function.clone()) { self.add_variable(name, function.clone()); } + self.schemas.borrow_mut().insert(runtime_type, index); Ok(function) } fn walk_rule_stmt(&self, rule_stmt: &'ctx ast::RuleStmt) -> Self::Result { let body = Arc::new(rule_body); let check = Arc::new(rule_check); - let proxy = RuleCaller { + let caller = RuleCaller { ctx: Rc::new(RefCell::new(RuleEvalContext::new_with_node( rule_stmt.clone(), ))), @@ -312,7 +311,7 @@ impl<'ctx> TypedResultWalker<'ctx> for Evaluator<'ctx> { check, }; // Add function to the global state - let index = self.add_rule(proxy); + let index = self.add_rule(caller); let function = self.proxy_function_value(index); // Store or add the variable in the scope let name = &rule_stmt.name.node; @@ -477,11 +476,12 @@ impl<'ctx> TypedResultWalker<'ctx> for Evaluator<'ctx> { self.add_target_var(name); for decorator in &schema_attr.decorators { self.walk_decorator_with_name(&decorator.node, Some(name), false) - .expect(kcl_error::RUNTIME_ERROR_MSG); + .expect(kcl_error::INTERNAL_ERROR_MSG); } - let (mut schema_value, config_value) = self - .get_schema_and_config() - .expect(kcl_error::RUNTIME_ERROR_MSG); + let (mut schema_value, config_value, _) = self + .get_schema_or_rule_config_info() + .expect(kcl_error::INTERNAL_ERROR_MSG); + schema_value.update_attr_map(name, &schema_attr.ty.node.to_string()); if let Some(entry) = config_value.dict_get_entry(name) { let is_override_attr = { let is_override_op = matches!( @@ -530,10 +530,13 @@ impl<'ctx> TypedResultWalker<'ctx> for Evaluator<'ctx> { let value = match &schema_attr.value { Some(value) => self.walk_expr(value).expect(kcl_error::RUNTIME_ERROR_MSG), None => { + let value = self.undefined_value(); // When the schema has no default value and config value, // set it with a undefined value. - let value = self.undefined_value(); - self.dict_insert_value(&mut schema_value, name, &value); + // Note that do not override the existed attribute value. + if schema_value.dict_get_entry(name).is_none() { + self.dict_insert_value(&mut schema_value, name, &value); + } value } }; @@ -914,12 +917,23 @@ impl<'ctx> TypedResultWalker<'ctx> for Evaluator<'ctx> { let value = (schema.body)(self, &schema.ctx, &list_value, &dict_value); self.pop_pkgpath(); value + } else if let Proxy::Rule(rule) = &frame.proxy { + self.push_pkgpath(&frame.pkgpath); + // Set new rule and config + { + let mut ctx = rule.ctx.borrow_mut(); + ctx.reset_with_config(config_value, config_meta); + } + let value = (rule.body)(self, &rule.ctx, &list_value, &dict_value); + self.pop_pkgpath(); + value } else { self.undefined_value() } } else { - schema_type.deep_copy().union_entry( - &mut self.runtime_ctx.borrow_mut(), + union_entry( + self, + &mut schema_type.deep_copy(), &config_value, true, &UnionOptions::default(), @@ -950,15 +964,15 @@ impl<'ctx> TypedResultWalker<'ctx> for Evaluator<'ctx> { .expect(kcl_error::RUNTIME_ERROR_MSG); let msg = { if let Some(msg) = &check_expr.msg { - self.walk_expr(msg).expect(kcl_error::RUNTIME_ERROR_MSG) + self.walk_expr(msg).expect(kcl_error::INTERNAL_ERROR_MSG) } else { self.string_value("") } } .as_str(); - let config_meta = self - .get_schema_config_meta() - .expect(kcl_error::RUNTIME_ERROR_MSG); + let (_, _, config_meta) = self + .get_schema_or_rule_config_info() + .expect(kcl_error::INTERNAL_ERROR_MSG); schema_assert( &mut self.runtime_ctx.borrow_mut(), &check_result, @@ -1196,7 +1210,9 @@ impl<'ctx> Evaluator<'ctx> { let is_local_var = self.is_local_var(name); let value = right_value.clone().expect(kcl_error::INTERNAL_ERROR_MSG); match (is_local_var, is_in_schema) { - (false, true) => self.update_schema_scope_value(name, Some(&value)), + (false, true) => { + self.update_schema_or_rule_scope_value(name, Some(&value)) + } _ => self.add_variable(name, value), } } @@ -1224,8 +1240,8 @@ impl<'ctx> Evaluator<'ctx> { value = value.load_attr(attr); } ast::ExprContext::Store => { - value.dict_set_value( - &mut self.runtime_ctx.borrow_mut(), + self.dict_set_value( + &mut value, attr, &right_value.clone().expect(kcl_error::INTERNAL_ERROR_MSG), ); @@ -1238,7 +1254,7 @@ impl<'ctx> Evaluator<'ctx> { && !is_in_lambda && !is_local_var { - self.update_schema_scope_value(name, None); + self.update_schema_or_rule_scope_value(name, None); } } } @@ -1266,11 +1282,8 @@ impl<'ctx> Evaluator<'ctx> { ) -> EvalResult { let mut list_value = self.list_value(); let mut dict_value = self.dict_value(); - let (_, config_value) = self - .get_schema_and_config() - .expect(kcl_error::INTERNAL_ERROR_MSG); - let config_meta = self - .get_schema_config_meta() + let (_, config_value, config_meta) = self + .get_schema_or_rule_config_info() .expect(kcl_error::INTERNAL_ERROR_MSG); for arg in &decorator.args { let value = self.walk_expr(arg).expect(kcl_error::RUNTIME_ERROR_MSG); diff --git a/kclvm/evaluator/src/proxy.rs b/kclvm/evaluator/src/proxy.rs index 6832d279a..6546afab7 100644 --- a/kclvm/evaluator/src/proxy.rs +++ b/kclvm/evaluator/src/proxy.rs @@ -2,7 +2,7 @@ use kclvm_runtime::ValueRef; use crate::error as kcl_error; use crate::func::FunctionCaller; -use crate::rule::RuleCaller; +use crate::rule::{RuleCaller, RuleEvalContextRef}; use crate::schema::{SchemaCaller, SchemaEvalContextRef}; use crate::Evaluator; @@ -27,9 +27,8 @@ pub(crate) fn call_schema_body( func: &ValueRef, args: &ValueRef, kwargs: &ValueRef, - ctx: Option<&SchemaEvalContextRef>, + ctx: &SchemaEvalContextRef, ) -> ValueRef { - // Call base schema function if let Some(index) = func.try_get_proxy() { let frame = { let frames = s.frames.borrow(); @@ -40,23 +39,49 @@ pub(crate) fn call_schema_body( }; if let Proxy::Schema(schema) = &frame.proxy { s.push_pkgpath(&frame.pkgpath); - if let Some(ctx) = ctx { - schema.ctx.borrow_mut().get_value_from(&ctx.borrow()) + { + schema.ctx.borrow_mut().set_info_with_schema(&ctx.borrow()) } let value = (schema.body)(s, &schema.ctx, args, kwargs); s.pop_pkgpath(); value } else { - match ctx { - Some(ctx) => ctx.borrow().value.clone(), - None => s.undefined_value(), - } + ctx.borrow().value.clone() } } else { - match ctx { - Some(ctx) => ctx.borrow().value.clone(), - None => s.undefined_value(), + ctx.borrow().value.clone() + } +} + +/// Call the associated schemas including parent schema and mixin schema +pub(crate) fn call_schema_body_from_rule( + s: &Evaluator, + func: &ValueRef, + args: &ValueRef, + kwargs: &ValueRef, + ctx: &RuleEvalContextRef, +) -> ValueRef { + if let Some(index) = func.try_get_proxy() { + let frame = { + let frames = s.frames.borrow(); + frames + .get(index) + .expect(kcl_error::INTERNAL_ERROR_MSG) + .clone() + }; + if let Proxy::Schema(schema) = &frame.proxy { + s.push_pkgpath(&frame.pkgpath); + { + schema.ctx.borrow_mut().set_info_with_rule(&ctx.borrow()) + } + let value = (schema.body)(s, &schema.ctx, args, kwargs); + s.pop_pkgpath(); + value + } else { + ctx.borrow().value.clone() } + } else { + ctx.borrow().value.clone() } } @@ -78,7 +103,7 @@ pub(crate) fn call_schema_check( if let Proxy::Schema(schema) = &frame.proxy { s.push_pkgpath(&frame.pkgpath); if let Some(ctx) = ctx { - schema.ctx.borrow_mut().get_value_from(&ctx.borrow()) + schema.ctx.borrow_mut().set_info_with_schema(&ctx.borrow()) } (schema.check)(s, &schema.ctx, args, kwargs); s.pop_pkgpath(); diff --git a/kclvm/evaluator/src/rule.rs b/kclvm/evaluator/src/rule.rs index 06a32bd07..7107ad4c9 100644 --- a/kclvm/evaluator/src/rule.rs +++ b/kclvm/evaluator/src/rule.rs @@ -9,7 +9,7 @@ use kclvm_runtime::ValueRef; use crate::error as kcl_error; use crate::lazy::LazyEvalScope; -use crate::proxy::{call_rule_check, call_schema_body}; +use crate::proxy::{call_rule_check, call_schema_body_from_rule}; use crate::Evaluator; pub type RuleBodyHandler = @@ -19,7 +19,7 @@ pub type RuleEvalContextRef = Rc>; /// Proxy functions represent the saved functions of the runtime its, /// rather than executing KCL defined functions or plugin functions. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct RuleEvalContext { pub node: ast::RuleStmt, pub scope: LazyEvalScope, @@ -46,7 +46,7 @@ impl RuleEvalContext { } } - /// Reset schema evaluation context state. + /// Reset rule evaluation context state. pub fn reset(&mut self) { self.value = ValueRef::dict(None); self.config = ValueRef::dict(None); @@ -55,6 +55,16 @@ impl RuleEvalContext { self.is_sub_schema = true; self.record_instance = true; } + + /// Reset rule evaluation context state. + pub fn reset_with_config(&mut self, config: ValueRef, config_meta: ValueRef) { + self.config = config; + self.config_meta = config_meta; + self.value = ValueRef::dict(None); + self.optional_mapping = ValueRef::dict(None); + self.is_sub_schema = true; + self.record_instance = true; + } } #[derive(Clone)] @@ -72,7 +82,7 @@ pub fn rule_body( kwargs: &ValueRef, ) -> ValueRef { s.push_schema(); - s.enter_scope(); + s.enter_scope_with_schema_rule_context(ctx); let rule_name = &ctx.borrow().node.name.node; // Evaluate arguments and keyword arguments and store values to local variables. s.walk_arguments(&ctx.borrow().node.args, args, kwargs); @@ -82,7 +92,7 @@ pub fn rule_body( .walk_identifier_with_ctx(&for_host_name.node, &ast::ExprContext::Load, None) .expect(kcl_error::RUNTIME_ERROR_MSG); // Call base schema function - call_schema_body(s, &base_constructor_func, args, kwargs, None) + call_schema_body_from_rule(s, &base_constructor_func, args, kwargs, ctx) } else { ctx.borrow().value.clone() }; @@ -99,7 +109,6 @@ pub fn rule_body( // Call rule check block function rule_check(s, ctx, args, kwargs); } - s.leave_scope(); s.pop_schema(); rule_value diff --git a/kclvm/evaluator/src/schema.rs b/kclvm/evaluator/src/schema.rs index 59c04c5e6..33a300405 100644 --- a/kclvm/evaluator/src/schema.rs +++ b/kclvm/evaluator/src/schema.rs @@ -5,10 +5,12 @@ use std::sync::Arc; use indexmap::IndexMap; use kclvm_ast::ast; use kclvm_ast::walker::TypedResultWalker; -use kclvm_runtime::{schema_runtime_type, ValueRef, MAIN_PKG_PATH}; +use kclvm_runtime::{schema_runtime_type, ConfigEntryOperationKind, ValueRef, MAIN_PKG_PATH}; use crate::lazy::LazyEvalScope; use crate::proxy::{call_schema_body, call_schema_check}; +use crate::rule::RuleEvalContext; +use crate::ty::type_pack_and_check; use crate::{error as kcl_error, Proxy}; use crate::{Evaluator, INNER_LEVEL}; @@ -58,7 +60,7 @@ impl SchemaEvalContext { /// Pass value references from other schema eval context. /// Note that do not change the schema node. - pub fn get_value_from(&mut self, other: &SchemaEvalContext) { + pub fn set_info_with_schema(&mut self, other: &SchemaEvalContext) { self.config = other.config.clone(); self.config_meta = other.config_meta.clone(); self.value = other.value.clone(); @@ -66,6 +68,118 @@ impl SchemaEvalContext { self.record_instance = other.record_instance; self.is_sub_schema = false; } + + /// Pass value references from other rule eval context. + /// Note that do not change the schema node. + pub fn set_info_with_rule(&mut self, other: &RuleEvalContext) { + self.config = other.config.clone(); + self.config_meta = other.config_meta.clone(); + self.value = other.value.clone(); + self.optional_mapping = other.optional_mapping.clone(); + self.record_instance = other.record_instance; + // Note that for the host schema, it will evaluate the final value. + self.is_sub_schema = true; + } + + /// Update parent schema and mixin schema information + pub fn get_parent_schema( + s: &Evaluator, + ctx: &SchemaEvalContextRef, + ) -> Option { + if let Some(parent_name) = &ctx.borrow().node.parent_name { + let func = s + .walk_identifier_with_ctx(&parent_name.node, &ast::ExprContext::Load, None) + .expect(kcl_error::RUNTIME_ERROR_MSG); + if let Some(index) = func.try_get_proxy() { + let frame = { + let frames = s.frames.borrow(); + frames + .get(index) + .expect(kcl_error::INTERNAL_ERROR_MSG) + .clone() + }; + if let Proxy::Schema(schema) = &frame.proxy { + Some(schema.ctx.clone()) + } else { + None + } + } else { + None + } + } else { + None + } + } + + /// Update parent schema and mixin schema information + pub fn get_mixin_schemas( + s: &Evaluator, + ctx: &SchemaEvalContextRef, + ) -> Vec { + let mut results = vec![]; + for mixin in &ctx.borrow().node.mixins { + let func = s + .walk_identifier_with_ctx(&mixin.node, &ast::ExprContext::Load, None) + .expect(kcl_error::RUNTIME_ERROR_MSG); + if let Some(index) = func.try_get_proxy() { + let frame = { + let frames = s.frames.borrow(); + frames + .get(index) + .expect(kcl_error::INTERNAL_ERROR_MSG) + .clone() + }; + if let Proxy::Schema(schema) = &frame.proxy { + results.push(schema.ctx.clone()) + } + } + } + results + } + + /// Whether the attribute is the schema context. + pub fn has_attr(s: &Evaluator, ctx: &SchemaEvalContextRef, name: &str) -> bool { + for stmt in &ctx.borrow().node.body { + if let ast::Stmt::SchemaAttr(attr) = &stmt.node { + if &attr.name.node == name { + return true; + } + } + } + if let Some(parent) = SchemaEvalContext::get_parent_schema(s, ctx) { + return SchemaEvalContext::has_attr(s, &parent, name); + } + false + } + + /// Whether the index signature is the schema context. + pub fn has_index_signature(s: &Evaluator, ctx: &SchemaEvalContextRef) -> bool { + if ctx.borrow().node.index_signature.is_some() { + return true; + } + if let Some(parent) = SchemaEvalContext::get_parent_schema(s, ctx) { + return SchemaEvalContext::has_index_signature(s, &parent); + } + false + } + + #[inline] + pub fn is_fit_config(s: &Evaluator, ctx: &SchemaEvalContextRef, value: &ValueRef) -> bool { + if value.is_config() { + let config = value.as_dict_ref(); + for (key, _) in &config.values { + let no_such_attr = + !SchemaEvalContext::has_attr(s, ctx, key) && !key.starts_with('_'); + let has_index_signature = SchemaEvalContext::has_index_signature(s, ctx); + if !has_index_signature && no_such_attr { + return false; + } + } + true + } else { + false + } + } } #[derive(Clone, Default, Debug)] @@ -102,7 +216,7 @@ pub(crate) fn schema_body( .walk_identifier_with_ctx(&parent_name.node, &ast::ExprContext::Load, None) .expect(kcl_error::RUNTIME_ERROR_MSG); // Call base schema function - call_schema_body(s, &base_constructor_func, args, kwargs, Some(ctx)); + call_schema_body(s, &base_constructor_func, args, kwargs, ctx); } // Eval schema body and record schema instances. if ctx.borrow().record_instance { @@ -126,7 +240,7 @@ pub(crate) fn schema_body( let mixin_func = s .walk_identifier_with_ctx(&mixin.node, &ast::ExprContext::Load, None) .expect(kcl_error::RUNTIME_ERROR_MSG); - schema_value = call_schema_body(s, &mixin_func, args, kwargs, Some(ctx)); + schema_value = call_schema_body(s, &mixin_func, args, kwargs, ctx); } // Schema Attribute optional check let mut optional_mapping = ctx.borrow().optional_mapping.clone(); @@ -247,8 +361,9 @@ pub(crate) fn schema_check( } else { "" }; - schema_value.schema_value_check( - &mut s.runtime_ctx.borrow_mut(), + schema_value_check( + s, + &mut schema_value, &ctx.borrow().config, schema_name, &index_sign_value, @@ -257,8 +372,9 @@ pub(crate) fn schema_check( index_signature.node.value_ty.node.to_string().as_str(), ); } else { - schema_value.schema_value_check( - &mut s.runtime_ctx.borrow_mut(), + schema_value_check( + s, + &mut schema_value, &ctx.borrow().config, schema_name, &s.undefined_value(), @@ -302,6 +418,61 @@ pub(crate) fn schema_check( schema_value } +/// Schema additional value check +fn schema_value_check( + s: &Evaluator, + schema_value: &mut ValueRef, + schema_config: &ValueRef, + schema_name: &str, + index_sign_value: &ValueRef, + index_key_name: &str, + key_type: &str, + value_type: &str, +) { + let has_index_signature = !key_type.is_empty(); + let config = schema_config.as_dict_ref(); + for (key, value) in &config.values { + let no_such_attr = schema_value.dict_get_value(key).is_none(); + if has_index_signature && no_such_attr { + // Allow index signature value has different values + // related to the index signature key name. + let should_update = + if let Some(index_key_value) = schema_value.dict_get_value(index_key_name) { + index_key_value.is_str() && key == &index_key_value.as_str() + } else { + true + }; + if should_update { + let op = config + .ops + .get(key) + .unwrap_or(&ConfigEntryOperationKind::Union); + schema_value.dict_update_entry( + key.as_str(), + &index_sign_value.deep_copy(), + &ConfigEntryOperationKind::Override, + &-1, + ); + s.dict_merge_key_value_pair( + schema_value, + key.as_str(), + value, + op.clone(), + -1, + false, + ); + let value = schema_value.dict_get_value(key).unwrap(); + schema_value.dict_update_key_value( + key.as_str(), + type_pack_and_check(s, &value, vec![value_type]), + ); + } + } else if !has_index_signature && no_such_attr { + panic!("No attribute named '{key}' in the schema '{schema_name}'"); + } + } +} + impl<'ctx> Evaluator<'ctx> { pub(crate) fn construct_schema_config_meta( &self, @@ -382,13 +553,13 @@ impl<'ctx> Evaluator<'ctx> { config_meta } - pub(crate) fn update_schema_scope_value( + pub(crate) fn update_schema_or_rule_scope_value( &self, name: &str, // Schema attribute name value: Option<&ValueRef>, // Optional right override value ) { - let (mut schema_value, config_value) = self - .get_schema_and_config() + let (mut schema_value, config_value, _) = self + .get_schema_or_rule_config_info() .expect(kcl_error::INTERNAL_ERROR_MSG); let config_value = config_value .dict_get_entry(name) diff --git a/kclvm/evaluator/src/scope.rs b/kclvm/evaluator/src/scope.rs index ffdfdcd7e..5ddc96be1 100644 --- a/kclvm/evaluator/src/scope.rs +++ b/kclvm/evaluator/src/scope.rs @@ -1,4 +1,4 @@ -use crate::{error as kcl_error, schema::SchemaEvalContextRef}; +use crate::{error as kcl_error, rule::RuleEvalContextRef, schema::SchemaEvalContextRef}; use indexmap::{IndexMap, IndexSet}; use kclvm_ast::ast; use kclvm_error::Position; @@ -27,6 +27,8 @@ pub struct Scope { pub arguments: IndexSet, /// Schema self denotes the scope that is belonged to a schema. pub schema_ctx: Option, + /// Rule self denotes the scope that is belonged to a schema. + pub rule_ctx: Option, } impl<'ctx> Evaluator<'ctx> { @@ -123,6 +125,20 @@ impl<'ctx> Evaluator<'ctx> { scopes.push(scope); } + /// Enter scope with the schema eval context. + pub(crate) fn enter_scope_with_schema_rule_context(&self, rule_ctx: &RuleEvalContextRef) { + let current_pkgpath = self.current_pkgpath(); + let mut ctx = self.ctx.borrow_mut(); + let pkg_scopes = &mut ctx.pkg_scopes; + let msg = format!("pkgpath {} is not found", current_pkgpath); + let scopes = pkg_scopes.get_mut(¤t_pkgpath).expect(&msg); + let scope = Scope { + rule_ctx: Some(rule_ctx.clone()), + ..Default::default() + }; + scopes.push(scope); + } + pub(crate) fn get_schema_eval_context(&self) -> Option { let pkgpath = self.current_pkgpath(); let ctx = self.ctx.borrow(); @@ -142,16 +158,43 @@ impl<'ctx> Evaluator<'ctx> { None } - #[inline] - pub(crate) fn get_schema_and_config(&self) -> Option<(ValueRef, ValueRef)> { - self.get_schema_eval_context() - .map(|v| (v.borrow().value.clone(), v.borrow().config.clone())) + pub(crate) fn get_rule_eval_context(&self) -> Option { + let pkgpath = self.current_pkgpath(); + let ctx = self.ctx.borrow(); + let pkg_scopes = &ctx.pkg_scopes; + // Global or local variables. + let scopes = pkg_scopes + .get(&pkgpath) + .unwrap_or_else(|| panic!("package {} is not found", pkgpath)); + // Scopes 0 is builtin scope, Scopes 1 is the global scope, Scopes 2~ are the local scopes + let scopes_len = scopes.len(); + for i in 0..scopes_len { + let index = scopes_len - i - 1; + if let Some(ctx) = &scopes[index].rule_ctx { + return Some(ctx.clone()); + } + } + None } + /// Returns (value, config, config_meta) #[inline] - pub(crate) fn get_schema_config_meta(&self) -> Option { - self.get_schema_eval_context() - .map(|v| v.borrow().config_meta.clone()) + pub(crate) fn get_schema_or_rule_config_info(&self) -> Option<(ValueRef, ValueRef, ValueRef)> { + match self.get_schema_eval_context() { + Some(v) => Some(( + v.borrow().value.clone(), + v.borrow().config.clone(), + v.borrow().config_meta.clone(), + )), + None => match self.get_rule_eval_context() { + Some(v) => Some(( + v.borrow().value.clone(), + v.borrow().config.clone(), + v.borrow().config_meta.clone(), + )), + None => None, + }, + } } /// Append a scalar value into the scope. @@ -341,14 +384,14 @@ impl<'ctx> Evaluator<'ctx> { } /// Get the variable value named `name` from the scope, return Err when not found - pub fn get_variable_in_schema(&self, name: &str) -> EvalResult { + pub fn get_variable_in_schema_or_rule(&self, name: &str) -> EvalResult { let pkgpath = self.current_pkgpath(); let ctx = self.ctx.borrow(); let scopes = ctx .pkg_scopes .get(&pkgpath) .expect(kcl_error::INTERNAL_ERROR_MSG); - // Query the schema self value in all scopes. + // Query the schema or rule self value in all scopes. for i in 0..scopes.len() { let index = scopes.len() - i - 1; if let Some(schema_ctx) = &scopes[index].schema_ctx { @@ -358,6 +401,13 @@ impl<'ctx> Evaluator<'ctx> { } else { self.get_variable_in_pkgpath(name, &pkgpath) }; + } else if let Some(rule_ctx) = &scopes[index].rule_ctx { + let rule_value: ValueRef = rule_ctx.borrow().value.clone(); + return if let Some(value) = rule_value.dict_get_value(name) { + Ok(value) + } else { + self.get_variable_in_pkgpath(name, &pkgpath) + }; } } self.get_variable_in_pkgpath(name, &pkgpath) @@ -465,7 +515,7 @@ impl<'ctx> Evaluator<'ctx> { // Get from local or global scope (false, _, _) | (_, _, true) => self.get_variable(name), // Get variable from the current schema scope. - (true, false, false) => self.get_variable_in_schema(name), + (true, false, false) => self.get_variable_in_schema_or_rule(name), // Get from local scope including lambda arguments, lambda variables, // loop variables or global variables. (true, true, _) => @@ -476,7 +526,7 @@ impl<'ctx> Evaluator<'ctx> { // Closure variable or local variables Some(level) if level > GLOBAL_LEVEL => self.get_variable(name), // Schema closure or global variables - _ => self.get_variable_in_schema(name), + _ => self.get_variable_in_schema_or_rule(name), } } } diff --git a/kclvm/evaluator/src/ty.rs b/kclvm/evaluator/src/ty.rs new file mode 100644 index 000000000..c158162e2 --- /dev/null +++ b/kclvm/evaluator/src/ty.rs @@ -0,0 +1,197 @@ +use kclvm_runtime::{ + check_type, dereference_type, is_dict_type, is_list_type, is_type_union, schema_config_meta, + schema_runtime_type, separate_kv, split_type_union, ConfigEntryOperationKind, ValueRef, + BUILTIN_TYPES, KCL_TYPE_ANY, PKG_PATH_PREFIX, +}; + +use crate::error as kcl_error; +use crate::schema::SchemaEvalContext; +use crate::{proxy::Proxy, Evaluator}; + +/// Use the schema instance to build a new schema instance using the schema construct function +pub fn resolve_schema(s: &Evaluator, schema: &ValueRef, keys: &[String]) -> ValueRef { + if !schema.is_schema() { + return schema.clone(); + } + let schema_value = schema.as_schema(); + let schema_type_name = schema_runtime_type(&schema_value.name, &schema_value.pkgpath); + if let Some(index) = s.schemas.borrow().get(&schema_type_name) { + let keys = keys.iter().map(|v| v.as_str()).collect(); + let config_value = schema.dict_get_entries(keys); + let config_meta = { + let ctx = s.runtime_ctx.borrow(); + schema_config_meta( + &ctx.panic_info.kcl_file, + ctx.panic_info.kcl_line as u64, + ctx.panic_info.kcl_col as u64, + ) + }; + + let frame = { + let frames = s.frames.borrow(); + frames + .get(*index) + .expect(kcl_error::INTERNAL_ERROR_MSG) + .clone() + }; + let schema = if let Proxy::Schema(caller) = &frame.proxy { + s.push_pkgpath(&frame.pkgpath); + // Set new schema and config + { + let mut ctx = caller.ctx.borrow_mut(); + ctx.reset_with_config(config_value, config_meta); + } + let value = (caller.body)(s, &caller.ctx, &schema_value.args, &schema_value.kwargs); + s.pop_pkgpath(); + value + } else { + schema.clone() + }; + // ctx.panic_info = now_panic_info; + return schema; + } + // ctx.panic_info = now_panic_info; + schema.clone() +} + +/// Type pack and check ValueRef with the expected type vector +pub fn type_pack_and_check(s: &Evaluator, value: &ValueRef, expected_types: Vec<&str>) -> ValueRef { + if value.is_none_or_undefined() || expected_types.is_empty() { + return value.clone(); + } + let is_schema = value.is_schema(); + let value_tpe = value.type_str(); + let mut checked = false; + let mut converted_value = value.clone(); + let expected_type = &expected_types.join(" | ").replace('@', ""); + for tpe in expected_types { + if !is_schema { + converted_value = convert_collection_value(s, value, &tpe); + } + // Runtime type check + checked = check_type(&converted_value, &tpe); + if checked { + break; + } + } + if !checked { + panic!("expect {expected_type}, got {value_tpe}"); + } + converted_value +} + +/// Convert collection value including dict/list to the potential schema +pub fn convert_collection_value(s: &Evaluator, value: &ValueRef, tpe: &str) -> ValueRef { + if tpe.is_empty() || tpe == KCL_TYPE_ANY { + return value.clone(); + } + let is_collection = value.is_list() || value.is_dict(); + let invalid_match_dict = is_dict_type(&tpe) && !value.is_dict(); + let invalid_match_list = is_list_type(&tpe) && !value.is_list(); + let invalid_match = invalid_match_dict || invalid_match_list; + if !is_collection || invalid_match { + return value.clone(); + } + // Convert a value to union types e.g., {a: 1} => A | B + if is_type_union(&tpe) { + let types = split_type_union(&tpe); + convert_collection_value_with_union_types(s, value, &types) + } else if is_dict_type(&tpe) { + //let (key_tpe, value_tpe) = separate_kv(tpe); + let (_, value_tpe) = separate_kv(&dereference_type(&tpe)); + let mut expected_dict = ValueRef::dict(None); + let dict_ref = value.as_dict_ref(); + expected_dict + .set_potential_schema_type(&dict_ref.potential_schema.clone().unwrap_or_default()); + for (k, v) in &dict_ref.values { + let expected_value = convert_collection_value(s, v, &value_tpe); + let op = dict_ref + .ops + .get(k) + .unwrap_or(&ConfigEntryOperationKind::Union); + let index = dict_ref.insert_indexs.get(k).unwrap_or(&-1); + expected_dict.dict_update_entry(k, &expected_value, op, index) + } + expected_dict + } else if is_list_type(&tpe) { + let expected_type = dereference_type(&tpe); + let mut expected_list = ValueRef::list(None); + let list_ref = value.as_list_ref(); + for v in &list_ref.values { + let expected_value = convert_collection_value(s, v, &expected_type); + expected_list.list_append(&expected_value) + } + expected_list + } else if BUILTIN_TYPES.contains(&tpe) { + value.clone() + } else { + // Get the type form @pkg.Schema + let schema_type_name = if tpe.contains('.') { + if tpe.starts_with(PKG_PATH_PREFIX) { + tpe.to_string() + } else { + format!("{PKG_PATH_PREFIX}{tpe}") + } + } else { + format!("{}.{}", s.current_pkgpath(), tpe) + }; + if let Some(index) = s.schemas.borrow().get(&schema_type_name) { + let config_meta = { + let ctx = s.runtime_ctx.borrow(); + schema_config_meta( + &ctx.panic_info.kcl_file, + ctx.panic_info.kcl_line as u64, + ctx.panic_info.kcl_col as u64, + ) + }; + let frame = { + let frames = s.frames.borrow(); + frames + .get(*index) + .expect(kcl_error::INTERNAL_ERROR_MSG) + .clone() + }; + let schema = if let Proxy::Schema(caller) = &frame.proxy { + // Try convert the config to schema, if failed, return the config + if !SchemaEvalContext::is_fit_config(s, &caller.ctx, &value) { + return value.clone(); + } + s.push_pkgpath(&frame.pkgpath); + // Set new schema and config + { + let mut ctx = caller.ctx.borrow_mut(); + ctx.reset_with_config(value.clone(), config_meta); + } + let value = (caller.body)(s, &caller.ctx, &s.list_value(), &s.dict_value()); + s.pop_pkgpath(); + value + } else { + value.clone() + }; + // ctx.panic_info = now_panic_info; + return schema.clone(); + } + // ctx.panic_info = now_meta_info; + value.clone() + } +} + +/// Convert collection value including dict/list to the potential schema and return errors. +pub fn convert_collection_value_with_union_types( + s: &Evaluator, + value: &ValueRef, + types: &[&str], +) -> ValueRef { + if value.is_schema() { + value.clone() + } else { + for tpe in types { + // Try match every type and convert the value, if matched, return the value. + let value = convert_collection_value(s, value, tpe); + if check_type(&value, tpe) { + return value; + } + } + value.clone() + } +} diff --git a/kclvm/evaluator/src/union.rs b/kclvm/evaluator/src/union.rs new file mode 100644 index 000000000..c17d90f41 --- /dev/null +++ b/kclvm/evaluator/src/union.rs @@ -0,0 +1,318 @@ +//! Copyright The KCL Authors. All rights reserved. + +use crate::*; +use kclvm_runtime::unification::value_subsume; +use kclvm_runtime::{ConfigEntryOperationKind, DictValue, UnionContext, UnionOptions, Value}; + +use self::ty::resolve_schema; + +fn do_union( + s: &Evaluator, + p: &mut ValueRef, + x: &ValueRef, + opts: &UnionOptions, + union_context: &mut UnionContext, +) -> ValueRef { + if p.is_same_ref(x) { + return p.clone(); + } + + let mut union_fn = |obj: &mut DictValue, delta: &DictValue| { + // Update potential schema type + obj.potential_schema = delta.potential_schema.clone(); + // Update attribute map + for (k, v) in &delta.ops { + obj.ops.insert(k.clone(), v.clone()); + } + // Update index map + for (k, v) in &delta.insert_indexs { + obj.insert_indexs.insert(k.clone(), *v); + } + // Update values + for (k, v) in &delta.values { + let operation = if let Some(op) = delta.ops.get(k) { + op + } else { + &ConfigEntryOperationKind::Union + }; + let index = if let Some(idx) = delta.insert_indexs.get(k) { + *idx + } else { + -1 + }; + if !obj.values.contains_key(k) { + obj.values.insert(k.clone(), v.clone()); + } else { + match operation { + ConfigEntryOperationKind::Union => { + let obj_value = obj.values.get_mut(k).unwrap(); + if opts.idempotent_check && !value_subsume(v, obj_value, false) { + union_context.conflict = true; + union_context.path_backtrace.push(k.clone()); + union_context.obj_json = if obj_value.is_config() { + "{...}".to_string() + } else if obj_value.is_list() { + "[...]".to_string() + } else { + obj_value.to_json_string() + }; + + union_context.delta_json = if v.is_config() { + "{...}".to_string() + } else if v.is_list() { + "[...]".to_string() + } else { + v.to_json_string() + }; + return; + } + union(s, obj_value, v, false, opts, union_context); + if union_context.conflict { + union_context.path_backtrace.push(k.clone()); + return; + } + } + ConfigEntryOperationKind::Override => { + if index < 0 { + obj.values.insert(k.clone(), v.clone()); + } else { + let origin_value = obj.values.get_mut(k).unwrap(); + if !origin_value.is_list() { + panic!("only list attribute can be inserted value"); + } + if v.is_none_or_undefined() { + origin_value.list_remove_at(index as usize); + } else { + origin_value.list_set(index as usize, v); + } + } + } + ConfigEntryOperationKind::Insert => { + let origin_value = obj.values.get_mut(k).unwrap(); + if origin_value.is_none_or_undefined() { + let list = ValueRef::list(None); + obj.values.insert(k.to_string(), list); + } + let origin_value = obj.values.get_mut(k).unwrap(); + if origin_value.is_same_ref(v) { + continue; + } + match (&mut *origin_value.rc.borrow_mut(), &*v.rc.borrow()) { + (Value::list_value(origin_value), Value::list_value(value)) => { + if index == -1 { + for elem in value.values.iter() { + origin_value.values.push(elem.clone()); + } + } else if index >= 0 { + let mut insert_index = index; + for v in &value.values { + origin_value + .values + .insert(insert_index as usize, v.clone()); + insert_index += 1; + } + } + } + _ => panic!("only list attribute can be inserted value"), + }; + } + } + } + } + }; + + //union schema vars + let mut union_schema = false; + let mut pkgpath: String = "".to_string(); + let mut name: String = "".to_string(); + let mut common_keys: Vec = vec![]; + let mut args = None; + let mut kwargs = None; + let mut valid = true; + match (&mut *p.rc.borrow_mut(), &*x.rc.borrow()) { + (Value::list_value(obj), Value::list_value(delta)) => { + if !opts.list_override { + let length = if obj.values.len() > delta.values.len() { + obj.values.len() + } else { + delta.values.len() + }; + let obj_len = obj.values.len(); + let delta_len = delta.values.len(); + for idx in 0..length { + if idx >= obj_len { + obj.values.push(delta.values[idx].clone()); + } else if idx < delta_len { + union( + s, + &mut obj.values[idx], + &delta.values[idx], + false, + opts, + union_context, + ); + if union_context.conflict { + union_context.path_backtrace.push(format!("list[{idx}]")); + } + } + } + } + } + (Value::dict_value(obj), Value::dict_value(delta)) => union_fn(obj, delta), + (Value::schema_value(obj), Value::dict_value(delta)) => { + name = obj.name.clone(); + pkgpath = obj.pkgpath.clone(); + let obj_value = obj.config.as_mut(); + union_fn(obj_value, delta); + common_keys = obj.config_keys.clone(); + let mut other_keys: Vec = delta.values.keys().cloned().collect(); + common_keys.append(&mut other_keys); + args = Some(obj.args.clone()); + kwargs = Some(obj.kwargs.clone()); + union_schema = true; + } + (Value::schema_value(obj), Value::schema_value(delta)) => { + name = obj.name.clone(); + pkgpath = obj.pkgpath.clone(); + let obj_value = obj.config.as_mut(); + let delta_value = delta.config.as_ref(); + union_fn(obj_value, delta_value); + common_keys = obj.config_keys.clone(); + let mut other_keys: Vec = delta.config_keys.clone(); + common_keys.append(&mut other_keys); + args = Some(delta.args.clone()); + kwargs = Some(delta.kwargs.clone()); + union_schema = true; + } + (Value::dict_value(obj), Value::schema_value(delta)) => { + name = delta.name.clone(); + pkgpath = delta.pkgpath.clone(); + let delta_value = delta.config.as_ref(); + union_fn(obj, delta_value); + common_keys = delta.config_keys.clone(); + let mut other_keys: Vec = obj.values.keys().cloned().collect(); + common_keys.append(&mut other_keys); + args = Some(delta.args.clone()); + kwargs = Some(delta.kwargs.clone()); + union_schema = true; + } + _ => valid = false, + } + if !valid { + panic!( + "union failure, expect {:?}, got {:?}", + p.type_str(), + x.type_str() + ) + } + if union_context.conflict { + return p.clone(); + } + if union_schema { + // Override schema arguments and keyword arguments. + let mut result = p.clone(); + if let (Some(args), Some(kwargs)) = (&args, &kwargs) { + result.set_schema_args(args, kwargs); + } + let optional_mapping = if p.is_schema() { + p.schema_optional_mapping() + } else { + x.schema_optional_mapping() + }; + let schema = result.dict_to_schema( + name.as_str(), + pkgpath.as_str(), + &common_keys, + &x.schema_config_meta(), + &optional_mapping, + args, + kwargs, + ); + if opts.config_resolve { + *p = resolve_schema(s, &schema, &common_keys); + } else { + *p = schema; + } + } + p.clone() +} + +fn union( + s: &Evaluator, + p: &mut ValueRef, + x: &ValueRef, + or_mode: bool, + opts: &UnionOptions, + union_context: &mut UnionContext, +) -> ValueRef { + if p.is_none_or_undefined() { + *p = x.clone(); + return p.clone(); + } + if x.is_none_or_undefined() { + return p.clone(); + } + if p.is_list_or_config() && x.is_list_or_config() { + do_union(s, p, x, opts, union_context); + } else if or_mode { + if let (Value::int_value(a), Value::int_value(b)) = + (&mut *p.rc.borrow_mut(), &*x.rc.borrow()) + { + *a |= *b; + return p.clone(); + }; + panic!( + "unsupported operand type(s) for |: '{:?}' and '{:?}'", + p.type_str(), + x.type_str() + ) + } else { + *p = x.clone(); + } + p.clone() +} + +pub fn union_entry( + s: &Evaluator, + p: &mut ValueRef, + x: &ValueRef, + or_mode: bool, + opts: &UnionOptions, +) -> ValueRef { + let mut union_context = UnionContext::default(); + let ret = union(s, p, x, or_mode, opts, &mut union_context); + if union_context.conflict { + union_context.path_backtrace.reverse(); + let conflict_key = union_context.path_backtrace.last().unwrap(); + let path_string = union_context.path_backtrace.join("."); + + // build note + // it will be like: + // {...} | { + // ... + // b = {...} + // ... + // } + + let note = format!( + " {{...}} | {{\n ...\n {} = {}\n ...\n }}", + conflict_key, union_context.delta_json + ); + if conflict_key.is_empty() { + panic!( + "conflicting values between {} and {}", + union_context.delta_json, union_context.obj_json + ); + } else { + panic!( + "conflicting values on the attribute '{}' between :\n {}\nand\n {}\nwith union path :\n {}\ntry operator '=' to override the attribute, like:\n{}", + conflict_key, + union_context.obj_json, + union_context.delta_json, + path_string, + note, + ); + } + } + ret +} diff --git a/kclvm/evaluator/src/value.rs b/kclvm/evaluator/src/value.rs index 6c4c691fe..90ca7bb0b 100644 --- a/kclvm/evaluator/src/value.rs +++ b/kclvm/evaluator/src/value.rs @@ -48,11 +48,6 @@ impl<'ctx> Evaluator<'ctx> { ValueRef::list(None) } - /// Construct a list value with `n` elements - pub(crate) fn _list_values(&self, values: &[&ValueRef]) -> ValueRef { - ValueRef::list(Some(values)) - } - /// Construct a empty kcl dict value. #[inline] pub(crate) fn dict_value(&self) -> ValueRef { diff --git a/kclvm/runtime/src/value/val_clone.rs b/kclvm/runtime/src/value/val_clone.rs index c4dd47e01..2294cde54 100644 --- a/kclvm/runtime/src/value/val_clone.rs +++ b/kclvm/runtime/src/value/val_clone.rs @@ -29,7 +29,7 @@ impl ValueRef { name: v.name.clone(), runtime_type: v.runtime_type.clone(), is_external: v.is_external, - proxy: v.proxy.clone(), + proxy: v.proxy, })))), }, Value::bool_value(ref v) => ValueRef { diff --git a/kclvm/runtime/src/value/val_func.rs b/kclvm/runtime/src/value/val_func.rs index a743a2fcc..56550bbbd 100644 --- a/kclvm/runtime/src/value/val_func.rs +++ b/kclvm/runtime/src/value/val_func.rs @@ -11,7 +11,7 @@ impl ValueRef { /// Try get the proxy function index pub fn try_get_proxy(&self) -> Option { match &*self.rc.borrow() { - crate::Value::func_value(func) => func.proxy.clone(), + crate::Value::func_value(func) => func.proxy, _ => None, } } diff --git a/kclvm/runtime/src/value/val_schema.rs b/kclvm/runtime/src/value/val_schema.rs index f4431b9a6..01bdd0f2e 100644 --- a/kclvm/runtime/src/value/val_schema.rs +++ b/kclvm/runtime/src/value/val_schema.rs @@ -302,55 +302,6 @@ impl ValueRef { } } } - - /// Schema additional value check - pub fn schema_value_check( - &mut self, - ctx: &mut Context, - schema_config: &ValueRef, - schema_name: &str, - index_sign_value: &ValueRef, - index_key_name: &str, - key_type: &str, - value_type: &str, - ) { - let schema_value = self; - let has_index_signature = !key_type.is_empty(); - let config = schema_config.as_dict_ref(); - for (key, value) in &config.values { - let no_such_attr = schema_value.dict_get_value(key).is_none(); - if has_index_signature && no_such_attr { - // Allow index signature value has different values - // related to the index signature key name. - let should_update = - if let Some(index_key_value) = schema_value.dict_get_value(index_key_name) { - index_key_value.is_str() && key == &index_key_value.as_str() - } else { - true - }; - if should_update { - let op = config - .ops - .get(key) - .unwrap_or(&ConfigEntryOperationKind::Union); - schema_value.dict_update_entry( - key.as_str(), - &index_sign_value.deep_copy(), - &ConfigEntryOperationKind::Override, - &-1, - ); - schema_value.dict_insert(ctx, key.as_str(), value, op.clone(), -1); - let value = schema_value.dict_get_value(key).unwrap(); - schema_value.dict_update_key_value( - key.as_str(), - type_pack_and_check(ctx, &value, vec![value_type]), - ); - } - } else if !has_index_signature && no_such_attr { - panic!("No attribute named '{key}' in the schema '{schema_name}'"); - } - } - } } #[cfg(test)] diff --git a/kclvm/runtime/src/value/val_type.rs b/kclvm/runtime/src/value/val_type.rs index 19f7098bd..41c0a69e8 100644 --- a/kclvm/runtime/src/value/val_type.rs +++ b/kclvm/runtime/src/value/val_type.rs @@ -211,7 +211,7 @@ pub fn convert_collection_value(ctx: &mut Context, value: &ValueRef, tpe: &str) if !is_collection || invalid_match { return value.clone(); } - // Convert a vlaue to union types e.g., {a: 1} => A | B + // Convert a value to union types e.g., {a: 1} => A | B if is_type_union(&tpe) { let types = split_type_union(&tpe); convert_collection_value_with_union_types(ctx, value, &types) diff --git a/kclvm/runtime/src/value/val_union.rs b/kclvm/runtime/src/value/val_union.rs index 804ac8db3..70a4933f8 100644 --- a/kclvm/runtime/src/value/val_union.rs +++ b/kclvm/runtime/src/value/val_union.rs @@ -6,11 +6,11 @@ use crate::*; /// UnionContext records some information during the value merging process, /// including the merging path and whether there are conflicts. #[derive(Default, Debug)] -struct UnionContext { - path_backtrace: Vec, - conflict: bool, - obj_json: String, - delta_json: String, +pub struct UnionContext { + pub path_backtrace: Vec, + pub conflict: bool, + pub obj_json: String, + pub delta_json: String, } /// UnionOptions denotes the union options between runtime values. From d69d7e8476e6eeef08ee4884c803322fbcc57587 Mon Sep 17 00:00:00 2001 From: peefy Date: Tue, 2 Apr 2024 11:30:56 +0800 Subject: [PATCH 2/3] feat: impl lazy eval scope for the evaluator Signed-off-by: peefy --- kclvm/compiler/src/codegen/llvm/node.rs | 2 +- kclvm/evaluator/src/context.rs | 134 ++---- kclvm/evaluator/src/func.rs | 1 + kclvm/evaluator/src/lazy.rs | 155 ++++++- kclvm/evaluator/src/lib.rs | 43 +- kclvm/evaluator/src/node.rs | 74 ++- kclvm/evaluator/src/rule.rs | 10 +- kclvm/evaluator/src/schema.rs | 439 +++++++++++++----- kclvm/evaluator/src/scope.rs | 150 ++---- kclvm/makefile | 4 + .../schema/irrelevant_order/simple_4/main.k | 3 +- 11 files changed, 639 insertions(+), 376 deletions(-) diff --git a/kclvm/compiler/src/codegen/llvm/node.rs b/kclvm/compiler/src/codegen/llvm/node.rs index 9ee100eab..ae8535056 100644 --- a/kclvm/compiler/src/codegen/llvm/node.rs +++ b/kclvm/compiler/src/codegen/llvm/node.rs @@ -1047,7 +1047,7 @@ impl<'ctx> TypedResultWalker<'ctx> for LLVMCodeGenContext<'ctx> { // Schema function closures let list_value = self.list_values(&[ // is_sub_schema - self.bool_value(true), + self.bool_value(false), schema_config_meta, schema_config, schema_value, diff --git a/kclvm/evaluator/src/context.rs b/kclvm/evaluator/src/context.rs index d1932bd91..5de8513e6 100644 --- a/kclvm/evaluator/src/context.rs +++ b/kclvm/evaluator/src/context.rs @@ -1,12 +1,8 @@ -use std::{ - collections::{HashMap, HashSet}, - rc::Rc, -}; +use std::rc::Rc; use generational_arena::Index; -use indexmap::IndexMap; use kclvm_error::Handler; -use kclvm_runtime::{ValueRef, MAIN_PKG_PATH}; +use kclvm_runtime::MAIN_PKG_PATH; use crate::{ error as kcl_error, @@ -14,41 +10,12 @@ use crate::{ proxy::{Frame, Proxy}, rule::RuleCaller, schema::SchemaCaller, - scope::Scope, - Evaluator, GLOBAL_LEVEL, + EvalContext, Evaluator, GLOBAL_LEVEL, }; pub struct EvaluatorContext { - pub pkgpath_stack: Vec, - pub filename_stack: Vec, - /// Imported package path set to judge is there a duplicate import. - pub imported: HashSet, - /// The lambda stack index denotes the scope level of the lambda function. - pub lambda_stack: Vec, - /// To judge is in the schema statement. - pub schema_stack: Vec<()>, - /// To judge is in the schema expression. - pub schema_expr_stack: Vec<()>, - /// Import names mapping - pub import_names: IndexMap>, - /// Package scope to store variable pointers. - pub pkg_scopes: HashMap>, - /// Local variables in the loop. - pub local_vars: HashSet, - /// The names of possible assignment objects for the current instruction. - pub target_vars: Vec, - /// Global string caches - pub global_strings: IndexMap>, - /// Global variable pointers cross different packages. - pub global_vars: IndexMap>, - /// The filename of the source file corresponding to the current instruction - pub current_filename: String, - /// The line number of the source file corresponding to the current instruction - pub current_line: u64, /// Error handler to store compile errors. pub handler: Handler, - /// Debug mode - pub debug: bool, /// Program work directory pub workdir: String, } @@ -56,22 +23,7 @@ pub struct EvaluatorContext { impl Default for EvaluatorContext { fn default() -> Self { Self { - imported: Default::default(), - lambda_stack: vec![GLOBAL_LEVEL], - schema_stack: Default::default(), - schema_expr_stack: Default::default(), - pkgpath_stack: vec![kclvm_ast::MAIN_PKG.to_string()], - filename_stack: Default::default(), - import_names: Default::default(), - pkg_scopes: Default::default(), - local_vars: Default::default(), - target_vars: Default::default(), - global_strings: Default::default(), - global_vars: Default::default(), - current_filename: Default::default(), - current_line: Default::default(), handler: Default::default(), - debug: Default::default(), workdir: Default::default(), } } @@ -81,9 +33,8 @@ impl<'ctx> Evaluator<'ctx> { /// Current package path #[inline] pub(crate) fn current_pkgpath(&self) -> String { - self.ctx + self.pkgpath_stack .borrow() - .pkgpath_stack .last() .expect(kcl_error::INTERNAL_ERROR_MSG) .to_string() @@ -92,10 +43,9 @@ impl<'ctx> Evaluator<'ctx> { /// Last package path #[inline] pub(crate) fn last_pkgpath(&self) -> String { - let len = self.ctx.borrow().pkgpath_stack.len(); - self.ctx + let len = self.pkgpath_stack.borrow().len(); + self.pkgpath_stack .borrow() - .pkgpath_stack .get(if len > 2 { len - 2 } else { 2 - len }) .unwrap_or(&MAIN_PKG_PATH.to_string()) .to_string() @@ -104,9 +54,8 @@ impl<'ctx> Evaluator<'ctx> { /// Current filename #[inline] pub(crate) fn current_filename(&self) -> String { - self.ctx + self.filename_stack .borrow() - .filename_stack .last() .expect(kcl_error::INTERNAL_ERROR_MSG) .to_string() @@ -115,131 +64,130 @@ impl<'ctx> Evaluator<'ctx> { /// Current line #[inline] pub(crate) fn current_line(&self) -> u64 { - self.ctx.borrow().current_line + *self.current_line.borrow() } #[inline] pub fn push_filename(&self, filename: &str) { - self.ctx - .borrow_mut() - .filename_stack - .push(filename.to_string()); + self.filename_stack.borrow_mut().push(filename.to_string()); } #[inline] pub fn pop_filename(&self) { - self.ctx.borrow_mut().filename_stack.pop(); + self.filename_stack.borrow_mut().pop(); } /// Push a lambda definition scope into the lambda stack #[inline] pub fn push_lambda(&self, scope: usize) { - self.ctx.borrow_mut().lambda_stack.push(scope); + self.lambda_stack.borrow_mut().push(scope); } /// Pop a lambda definition scope. #[inline] pub fn pop_lambda(&self) { - self.ctx.borrow_mut().lambda_stack.pop(); + self.lambda_stack.borrow_mut().pop(); } #[inline] pub fn is_in_lambda(&self) -> bool { *self - .ctx - .borrow() .lambda_stack + .borrow() .last() .expect(kcl_error::INTERNAL_ERROR_MSG) > GLOBAL_LEVEL } #[inline] - pub fn push_schema(&self) { - self.ctx.borrow_mut().schema_stack.push(()); + pub fn push_schema(&self, v: EvalContext) { + self.schema_stack.borrow_mut().push(v); } #[inline] pub fn pop_schema(&self) { - self.ctx.borrow_mut().schema_stack.pop(); + self.schema_stack.borrow_mut().pop(); } #[inline] pub fn is_in_schema(&self) -> bool { - !self.ctx.borrow().schema_stack.is_empty() + !self.schema_stack.borrow().is_empty() } #[inline] pub fn push_schema_expr(&self) { - self.ctx.borrow_mut().schema_expr_stack.push(()); + self.schema_expr_stack.borrow_mut().push(()); } #[inline] pub fn pop_schema_expr(&self) { - self.ctx.borrow_mut().schema_expr_stack.pop(); + self.schema_expr_stack.borrow_mut().pop(); } #[inline] pub fn is_in_schema_expr(&self) -> bool { - !self.ctx.borrow().schema_expr_stack.is_empty() + !self.schema_expr_stack.borrow().is_empty() } #[inline] pub fn add_local_var(&self, name: &str) { - self.ctx.borrow_mut().local_vars.insert(name.to_string()); + self.local_vars.borrow_mut().insert(name.to_string()); } #[inline] pub fn remove_local_var(&self, name: &str) { - self.ctx.borrow_mut().local_vars.remove(name); + self.local_vars.borrow_mut().remove(name); } #[inline] pub fn is_local_var(&self, name: &str) -> bool { - self.ctx.borrow().local_vars.contains(name) + self.local_vars.borrow().contains(name) } #[inline] pub(crate) fn clear_local_vars(&self) { - self.ctx.borrow_mut().local_vars.clear(); + self.local_vars.borrow_mut().clear(); } - /// Reset target vars. #[inline] - pub(crate) fn reset_target_vars(&self) { - let target_vars = &mut self.ctx.borrow_mut().target_vars; - target_vars.clear(); - target_vars.push("".to_string()); + pub(crate) fn add_target_var(&self, name: &str) { + self.target_vars.borrow_mut().push(name.to_string()); } #[inline] - pub(crate) fn add_target_var(&self, name: &str) { - self.ctx.borrow_mut().target_vars.push(name.to_string()); + pub(crate) fn pop_target_var(&self) { + self.target_vars.borrow_mut().pop(); + } + + #[inline] + pub(crate) fn get_target_var(&self) -> String { + self.target_vars + .borrow() + .last() + .expect(kcl_error::INTERNAL_ERROR_MSG) + .to_string() } #[inline] pub(crate) fn check_imported(&self, pkgpath: &str) -> bool { - let imported = &mut self.ctx.borrow_mut().imported; + let imported = &mut self.imported.borrow_mut(); imported.contains(pkgpath) } #[inline] pub(crate) fn mark_imported(&self, pkgpath: &str) { - let imported = &mut self.ctx.borrow_mut().imported; + let imported = &mut self.imported.borrow_mut(); (*imported).insert(pkgpath.to_string()); } #[inline] pub(crate) fn push_pkgpath(&self, pkgpath: &str) { - self.ctx - .borrow_mut() - .pkgpath_stack - .push(pkgpath.to_string()); + self.pkgpath_stack.borrow_mut().push(pkgpath.to_string()); } #[inline] pub(crate) fn pop_pkgpath(&self) { - self.ctx.borrow_mut().pkgpath_stack.pop(); + self.pkgpath_stack.borrow_mut().pop(); } /// Append a function into the scope diff --git a/kclvm/evaluator/src/func.rs b/kclvm/evaluator/src/func.rs index 51ed8b9ba..fada86ea7 100644 --- a/kclvm/evaluator/src/func.rs +++ b/kclvm/evaluator/src/func.rs @@ -62,6 +62,7 @@ impl<'ctx> Evaluator<'ctx> { Proxy::Schema(schema) => { { let ctx = &mut schema.ctx.borrow_mut(); + // Reset config and config_meta ctx.reset_with_config(self.dict_value(), self.dict_value()); } (schema.body)(self, &schema.ctx, args, kwargs) diff --git a/kclvm/evaluator/src/lazy.rs b/kclvm/evaluator/src/lazy.rs index 9b2c86d45..85a10cc92 100644 --- a/kclvm/evaluator/src/lazy.rs +++ b/kclvm/evaluator/src/lazy.rs @@ -1,27 +1,28 @@ -use std::ops::Range; +use std::cell::RefCell; +use std::rc::Rc; +use generational_arena::Index; use indexmap::IndexMap; +use kclvm_ast::ast; use kclvm_runtime::ValueRef; -pub type EvalBodyRange = Range; +use crate::error as kcl_error; +use crate::Evaluator; +pub type LazyEvalScopeRef = Rc>; /// LazyEvalScope represents a scope of sequentially independent calculations, where /// the calculation of values is lazy and only recursively performed through /// backtracking when needed. #[derive(PartialEq, Clone, Default, Debug)] pub struct LazyEvalScope { - /// Temp variable values. - pub vars: IndexMap, /// Variable value cache. pub cache: IndexMap, /// Backtrack levels. pub levels: IndexMap, /// Variable setter function pointers. - pub setters: IndexMap>, + pub setters: IndexMap>, /// Calculate times without backtracking. pub cal_times: IndexMap, - // Scope statement - // pub body: &'ctx [Box>], } impl LazyEvalScope { @@ -47,18 +48,140 @@ impl LazyEvalScope { next_cal_time >= self.setter_len(key) } } +} - #[inline] - pub fn contains_key(&self, key: &str) -> bool { - self.vars.contains_key(key) +#[derive(PartialEq, Clone, Default, Debug)] +pub struct Setter { + // Schema or body index, none denotes the current schema. + pub index: Option, + // Statement index in the schema or body in the body array. + pub stmt: usize, +} + +/// Merge setters and set the value with default undefined value. +pub(crate) fn merge_setters( + v: &mut ValueRef, + s: &mut IndexMap>, + other: &IndexMap>, +) { + for (key, setters) in other { + if let Some(values) = s.get_mut(key) { + for setter in setters { + values.push(setter.clone()); + } + } else { + s.insert(key.to_string(), setters.clone()); + } + // Store a undefined value for the setter initial value to + // prevent self referencing. + if v.get_by_key(key).is_none() { + v.dict_update_key_value(key, ValueRef::undefined()); + } + } +} + +impl<'ctx> Evaluator<'ctx> { + /// Emit setter functions for the AST body. + /// TODO: Separate if statements with the same targets, such as + /// ```no_check + /// a = 1 + /// if True: + /// a = 1 + /// a += 1 # a = 2 instead of a = 3 + /// ``` + pub(crate) fn emit_setters( + &self, + body: &'ctx [Box>], + index: Option, + ) -> IndexMap> { + let mut body_map: IndexMap> = IndexMap::new(); + self.emit_setters_with(body, &mut body_map, false, &mut vec![], index); + body_map } - /// Set value to the context. - #[inline] - pub fn set_value(&mut self, key: &str, value: &ValueRef) { - self.vars.insert(key.to_string(), value.clone()); - if self.cal_increment(key) && self.cache.get(key).is_none() { - self.cache.insert(key.to_string(), value.clone()); + /// Emit setter functions for the AST body. + pub(crate) fn emit_setters_with( + &self, + body: &'ctx [Box>], + body_map: &mut IndexMap>, + is_in_if: bool, + in_if_names: &mut Vec, + index: Option, + ) { + let add_stmt = |name: &str, i: usize, body_map: &mut IndexMap>| { + if !body_map.contains_key(name) { + body_map.insert(name.to_string(), vec![]); + } + let body_vec = body_map.get_mut(name).expect(kcl_error::INTERNAL_ERROR_MSG); + body_vec.push(Setter { + index: index.clone(), + stmt: i, + }); + }; + for (i, stmt) in body.into_iter().enumerate() { + match &stmt.node { + ast::Stmt::Unification(unification_stmt) => { + let name = &unification_stmt.target.node.names[0].node; + if is_in_if { + in_if_names.push(name.to_string()); + } else { + add_stmt(name, i, body_map); + } + } + ast::Stmt::Assign(assign_stmt) => { + for target in &assign_stmt.targets { + let name = &target.node.names[0].node; + if is_in_if { + in_if_names.push(name.to_string()); + } else { + add_stmt(name, i, body_map); + } + } + } + ast::Stmt::AugAssign(aug_assign_stmt) => { + let target = &aug_assign_stmt.target; + let name = &target.node.names[0].node; + if is_in_if { + in_if_names.push(name.to_string()); + } else { + add_stmt(name, i, body_map); + } + } + ast::Stmt::If(if_stmt) => { + let mut names: Vec = vec![]; + self.emit_setters_with(&if_stmt.body, body_map, true, &mut names, index); + if is_in_if { + for name in &names { + in_if_names.push(name.to_string()); + } + } else { + for name in &names { + add_stmt(name, i, body_map); + } + names.clear(); + } + self.emit_setters_with(&if_stmt.orelse, body_map, true, &mut names, index); + if is_in_if { + for name in &names { + in_if_names.push(name.to_string()); + } + } else { + for name in &names { + add_stmt(name, i, body_map); + } + names.clear(); + } + } + ast::Stmt::SchemaAttr(schema_attr) => { + let name = schema_attr.name.node.as_str(); + if is_in_if { + in_if_names.push(name.to_string()); + } else { + add_stmt(name, i, body_map); + } + } + _ => {} + } } } } diff --git a/kclvm/evaluator/src/lib.rs b/kclvm/evaluator/src/lib.rs index 873aed937..84a7ef1fe 100644 --- a/kclvm/evaluator/src/lib.rs +++ b/kclvm/evaluator/src/lib.rs @@ -24,6 +24,10 @@ use context::EvaluatorContext; use generational_arena::{Arena, Index}; use indexmap::IndexMap; use proxy::{Frame, Proxy}; +use rule::RuleEvalContextRef; +use schema::SchemaEvalContextRef; +use scope::Scope; +use std::collections::{HashMap, HashSet}; use std::panic::RefUnwindSafe; use std::rc::Rc; use std::str; @@ -51,6 +55,31 @@ pub struct Evaluator<'ctx> { pub frames: RefCell>>, pub schemas: RefCell>, pub runtime_ctx: Rc>, + pub pkgpath_stack: RefCell>, + pub filename_stack: RefCell>, + /// The names of possible assignment objects for the current instruction. + pub target_vars: RefCell>, + /// Imported package path set to judge is there a duplicate import. + pub imported: RefCell>, + /// The lambda stack index denotes the scope level of the lambda function. + pub lambda_stack: RefCell>, + /// To judge is in the schema statement. + pub schema_stack: RefCell>, + /// To judge is in the schema expression. + pub schema_expr_stack: RefCell>, + /// Import names mapping + pub import_names: RefCell>>, + /// Package scope to store variable pointers. + pub pkg_scopes: RefCell>>, + /// Local variables in the loop. + pub local_vars: RefCell>, + /// The line number of the source file corresponding to the current instruction + pub current_line: RefCell, +} + +pub enum EvalContext { + Schema(SchemaEvalContextRef), + Rule(RuleEvalContextRef), } impl<'ctx> Evaluator<'ctx> { @@ -72,6 +101,17 @@ impl<'ctx> Evaluator<'ctx> { program, frames: RefCell::new(Arena::new()), schemas: RefCell::new(IndexMap::new()), + target_vars: RefCell::new(vec![]), + imported: RefCell::new(Default::default()), + lambda_stack: RefCell::new(vec![GLOBAL_LEVEL]), + schema_stack: RefCell::new(Default::default()), + schema_expr_stack: RefCell::new(Default::default()), + pkgpath_stack: RefCell::new(vec![kclvm_ast::MAIN_PKG.to_string()]), + filename_stack: RefCell::new(Default::default()), + import_names: RefCell::new(Default::default()), + pkg_scopes: RefCell::new(Default::default()), + local_vars: RefCell::new(Default::default()), + current_line: RefCell::new(Default::default()), } } @@ -87,8 +127,7 @@ impl<'ctx> Evaluator<'ctx> { /// Plan globals to a planed json and yaml string. pub fn plan_globals_to_string(&self) -> (String, String) { let current_pkgpath = self.current_pkgpath(); - let ctx = self.ctx.borrow(); - let pkg_scopes = &ctx.pkg_scopes; + let pkg_scopes = &self.pkg_scopes.borrow(); let scopes = pkg_scopes .get(¤t_pkgpath) .unwrap_or_else(|| panic!("pkgpath {} is not found", current_pkgpath)); diff --git a/kclvm/evaluator/src/node.rs b/kclvm/evaluator/src/node.rs index 1b45c6f38..1f4d6752a 100644 --- a/kclvm/evaluator/src/node.rs +++ b/kclvm/evaluator/src/node.rs @@ -5,6 +5,7 @@ use std::rc::Rc; use std::sync::Arc; use anyhow::Ok; +use generational_arena::Index; use kclvm_ast::ast::{self, CallExpr, ConfigEntry, NodeRef}; use kclvm_ast::walker::TypedResultWalker; use kclvm_runtime::val_func::invoke_function; @@ -16,7 +17,7 @@ use kclvm_runtime::{ use kclvm_sema::{builtin, plugin}; use crate::func::{func_body, FunctionCaller, FunctionEvalContext}; -use crate::lazy::EvalBodyRange; +use crate::lazy::Setter; use crate::proxy::Proxy; use crate::rule::{rule_body, rule_check, RuleCaller, RuleEvalContext}; use crate::schema::{schema_body, schema_check, SchemaCaller, SchemaEvalContext}; @@ -34,7 +35,6 @@ impl<'ctx> TypedResultWalker<'ctx> for Evaluator<'ctx> { */ fn walk_stmt(&self, stmt: &'ctx ast::Node) -> Self::Result { - self.reset_target_vars(); match &stmt.node { ast::Stmt::TypeAlias(type_alias) => self.walk_type_alias_stmt(type_alias), ast::Stmt::Expr(expr_stmt) => self.walk_expr_stmt(expr_stmt), @@ -70,34 +70,28 @@ impl<'ctx> TypedResultWalker<'ctx> for Evaluator<'ctx> { let name = &unification_stmt.target.node.names[0].node; self.add_target_var(name); // The right value of the unification_stmt is a schema_expr. - let value = self - .walk_schema_expr(&unification_stmt.value.node) - .expect(kcl_error::RUNTIME_ERROR_MSG); + let value = self.walk_schema_expr(&unification_stmt.value.node)?; if self.scope_level() == GLOBAL_LEVEL || self.is_in_lambda() { if self.resolve_variable(name) { - let mut org_value = self - .walk_identifier_with_ctx( - &unification_stmt.target.node, - &ast::ExprContext::Load, - None, - ) - .expect(kcl_error::RUNTIME_ERROR_MSG); + let mut org_value = self.walk_identifier_with_ctx( + &unification_stmt.target.node, + &ast::ExprContext::Load, + None, + )?; let value = org_value.bin_aug_bit_or(&mut self.runtime_ctx.borrow_mut(), &value); // Store the identifier value self.walk_identifier_with_ctx( &unification_stmt.target.node, &ast::ExprContext::Store, Some(value.clone()), - ) - .expect(kcl_error::RUNTIME_ERROR_MSG); + )?; return Ok(value.clone()); } else { self.walk_identifier_with_ctx( &unification_stmt.target.node, &unification_stmt.target.node.ctx, Some(value.clone()), - ) - .expect(kcl_error::RUNTIME_ERROR_MSG); + )?; return Ok(value); } // Local variables including schema/rule/lambda @@ -120,6 +114,7 @@ impl<'ctx> TypedResultWalker<'ctx> for Evaluator<'ctx> { .expect(kcl_error::RUNTIME_ERROR_MSG); return Ok(value); } + self.pop_target_var(); Ok(value) } @@ -164,6 +159,10 @@ impl<'ctx> TypedResultWalker<'ctx> for Evaluator<'ctx> { .expect(kcl_error::RUNTIME_ERROR_MSG); } } + // Pop target vars. + for _ in &assign_stmt.targets { + self.pop_target_var(); + } Ok(value) } @@ -201,6 +200,7 @@ impl<'ctx> TypedResultWalker<'ctx> for Evaluator<'ctx> { Some(value.clone()), ) .expect(kcl_error::RUNTIME_ERROR_MSG); + self.pop_target_var(); Ok(value) } @@ -283,6 +283,7 @@ impl<'ctx> TypedResultWalker<'ctx> for Evaluator<'ctx> { let caller = SchemaCaller { ctx: Rc::new(RefCell::new(SchemaEvalContext::new_with_node( schema_stmt.clone(), + Index::from_raw_parts(self.frames.borrow().len(), 0), ))), body, check, @@ -567,6 +568,11 @@ impl<'ctx> TypedResultWalker<'ctx> for Evaluator<'ctx> { } } } + // Set config cache for the schema eval context. + if let Some(schema_ctx) = self.get_schema_eval_context() { + schema_ctx.borrow().set_value(self, name); + } + self.pop_target_var(); Ok(schema_value) } @@ -1168,16 +1174,40 @@ impl<'ctx> Evaluator<'ctx> { result } - pub fn walk_stmts_with_range( + pub(crate) fn walk_schema_stmt_with_setter( &self, stmts: &'ctx [Box>], - range: &EvalBodyRange, + setter: &Setter, ) -> EvalResult { - let mut result = self.ok_result(); - if let Some(stmts) = stmts.get(range.clone()) { - result = self.walk_stmts(stmts); + if let Some(index) = setter.index { + let frame = { + let frames = self.frames.borrow(); + frames + .get(index) + .expect(kcl_error::INTERNAL_ERROR_MSG) + .clone() + }; + if let Proxy::Schema(schema) = &frame.proxy { + if let Some(stmt) = schema.ctx.borrow().node.body.get(setter.stmt) { + self.push_pkgpath(&frame.pkgpath); + self.enter_scope(); + let value = self.walk_stmt(stmt); + self.leave_scope(); + self.pop_pkgpath(); + value + } else { + self.ok_result() + } + } else { + self.ok_result() + } + } else { + if let Some(stmt) = stmts.get(setter.stmt) { + self.walk_stmt(stmt) + } else { + self.ok_result() + } } - result } pub fn walk_identifier_with_ctx( diff --git a/kclvm/evaluator/src/rule.rs b/kclvm/evaluator/src/rule.rs index 7107ad4c9..eda46025c 100644 --- a/kclvm/evaluator/src/rule.rs +++ b/kclvm/evaluator/src/rule.rs @@ -81,11 +81,6 @@ pub fn rule_body( args: &ValueRef, kwargs: &ValueRef, ) -> ValueRef { - s.push_schema(); - s.enter_scope_with_schema_rule_context(ctx); - let rule_name = &ctx.borrow().node.name.node; - // Evaluate arguments and keyword arguments and store values to local variables. - s.walk_arguments(&ctx.borrow().node.args, args, kwargs); // Schema Value let rule_value = if let Some(for_host_name) = &ctx.borrow().node.for_host_name { let base_constructor_func = s @@ -96,6 +91,11 @@ pub fn rule_body( } else { ctx.borrow().value.clone() }; + let rule_name = &ctx.borrow().node.name.node; + s.push_schema(crate::EvalContext::Rule(ctx.clone())); + s.enter_scope(); + // Evaluate arguments and keyword arguments and store values to local variables. + s.walk_arguments(&ctx.borrow().node.args, args, kwargs); // Eval schema body and record schema instances. if ctx.borrow().record_instance { // Rule decorators check diff --git a/kclvm/evaluator/src/schema.rs b/kclvm/evaluator/src/schema.rs index 33a300405..49bb94dde 100644 --- a/kclvm/evaluator/src/schema.rs +++ b/kclvm/evaluator/src/schema.rs @@ -2,16 +2,17 @@ use std::cell::RefCell; use std::rc::Rc; use std::sync::Arc; +use generational_arena::Index; use indexmap::IndexMap; use kclvm_ast::ast; use kclvm_ast::walker::TypedResultWalker; use kclvm_runtime::{schema_runtime_type, ConfigEntryOperationKind, ValueRef, MAIN_PKG_PATH}; -use crate::lazy::LazyEvalScope; +use crate::lazy::{merge_setters, LazyEvalScope, LazyEvalScopeRef}; use crate::proxy::{call_schema_body, call_schema_check}; use crate::rule::RuleEvalContext; use crate::ty::type_pack_and_check; -use crate::{error as kcl_error, Proxy}; +use crate::{error as kcl_error, EvalResult, Proxy}; use crate::{Evaluator, INNER_LEVEL}; pub type SchemaBodyHandler = @@ -24,7 +25,8 @@ pub type SchemaEvalContextRef = Rc>; #[derive(Clone, Debug)] pub struct SchemaEvalContext { pub node: ast::SchemaStmt, - pub scope: LazyEvalScope, + pub scope: Option, + pub index: Index, pub value: ValueRef, pub config: ValueRef, pub config_meta: ValueRef, @@ -35,10 +37,11 @@ pub struct SchemaEvalContext { impl SchemaEvalContext { #[inline] - pub fn new_with_node(node: ast::SchemaStmt) -> Self { + pub fn new_with_node(node: ast::SchemaStmt, index: Index) -> Self { SchemaEvalContext { node, - scope: LazyEvalScope::default(), + scope: None, + index, value: ValueRef::dict(None), config: ValueRef::dict(None), config_meta: ValueRef::dict(None), @@ -56,6 +59,13 @@ impl SchemaEvalContext { self.optional_mapping = ValueRef::dict(None); self.is_sub_schema = true; self.record_instance = true; + // Clear lazy eval scope. + if let Some(scope) = &self.scope { + let mut scope = scope.borrow_mut(); + scope.cache.clear(); + scope.levels.clear(); + scope.cal_times.clear(); + } } /// Pass value references from other schema eval context. @@ -67,6 +77,17 @@ impl SchemaEvalContext { self.optional_mapping = other.optional_mapping.clone(); self.record_instance = other.record_instance; self.is_sub_schema = false; + // Set lazy eval scope. + if let Some(scope) = &self.scope { + if let Some(other) = &other.scope { + let mut scope = scope.borrow_mut(); + let other = other.borrow(); + scope.cache = other.cache.clone(); + scope.levels = other.levels.clone(); + scope.cal_times = other.cal_times.clone(); + scope.setters = other.setters.clone(); + } + } } /// Pass value references from other rule eval context. @@ -84,9 +105,9 @@ impl SchemaEvalContext { /// Update parent schema and mixin schema information pub fn get_parent_schema( s: &Evaluator, - ctx: &SchemaEvalContextRef, - ) -> Option { - if let Some(parent_name) = &ctx.borrow().node.parent_name { + ctx: &SchemaEvalContext, + ) -> Option<(Index, SchemaEvalContextRef)> { + if let Some(parent_name) = &ctx.node.parent_name { let func = s .walk_identifier_with_ctx(&parent_name.node, &ast::ExprContext::Load, None) .expect(kcl_error::RUNTIME_ERROR_MSG); @@ -99,7 +120,7 @@ impl SchemaEvalContext { .clone() }; if let Proxy::Schema(schema) = &frame.proxy { - Some(schema.ctx.clone()) + Some((index, schema.ctx.clone())) } else { None } @@ -114,10 +135,10 @@ impl SchemaEvalContext { /// Update parent schema and mixin schema information pub fn get_mixin_schemas( s: &Evaluator, - ctx: &SchemaEvalContextRef, - ) -> Vec { + ctx: &SchemaEvalContext, + ) -> Vec<(Index, SchemaEvalContextRef)> { let mut results = vec![]; - for mixin in &ctx.borrow().node.mixins { + for mixin in &ctx.node.mixins { let func = s .walk_identifier_with_ctx(&mixin.node, &ast::ExprContext::Load, None) .expect(kcl_error::RUNTIME_ERROR_MSG); @@ -130,7 +151,7 @@ impl SchemaEvalContext { .clone() }; if let Proxy::Schema(schema) = &frame.proxy { - results.push(schema.ctx.clone()) + results.push((index, schema.ctx.clone())) } } } @@ -146,7 +167,7 @@ impl SchemaEvalContext { } } } - if let Some(parent) = SchemaEvalContext::get_parent_schema(s, ctx) { + if let Some((_, parent)) = SchemaEvalContext::get_parent_schema(s, &ctx.borrow()) { return SchemaEvalContext::has_attr(s, &parent, name); } false @@ -157,7 +178,7 @@ impl SchemaEvalContext { if ctx.borrow().node.index_signature.is_some() { return true; } - if let Some(parent) = SchemaEvalContext::get_parent_schema(s, ctx) { + if let Some((_, parent)) = SchemaEvalContext::get_parent_schema(s, &ctx.borrow()) { return SchemaEvalContext::has_index_signature(s, &parent); } false @@ -180,6 +201,131 @@ impl SchemaEvalContext { false } } + + /// Init the lazy scope used to + pub fn init_lazy_scope(&mut self, s: &Evaluator, index: Option) { + // TODO: cache the lazy scope cross different schema instances. + let mut setters = IndexMap::new(); + // Parent schema setters + if let Some((idx, parent)) = SchemaEvalContext::get_parent_schema(s, self) { + { + let mut parent = parent.borrow_mut(); + parent.init_lazy_scope(s, Some(idx)); + } + if let Some(scope) = &parent.borrow().scope { + merge_setters(&mut self.value, &mut setters, &scope.borrow().setters); + } + } + // Self setters + merge_setters( + &mut self.value, + &mut setters, + &s.emit_setters(&self.node.body, index), + ); + // Mixin schema setters + for (idx, mixin) in SchemaEvalContext::get_mixin_schemas(s, self) { + { + let mut mixin = mixin.borrow_mut(); + mixin.init_lazy_scope(s, Some(idx)); + } + if let Some(scope) = &mixin.borrow().scope { + merge_setters(&mut self.value, &mut setters, &scope.borrow().setters); + } + } + self.scope = Some(Rc::new(RefCell::new(LazyEvalScope { + setters, + ..Default::default() + }))) + } + + /// Get the value from the context. + pub fn get_value(&self, s: &Evaluator, key: &str, pkgpath: &str, target: &str) -> EvalResult { + if let Some(scope) = &self.scope { + let value = { + match self.value.get_by_key(key) { + Some(value) => Ok(value.clone()), + None => s.get_variable_in_pkgpath(key, &pkgpath), + } + }; + // Deal in-place modify and return it self immediately. + if key == target && { + let scope = scope.borrow(); + !scope.is_backtracking(key) || scope.setter_len(key) <= 1 + } { + value + } else { + let cached_value = { + let scope = scope.borrow(); + scope.cache.get(key).cloned() + }; + match cached_value { + Some(value) => Ok(value.clone()), + None => { + let setters = { + let scope = scope.borrow(); + scope.setters.get(key).cloned() + }; + match &setters { + Some(setters) if !setters.is_empty() => { + // Call all setters function to calculate the value recursively. + let level = { + let scope = scope.borrow(); + *scope.levels.get(key).unwrap_or(&0) + }; + let next_level = level + 1; + { + let mut scope = scope.borrow_mut(); + scope.levels.insert(key.to_string(), next_level); + } + let n = setters.len(); + let index = n - next_level; + if index >= n { + value + } else { + // Call frame + s.walk_schema_stmt_with_setter( + &self.node.body, + &setters[index], + ) + .expect(kcl_error::INTERNAL_ERROR_MSG); + { + let mut scope = scope.borrow_mut(); + scope.levels.insert(key.to_string(), level); + let value = match self.value.get_by_key(key) { + Some(value) => value.clone(), + None => s.undefined_value(), + }; + scope.cache.insert(key.to_string(), value.clone()); + Ok(value) + } + } + } + _ => value, + } + } + } + } + } else { + return if let Some(value) = self.value.dict_get_value(key) { + Ok(value) + } else { + s.get_variable_in_pkgpath(key, &pkgpath) + }; + } + } + + /// Set value to the context. + #[inline] + pub fn set_value(&self, s: &Evaluator, key: &str) { + if let Some(scope) = &self.scope { + let mut scope = scope.borrow_mut(); + if scope.cal_increment(key) && scope.cache.get(key).is_none() { + scope + .cache + .insert(key.to_string(), s.dict_get_value(&self.value, key)); + } + } + } } #[derive(Clone, Default, Debug)] @@ -197,6 +343,15 @@ pub struct SchemaCaller { pub check: SchemaBodyHandler, } +/// Init or reset the schema lazy eval scope. +pub(crate) fn init_lazy_scope(s: &Evaluator, ctx: &mut SchemaEvalContext) { + let is_sub_schema = { ctx.is_sub_schema }; + let index = { ctx.index }; + if is_sub_schema { + ctx.init_lazy_scope(s, Some(index)); + } +} + /// Schema body function pub(crate) fn schema_body( s: &Evaluator, @@ -204,22 +359,25 @@ pub(crate) fn schema_body( args: &ValueRef, kwargs: &ValueRef, ) -> ValueRef { - s.push_schema(); - s.enter_scope_with_schema_eval_context(ctx); - let schema_name = &ctx.borrow().node.name.node; - // Evaluate arguments and keyword arguments and store values to local variables. - s.walk_arguments(&ctx.borrow().node.args, args, kwargs); - // Schema Value - let mut schema_value = ctx.borrow().value.clone(); - if let Some(parent_name) = &ctx.borrow().node.parent_name { + init_lazy_scope(s, &mut ctx.borrow_mut()); + // Schema self value or parent schema value; + let mut schema_value = if let Some(parent_name) = &ctx.borrow().node.parent_name { let base_constructor_func = s .walk_identifier_with_ctx(&parent_name.node, &ast::ExprContext::Load, None) .expect(kcl_error::RUNTIME_ERROR_MSG); // Call base schema function - call_schema_body(s, &base_constructor_func, args, kwargs, ctx); - } + call_schema_body(s, &base_constructor_func, args, kwargs, ctx) + } else { + ctx.borrow().value.clone() + }; + let schema_name = { ctx.borrow().node.name.node.to_string() }; + s.push_schema(crate::EvalContext::Schema(ctx.clone())); + s.enter_scope(); + // Evaluate arguments and keyword arguments and store values to local variables. + s.walk_arguments(&ctx.borrow().node.args, args, kwargs); // Eval schema body and record schema instances. - if ctx.borrow().record_instance { + let record_instance = { ctx.borrow().record_instance }; + if record_instance { let schema_pkgpath = &s.current_pkgpath(); // Run schema compiled function for stmt in &ctx.borrow().node.body { @@ -227,34 +385,41 @@ pub(crate) fn schema_body( } // Schema decorators check for decorator in &ctx.borrow().node.decorators { - s.walk_decorator_with_name(&decorator.node, Some(schema_name), true) + s.walk_decorator_with_name(&decorator.node, Some(&schema_name), true) .expect(kcl_error::RUNTIME_ERROR_MSG); } - let runtime_type = kclvm_runtime::schema_runtime_type(schema_name, schema_pkgpath); + let runtime_type = kclvm_runtime::schema_runtime_type(&schema_name, schema_pkgpath); schema_value.set_potential_schema_type(&runtime_type); // Set schema arguments and keyword arguments schema_value.set_schema_args(args, kwargs); } // Schema Mixins - for mixin in &ctx.borrow().node.mixins { - let mixin_func = s - .walk_identifier_with_ctx(&mixin.node, &ast::ExprContext::Load, None) - .expect(kcl_error::RUNTIME_ERROR_MSG); - schema_value = call_schema_body(s, &mixin_func, args, kwargs, ctx); + { + let ctx_ref = ctx.borrow(); + for mixin in &ctx_ref.node.mixins { + let mixin_func = s + .walk_identifier_with_ctx(&mixin.node, &ast::ExprContext::Load, None) + .expect(kcl_error::RUNTIME_ERROR_MSG); + schema_value = call_schema_body(s, &mixin_func, args, kwargs, ctx); + } } // Schema Attribute optional check - let mut optional_mapping = ctx.borrow().optional_mapping.clone(); - for stmt in &ctx.borrow().node.body { - if let ast::Stmt::SchemaAttr(schema_attr) = &stmt.node { - s.dict_insert_value( - &mut optional_mapping, - &schema_attr.name.node, - &s.bool_value(schema_attr.is_optional), - ) + let mut optional_mapping = { ctx.borrow().optional_mapping.clone() }; + { + let ctx = ctx.borrow(); + for stmt in &ctx.node.body { + if let ast::Stmt::SchemaAttr(schema_attr) = &stmt.node { + s.dict_insert_value( + &mut optional_mapping, + &schema_attr.name.node, + &s.bool_value(schema_attr.is_optional), + ) + } } } // Do schema check for the sub schema. - if ctx.borrow().is_sub_schema { + let is_sub_schema = { ctx.borrow().is_sub_schema }; + if is_sub_schema { let index_sign_key_name = if let Some(index_signature) = &ctx.borrow().node.index_signature { if let Some(key_name) = &index_signature.node.key_name { @@ -270,7 +435,10 @@ pub(crate) fn schema_body( schema_check(s, ctx, args, kwargs); } else { // Do check function for every index signature key - let config = ctx.borrow().config.clone(); + let config = { + let ctx = ctx.borrow(); + ctx.config.clone() + }; for (k, _) in &config.as_dict_ref().values { // relaxed keys if schema_value.attr_map_get(k).is_none() { @@ -296,31 +464,38 @@ pub(crate) fn schema_with_config( args: &ValueRef, kwargs: &ValueRef, ) -> ValueRef { - let name = ctx.borrow().node.name.node.to_string(); + let name = { ctx.borrow().node.name.node.to_string() }; let pkgpath = s.current_pkgpath(); - let config_keys: Vec = ctx - .borrow() - .config - .as_dict_ref() - .values - .keys() - .cloned() - .collect(); - let schema = schema_dict.dict_to_schema( - &name, - &pkgpath, - &config_keys, - &ctx.borrow().config_meta, - &ctx.borrow().optional_mapping, - Some(args.clone()), - Some(kwargs.clone()), - ); + let config_keys: Vec = { + ctx.borrow() + .config + .as_dict_ref() + .values + .keys() + .cloned() + .collect() + }; + let schema = { + let ctx = ctx.borrow(); + schema_dict.dict_to_schema( + &name, + &pkgpath, + &config_keys, + &ctx.config_meta, + &ctx.optional_mapping, + Some(args.clone()), + Some(kwargs.clone()), + ) + }; let runtime_type = schema_runtime_type(&name, &pkgpath); // Instance package path is the last frame calling package path. let instance_pkgpath = s.last_pkgpath(); - if ctx.borrow().record_instance - && (instance_pkgpath.is_empty() || instance_pkgpath == MAIN_PKG_PATH) - { + let record_instance = { ctx.borrow().record_instance }; + // Currently, `MySchema.instances()` it is only valid for files in the main package to + // avoid unexpected non idempotent calls. For example, I instantiated a MySchema in pkg1, + // but the length of the list returned by calling the instances method in other packages + // is uncertain. + if record_instance && (instance_pkgpath.is_empty() || instance_pkgpath == MAIN_PKG_PATH) { let mut ctx = s.runtime_ctx.borrow_mut(); // Record schema instance in the context if !ctx.instances.contains_key(&runtime_type) { @@ -332,7 +507,8 @@ pub(crate) fn schema_with_config( .push(schema_dict.clone()); } // Dict to schema - if ctx.borrow().is_sub_schema { + let is_sub_schema = { ctx.borrow().is_sub_schema }; + if is_sub_schema { schema } else { schema_dict.clone() @@ -346,72 +522,85 @@ pub(crate) fn schema_check( args: &ValueRef, kwargs: &ValueRef, ) -> ValueRef { - let schema_name = &ctx.borrow().node.name.node; - let mut schema_value = ctx.borrow().value.clone(); + let schema_name = { ctx.borrow().node.name.node.to_string() }; + let mut schema_value = { ctx.borrow().value.clone() }; // Do check function // Schema runtime index signature and relaxed check - if let Some(index_signature) = &ctx.borrow().node.index_signature { - let index_sign_value = if let Some(value) = &index_signature.node.value { - s.walk_expr(value).expect(kcl_error::RUNTIME_ERROR_MSG) - } else { - s.undefined_value() - }; - let key_name = if let Some(key_name) = &index_signature.node.key_name { - key_name.as_str() + { + let ctx = ctx.borrow(); + if let Some(index_signature) = &ctx.node.index_signature { + let index_sign_value = if let Some(value) = &index_signature.node.value { + s.walk_expr(value).expect(kcl_error::RUNTIME_ERROR_MSG) + } else { + s.undefined_value() + }; + let key_name = if let Some(key_name) = &index_signature.node.key_name { + key_name.as_str() + } else { + "" + }; + schema_value_check( + s, + &mut schema_value, + &ctx.config, + &schema_name, + &index_sign_value, + key_name, + index_signature.node.key_ty.node.to_string().as_str(), + index_signature.node.value_ty.node.to_string().as_str(), + ); } else { - "" - }; - schema_value_check( - s, - &mut schema_value, - &ctx.borrow().config, - schema_name, - &index_sign_value, - key_name, - index_signature.node.key_ty.node.to_string().as_str(), - index_signature.node.value_ty.node.to_string().as_str(), - ); - } else { - schema_value_check( - s, - &mut schema_value, - &ctx.borrow().config, - schema_name, - &s.undefined_value(), - "", - "", - "", - ); + schema_value_check( + s, + &mut schema_value, + &ctx.config, + &schema_name, + &s.undefined_value(), + "", + "", + "", + ); + } } // Call base check function - if let Some(parent_name) = &ctx.borrow().node.parent_name { - let base_constructor_func = s - .walk_identifier_with_ctx(&parent_name.node, &ast::ExprContext::Load, None) - .expect(kcl_error::RUNTIME_ERROR_MSG); - call_schema_check(s, &base_constructor_func, args, kwargs, Some(ctx)) + { + let ctx_ref = ctx.borrow(); + if let Some(parent_name) = &ctx_ref.node.parent_name { + let base_constructor_func = s + .walk_identifier_with_ctx(&parent_name.node, &ast::ExprContext::Load, None) + .expect(kcl_error::RUNTIME_ERROR_MSG); + call_schema_check(s, &base_constructor_func, args, kwargs, Some(ctx)) + } } // Call self check function - for check_expr in &ctx.borrow().node.checks { - s.walk_check_expr(&check_expr.node) - .expect(kcl_error::RUNTIME_ERROR_MSG); + { + let ctx = ctx.borrow(); + for check_expr in &ctx.node.checks { + s.walk_check_expr(&check_expr.node) + .expect(kcl_error::RUNTIME_ERROR_MSG); + } } + // Call mixin check functions - for mixin in &ctx.borrow().node.mixins { - let mixin_func = s - .walk_identifier_with_ctx(&mixin.node, &ast::ExprContext::Load, None) - .expect(kcl_error::RUNTIME_ERROR_MSG); - if let Some(index) = mixin_func.try_get_proxy() { - let frame = { - let frames = s.frames.borrow(); - frames - .get(index) - .expect(kcl_error::INTERNAL_ERROR_MSG) - .clone() - }; - if let Proxy::Schema(schema) = &frame.proxy { - s.push_pkgpath(&frame.pkgpath); - (schema.check)(s, &schema.ctx, args, kwargs); - s.pop_pkgpath(); + { + let ctx = ctx.borrow(); + for mixin in &ctx.node.mixins { + let mixin_func = s + .walk_identifier_with_ctx(&mixin.node, &ast::ExprContext::Load, None) + .expect(kcl_error::RUNTIME_ERROR_MSG); + if let Some(index) = mixin_func.try_get_proxy() { + let frame = { + let frames = s.frames.borrow(); + frames + .get(index) + .expect(kcl_error::INTERNAL_ERROR_MSG) + .clone() + }; + if let Proxy::Schema(schema) = &frame.proxy { + s.push_pkgpath(&frame.pkgpath); + (schema.check)(s, &schema.ctx, args, kwargs); + s.pop_pkgpath(); + } } } } @@ -563,7 +752,7 @@ impl<'ctx> Evaluator<'ctx> { .expect(kcl_error::INTERNAL_ERROR_MSG); let config_value = config_value .dict_get_entry(name) - .unwrap_or(self.none_value()); + .unwrap_or(self.undefined_value()); if self.scope_level() >= INNER_LEVEL && !self.is_local_var(name) { if let Some(value) = value { self.schema_dict_merge( @@ -575,6 +764,10 @@ impl<'ctx> Evaluator<'ctx> { ); } self.value_union(&mut schema_value, &config_value); + // Set config cache for the schema eval context. + if let Some(schema_ctx) = self.get_schema_eval_context() { + schema_ctx.borrow().set_value(self, name); + } } } } diff --git a/kclvm/evaluator/src/scope.rs b/kclvm/evaluator/src/scope.rs index 5ddc96be1..2eb57043c 100644 --- a/kclvm/evaluator/src/scope.rs +++ b/kclvm/evaluator/src/scope.rs @@ -25,18 +25,13 @@ pub struct Scope { pub variables: IndexMap, /// Potential arguments in the current scope, such as schema/lambda arguments. pub arguments: IndexSet, - /// Schema self denotes the scope that is belonged to a schema. - pub schema_ctx: Option, - /// Rule self denotes the scope that is belonged to a schema. - pub rule_ctx: Option, } impl<'ctx> Evaluator<'ctx> { /// Init a scope named `pkgpath` with all builtin functions pub(crate) fn init_scope(&self, pkgpath: &str) { { - let mut ctx = self.ctx.borrow_mut(); - let pkg_scopes = &mut ctx.pkg_scopes; + let pkg_scopes = &mut self.pkg_scopes.borrow_mut(); if pkg_scopes.contains_key(pkgpath) { return; } @@ -82,8 +77,7 @@ impl<'ctx> Evaluator<'ctx> { /// Get the scope level pub(crate) fn scope_level(&self) -> usize { let current_pkgpath = self.current_pkgpath(); - let ctx = self.ctx.borrow(); - let pkg_scopes = &ctx.pkg_scopes; + let pkg_scopes = &self.pkg_scopes.borrow(); let msg = format!("pkgpath {} is not found", current_pkgpath); let scopes = pkg_scopes.get(¤t_pkgpath).expect(&msg); // Sub the builtin global scope @@ -93,8 +87,7 @@ impl<'ctx> Evaluator<'ctx> { /// Enter scope pub(crate) fn enter_scope(&self) { let current_pkgpath = self.current_pkgpath(); - let mut ctx = self.ctx.borrow_mut(); - let pkg_scopes = &mut ctx.pkg_scopes; + let pkg_scopes = &mut self.pkg_scopes.borrow_mut(); let msg = format!("pkgpath {} is not found", current_pkgpath); let scopes = pkg_scopes.get_mut(¤t_pkgpath).expect(&msg); let scope = Scope::default(); @@ -104,77 +97,30 @@ impl<'ctx> Evaluator<'ctx> { /// Leave scope pub(crate) fn leave_scope(&self) { let current_pkgpath = self.current_pkgpath(); - let mut ctx = self.ctx.borrow_mut(); - let pkg_scopes = &mut ctx.pkg_scopes; + let pkg_scopes = &mut self.pkg_scopes.borrow_mut(); let msg = format!("pkgpath {} is not found", current_pkgpath); let scopes = pkg_scopes.get_mut(¤t_pkgpath).expect(&msg); scopes.pop(); } - /// Enter scope with the schema eval context. - pub(crate) fn enter_scope_with_schema_eval_context(&self, schema_ctx: &SchemaEvalContextRef) { - let current_pkgpath = self.current_pkgpath(); - let mut ctx = self.ctx.borrow_mut(); - let pkg_scopes = &mut ctx.pkg_scopes; - let msg = format!("pkgpath {} is not found", current_pkgpath); - let scopes = pkg_scopes.get_mut(¤t_pkgpath).expect(&msg); - let scope = Scope { - schema_ctx: Some(schema_ctx.clone()), - ..Default::default() - }; - scopes.push(scope); - } - - /// Enter scope with the schema eval context. - pub(crate) fn enter_scope_with_schema_rule_context(&self, rule_ctx: &RuleEvalContextRef) { - let current_pkgpath = self.current_pkgpath(); - let mut ctx = self.ctx.borrow_mut(); - let pkg_scopes = &mut ctx.pkg_scopes; - let msg = format!("pkgpath {} is not found", current_pkgpath); - let scopes = pkg_scopes.get_mut(¤t_pkgpath).expect(&msg); - let scope = Scope { - rule_ctx: Some(rule_ctx.clone()), - ..Default::default() - }; - scopes.push(scope); - } - pub(crate) fn get_schema_eval_context(&self) -> Option { - let pkgpath = self.current_pkgpath(); - let ctx = self.ctx.borrow(); - let pkg_scopes = &ctx.pkg_scopes; - // Global or local variables. - let scopes = pkg_scopes - .get(&pkgpath) - .unwrap_or_else(|| panic!("package {} is not found", pkgpath)); - // Scopes 0 is builtin scope, Scopes 1 is the global scope, Scopes 2~ are the local scopes - let scopes_len = scopes.len(); - for i in 0..scopes_len { - let index = scopes_len - i - 1; - if let Some(ctx) = &scopes[index].schema_ctx { - return Some(ctx.clone()); - } + match self.schema_stack.borrow().last() { + Some(ctx) => match ctx { + crate::EvalContext::Schema(schema) => Some(schema.clone()), + crate::EvalContext::Rule(_) => None, + }, + None => None, } - None } pub(crate) fn get_rule_eval_context(&self) -> Option { - let pkgpath = self.current_pkgpath(); - let ctx = self.ctx.borrow(); - let pkg_scopes = &ctx.pkg_scopes; - // Global or local variables. - let scopes = pkg_scopes - .get(&pkgpath) - .unwrap_or_else(|| panic!("package {} is not found", pkgpath)); - // Scopes 0 is builtin scope, Scopes 1 is the global scope, Scopes 2~ are the local scopes - let scopes_len = scopes.len(); - for i in 0..scopes_len { - let index = scopes_len - i - 1; - if let Some(ctx) = &scopes[index].rule_ctx { - return Some(ctx.clone()); - } + match self.schema_stack.borrow().last() { + Some(ctx) => match ctx { + crate::EvalContext::Schema(_) => None, + crate::EvalContext::Rule(rule) => Some(rule.clone()), + }, + None => None, } - None } /// Returns (value, config, config_meta) @@ -200,8 +146,7 @@ impl<'ctx> Evaluator<'ctx> { /// Append a scalar value into the scope. pub fn add_scalar(&self, scalar: ValueRef, is_schema: bool) { let current_pkgpath = self.current_pkgpath(); - let mut ctx = self.ctx.borrow_mut(); - let pkg_scopes = &mut ctx.pkg_scopes; + let pkg_scopes = &mut self.pkg_scopes.borrow_mut(); let scopes = pkg_scopes .get_mut(¤t_pkgpath) .unwrap_or_else(|| panic!("pkgpath {} is not found", current_pkgpath)); @@ -226,8 +171,7 @@ impl<'ctx> Evaluator<'ctx> { /// Append a variable into the scope pub fn add_variable(&self, name: &str, pointer: ValueRef) { let current_pkgpath = self.current_pkgpath(); - let mut ctx = self.ctx.borrow_mut(); - let pkg_scopes = &mut ctx.pkg_scopes; + let pkg_scopes = &mut self.pkg_scopes.borrow_mut(); let msg = format!("pkgpath {} is not found", current_pkgpath); let scopes = pkg_scopes.get_mut(¤t_pkgpath).expect(&msg); if let Some(last) = scopes.last_mut() { @@ -240,8 +184,7 @@ impl<'ctx> Evaluator<'ctx> { pub(crate) fn store_argument_in_current_scope(&self, name: &str) { // Find argument name in the scope let current_pkgpath = self.current_pkgpath(); - let mut ctx = self.ctx.borrow_mut(); - let pkg_scopes = &mut ctx.pkg_scopes; + let pkg_scopes = &mut self.pkg_scopes.borrow_mut(); let msg = format!("pkgpath {} is not found", current_pkgpath); let scopes = pkg_scopes.get_mut(¤t_pkgpath).expect(&msg); let index = scopes.len() - 1; @@ -253,8 +196,7 @@ impl<'ctx> Evaluator<'ctx> { pub fn store_variable_in_current_scope(&self, name: &str, value: ValueRef) -> bool { // Find argument name in the scope let current_pkgpath = self.current_pkgpath(); - let mut ctx = self.ctx.borrow_mut(); - let pkg_scopes = &mut ctx.pkg_scopes; + let pkg_scopes = &mut self.pkg_scopes.borrow_mut(); let msg = format!("pkgpath {} is not found", current_pkgpath); let scopes = pkg_scopes.get_mut(¤t_pkgpath).expect(&msg); let index = scopes.len() - 1; @@ -271,8 +213,7 @@ impl<'ctx> Evaluator<'ctx> { pub fn store_variable(&self, name: &str, value: ValueRef) -> bool { // Find argument name in the scope let current_pkgpath = self.current_pkgpath(); - let mut ctx = self.ctx.borrow_mut(); - let pkg_scopes = &mut ctx.pkg_scopes; + let pkg_scopes = &mut self.pkg_scopes.borrow_mut(); let msg = format!("pkgpath {} is not found", current_pkgpath); let scopes = pkg_scopes.get_mut(¤t_pkgpath).expect(&msg); for i in 0..scopes.len() { @@ -297,8 +238,7 @@ impl<'ctx> Evaluator<'ctx> { pub fn resolve_variable_level(&self, name: &str) -> Option { // Find argument name in the scope let current_pkgpath = self.current_pkgpath(); - let ctx = self.ctx.borrow(); - let pkg_scopes = &ctx.pkg_scopes; + let pkg_scopes = &self.pkg_scopes.borrow(); let msg = format!("pkgpath {} is not found", current_pkgpath); let scopes = pkg_scopes.get(¤t_pkgpath).expect(&msg); let mut level = None; @@ -322,8 +262,7 @@ impl<'ctx> Evaluator<'ctx> { pub fn add_or_update_local_variable(&self, name: &str, value: ValueRef) { let current_pkgpath = self.current_pkgpath(); let is_local_var = self.is_local_var(name); - let mut ctx = self.ctx.borrow_mut(); - let pkg_scopes = &mut ctx.pkg_scopes; + let pkg_scopes = &mut self.pkg_scopes.borrow_mut(); let msg = format!("pkgpath {} is not found", current_pkgpath); let scopes = pkg_scopes.get_mut(¤t_pkgpath).expect(&msg); let mut existed = false; @@ -355,8 +294,7 @@ impl<'ctx> Evaluator<'ctx> { pub fn add_or_update_global_variable(&self, name: &str, value: ValueRef) { // Find argument name in the scope let current_pkgpath = self.current_pkgpath(); - let mut ctx = self.ctx.borrow_mut(); - let pkg_scopes = &mut ctx.pkg_scopes; + let pkg_scopes = &mut self.pkg_scopes.borrow_mut(); let msg = format!("pkgpath {} is not found", current_pkgpath); let scopes = pkg_scopes.get_mut(¤t_pkgpath).expect(&msg); let mut existed = false; @@ -386,37 +324,25 @@ impl<'ctx> Evaluator<'ctx> { /// Get the variable value named `name` from the scope, return Err when not found pub fn get_variable_in_schema_or_rule(&self, name: &str) -> EvalResult { let pkgpath = self.current_pkgpath(); - let ctx = self.ctx.borrow(); - let scopes = ctx - .pkg_scopes - .get(&pkgpath) - .expect(kcl_error::INTERNAL_ERROR_MSG); - // Query the schema or rule self value in all scopes. - for i in 0..scopes.len() { - let index = scopes.len() - i - 1; - if let Some(schema_ctx) = &scopes[index].schema_ctx { - let schema_value: ValueRef = schema_ctx.borrow().value.clone(); - return if let Some(value) = schema_value.dict_get_value(name) { - Ok(value) - } else { - self.get_variable_in_pkgpath(name, &pkgpath) - }; - } else if let Some(rule_ctx) = &scopes[index].rule_ctx { - let rule_value: ValueRef = rule_ctx.borrow().value.clone(); - return if let Some(value) = rule_value.dict_get_value(name) { - Ok(value) - } else { - self.get_variable_in_pkgpath(name, &pkgpath) - }; - } + if let Some(schema_ctx) = self.get_schema_eval_context() { + return schema_ctx + .borrow() + .get_value(self, name, &pkgpath, &self.get_target_var()); + } else if let Some(rule_ctx) = self.get_rule_eval_context() { + let rule_value: ValueRef = rule_ctx.borrow().value.clone(); + return if let Some(value) = rule_value.dict_get_value(name) { + Ok(value) + } else { + self.get_variable_in_pkgpath(name, &pkgpath) + }; + } else { + self.get_variable_in_pkgpath(name, &pkgpath) } - self.get_variable_in_pkgpath(name, &pkgpath) } /// Get the variable value named `name` from the scope named `pkgpath`, return Err when not found pub fn get_variable_in_pkgpath(&self, name: &str, pkgpath: &str) -> EvalResult { - let ctx = self.ctx.borrow(); - let pkg_scopes = &ctx.pkg_scopes; + let pkg_scopes = self.pkg_scopes.borrow(); let pkgpath = if !pkgpath.starts_with(kclvm_runtime::PKG_PATH_PREFIX) && pkgpath != MAIN_PKG_PATH { format!("{}{}", kclvm_runtime::PKG_PATH_PREFIX, pkgpath) diff --git a/kclvm/makefile b/kclvm/makefile index aa7e74a01..7e7af13fb 100644 --- a/kclvm/makefile +++ b/kclvm/makefile @@ -79,6 +79,10 @@ test-runtime: install-kclvm-py install-pytest test-grammar: install-kclvm-py install-pytest cd tests/integration/grammar && python3 -m pytest -v -n 5 +# E2E grammar tests with the fast evaluator +test-grammar-evaluator: install-kclvm-py install-pytest + cd tests/integration/grammar && KCL_FAST_EVAL=1 python3 -m pytest -v -n 5 + # E2E konfig tests. test-konfig: install-kclvm-py install-pytest cd tests/integration/konfig && python3 -m pytest -v -n 5 diff --git a/test/grammar/schema/irrelevant_order/simple_4/main.k b/test/grammar/schema/irrelevant_order/simple_4/main.k index c3f76c4b2..fec74eae6 100644 --- a/test/grammar/schema/irrelevant_order/simple_4/main.k +++ b/test/grammar/schema/irrelevant_order/simple_4/main.k @@ -1,8 +1,7 @@ schema Config: a: int = b - b: int + b: int = 1 if True: - b = 1 b += 1 x0 = Config {} From ac0fee8d5dc4dad525757f622048c9f7b057e319 Mon Sep 17 00:00:00 2001 From: peefy Date: Tue, 2 Apr 2024 12:07:55 +0800 Subject: [PATCH 3/3] chore: change test cases with the backtrack meta Signed-off-by: peefy --- kclvm/evaluator/src/context.rs | 3 ++- kclvm/evaluator/src/lazy.rs | 11 ++++++++- kclvm/evaluator/src/lib.rs | 4 ++++ kclvm/evaluator/src/schema.rs | 24 +++++++++---------- kclvm/evaluator/src/scope.rs | 1 - .../relaxed_1/{main.k => _main.k} | 0 .../simple_1/{main.k => _main.k} | 0 7 files changed, 27 insertions(+), 16 deletions(-) rename test/grammar/schema/irrelevant_order/relaxed_1/{main.k => _main.k} (100%) rename test/grammar/schema/irrelevant_order/simple_1/{main.k => _main.k} (100%) diff --git a/kclvm/evaluator/src/context.rs b/kclvm/evaluator/src/context.rs index 5de8513e6..a246d0289 100644 --- a/kclvm/evaluator/src/context.rs +++ b/kclvm/evaluator/src/context.rs @@ -14,7 +14,8 @@ use crate::{ }; pub struct EvaluatorContext { - /// Error handler to store compile errors. + /// TODO: Error handler to store runtime errors with filename + /// and line information. pub handler: Handler, /// Program work directory pub workdir: String, diff --git a/kclvm/evaluator/src/lazy.rs b/kclvm/evaluator/src/lazy.rs index 85a10cc92..62998df77 100644 --- a/kclvm/evaluator/src/lazy.rs +++ b/kclvm/evaluator/src/lazy.rs @@ -80,9 +80,18 @@ pub(crate) fn merge_setters( } } +/// TODO: Schema or Global internal order independent computation +/// backtracking meta information. +pub struct BacktrackMeta { + pub target: String, + pub level: usize, + pub count: usize, + pub stop: bool, +} + impl<'ctx> Evaluator<'ctx> { /// Emit setter functions for the AST body. - /// TODO: Separate if statements with the same targets, such as + /// TODO: Separate if statements with the same targets using the backtrack meta, such as /// ```no_check /// a = 1 /// if True: diff --git a/kclvm/evaluator/src/lib.rs b/kclvm/evaluator/src/lib.rs index 84a7ef1fe..f2e785c65 100644 --- a/kclvm/evaluator/src/lib.rs +++ b/kclvm/evaluator/src/lib.rs @@ -23,6 +23,7 @@ extern crate kclvm_error; use context::EvaluatorContext; use generational_arena::{Arena, Index}; use indexmap::IndexMap; +use lazy::BacktrackMeta; use proxy::{Frame, Proxy}; use rule::RuleEvalContextRef; use schema::SchemaEvalContextRef; @@ -75,6 +76,8 @@ pub struct Evaluator<'ctx> { pub local_vars: RefCell>, /// The line number of the source file corresponding to the current instruction pub current_line: RefCell, + /// Schema attr backtrack meta + pub backtrack_meta: RefCell>, } pub enum EvalContext { @@ -112,6 +115,7 @@ impl<'ctx> Evaluator<'ctx> { pkg_scopes: RefCell::new(Default::default()), local_vars: RefCell::new(Default::default()), current_line: RefCell::new(Default::default()), + backtrack_meta: RefCell::new(None), } } diff --git a/kclvm/evaluator/src/schema.rs b/kclvm/evaluator/src/schema.rs index 49bb94dde..3af068173 100644 --- a/kclvm/evaluator/src/schema.rs +++ b/kclvm/evaluator/src/schema.rs @@ -24,7 +24,7 @@ pub type SchemaEvalContextRef = Rc>; /// rather than executing KCL defined functions or plugin functions. #[derive(Clone, Debug)] pub struct SchemaEvalContext { - pub node: ast::SchemaStmt, + pub node: Rc, pub scope: Option, pub index: Index, pub value: ValueRef, @@ -32,14 +32,13 @@ pub struct SchemaEvalContext { pub config_meta: ValueRef, pub optional_mapping: ValueRef, pub is_sub_schema: bool, - pub record_instance: bool, } impl SchemaEvalContext { #[inline] pub fn new_with_node(node: ast::SchemaStmt, index: Index) -> Self { SchemaEvalContext { - node, + node: Rc::new(node), scope: None, index, value: ValueRef::dict(None), @@ -47,7 +46,6 @@ impl SchemaEvalContext { config_meta: ValueRef::dict(None), optional_mapping: ValueRef::dict(None), is_sub_schema: true, - record_instance: true, } } @@ -58,7 +56,6 @@ impl SchemaEvalContext { self.value = ValueRef::dict(None); self.optional_mapping = ValueRef::dict(None); self.is_sub_schema = true; - self.record_instance = true; // Clear lazy eval scope. if let Some(scope) = &self.scope { let mut scope = scope.borrow_mut(); @@ -75,7 +72,6 @@ impl SchemaEvalContext { self.config_meta = other.config_meta.clone(); self.value = other.value.clone(); self.optional_mapping = other.optional_mapping.clone(); - self.record_instance = other.record_instance; self.is_sub_schema = false; // Set lazy eval scope. if let Some(scope) = &self.scope { @@ -97,7 +93,6 @@ impl SchemaEvalContext { self.config_meta = other.config_meta.clone(); self.value = other.value.clone(); self.optional_mapping = other.optional_mapping.clone(); - self.record_instance = other.record_instance; // Note that for the host schema, it will evaluate the final value. self.is_sub_schema = true; } @@ -376,15 +371,19 @@ pub(crate) fn schema_body( // Evaluate arguments and keyword arguments and store values to local variables. s.walk_arguments(&ctx.borrow().node.args, args, kwargs); // Eval schema body and record schema instances. - let record_instance = { ctx.borrow().record_instance }; - if record_instance { + { let schema_pkgpath = &s.current_pkgpath(); + // To prevent schema recursive calling, thus clone the AST here. + let node = { + let ctx = ctx.borrow(); + ctx.node.clone() + }; // Run schema compiled function - for stmt in &ctx.borrow().node.body { + for stmt in &node.body { s.walk_stmt(stmt).expect(kcl_error::RUNTIME_ERROR_MSG); } // Schema decorators check - for decorator in &ctx.borrow().node.decorators { + for decorator in &node.decorators { s.walk_decorator_with_name(&decorator.node, Some(&schema_name), true) .expect(kcl_error::RUNTIME_ERROR_MSG); } @@ -490,12 +489,11 @@ pub(crate) fn schema_with_config( let runtime_type = schema_runtime_type(&name, &pkgpath); // Instance package path is the last frame calling package path. let instance_pkgpath = s.last_pkgpath(); - let record_instance = { ctx.borrow().record_instance }; // Currently, `MySchema.instances()` it is only valid for files in the main package to // avoid unexpected non idempotent calls. For example, I instantiated a MySchema in pkg1, // but the length of the list returned by calling the instances method in other packages // is uncertain. - if record_instance && (instance_pkgpath.is_empty() || instance_pkgpath == MAIN_PKG_PATH) { + if instance_pkgpath.is_empty() || instance_pkgpath == MAIN_PKG_PATH { let mut ctx = s.runtime_ctx.borrow_mut(); // Record schema instance in the context if !ctx.instances.contains_key(&runtime_type) { diff --git a/kclvm/evaluator/src/scope.rs b/kclvm/evaluator/src/scope.rs index 2eb57043c..1e37de2b3 100644 --- a/kclvm/evaluator/src/scope.rs +++ b/kclvm/evaluator/src/scope.rs @@ -152,7 +152,6 @@ impl<'ctx> Evaluator<'ctx> { .unwrap_or_else(|| panic!("pkgpath {} is not found", current_pkgpath)); if let Some(last) = scopes.last_mut() { let scalars = &mut last.scalars; - // TODO: To avoid conflicts, only the last schema scalar expressions are allowed. let schema_scalar_idx = &mut last.schema_scalar_idx; if is_schema { // Remove the last schema scalar. diff --git a/test/grammar/schema/irrelevant_order/relaxed_1/main.k b/test/grammar/schema/irrelevant_order/relaxed_1/_main.k similarity index 100% rename from test/grammar/schema/irrelevant_order/relaxed_1/main.k rename to test/grammar/schema/irrelevant_order/relaxed_1/_main.k diff --git a/test/grammar/schema/irrelevant_order/simple_1/main.k b/test/grammar/schema/irrelevant_order/simple_1/_main.k similarity index 100% rename from test/grammar/schema/irrelevant_order/simple_1/main.k rename to test/grammar/schema/irrelevant_order/simple_1/_main.k