From f6ac1a63d5cec5dc7740a1f706e25b4241e8e3c1 Mon Sep 17 00:00:00 2001 From: Ziyang Hu Date: Mon, 9 Jan 2023 20:59:08 +0800 Subject: [PATCH] imperative scripts --- cozo-core/src/cozoscript.pest | 19 +++++++----- cozo-core/src/parse/imperative.rs | 50 ++++++++++++++++++------------- cozo-core/src/parse/mod.rs | 17 +++++++---- cozo-core/src/runtime/db.rs | 7 ++++- cozo-core/src/runtime/relation.rs | 14 ++++++--- cozo-core/src/runtime/tests.rs | 17 +++++++++++ 6 files changed, 85 insertions(+), 39 deletions(-) diff --git a/cozo-core/src/cozoscript.pest b/cozo-core/src/cozoscript.pest index de0b954f..8af5ff31 100644 --- a/cozo-core/src/cozoscript.pest +++ b/cozo-core/src/cozoscript.pest @@ -9,6 +9,7 @@ script = _{sys_script | imperative_script | query_script} query_script = {SOI ~ (option | rule | const_rule | fixed_rule)+ ~ EOI} query_script_inner = {"{" ~ (option | rule | const_rule | fixed_rule)+ ~ "}"} +query_script_inner_no_bracket = { (option | rule | const_rule | fixed_rule)+ } imperative_script = {SOI ~ imperative_stmt+ ~ EOI} sys_script = {SOI ~ "::" ~ (compact_op | list_relations_op | list_relation_op | remove_relations_op | trigger_relation_op | trigger_relation_show_op | rename_relations_op | running_op | kill_op | explain_op | access_level_op) ~ EOI} @@ -16,7 +17,7 @@ sys_script = {SOI ~ "::" ~ (compact_op | list_relations_op | list_relation_op | compact_op = {"compact"} running_op = {"running"} kill_op = {"kill" ~ expr} -explain_op = {"explain" ~ query_script_inner} +explain_op = {"explain" ~ "{" ~ query_script_inner_no_bracket ~ "}"} list_relations_op = {"relations"} list_relation_op = {"columns" ~ compound_ident} remove_relations_op = {"remove" ~ (compound_ident ~ ",")* ~ compound_ident } @@ -25,7 +26,7 @@ access_level_op = {"access_level" ~ access_level ~ (compound_ident ~ ",")* ~ com access_level = {("normal" | "protected" | "read_only" | "hidden")} trigger_relation_show_op = {"show_triggers" ~ compound_ident } trigger_relation_op = {"set_triggers" ~ compound_ident ~ trigger_clause* } -trigger_clause = { "on" ~ (trigger_put | trigger_rm | trigger_replace) ~ query_script_inner } +trigger_clause = { "on" ~ (trigger_put | trigger_rm | trigger_replace) ~ "{" ~ query_script_inner_no_bracket ~ "}" } trigger_put = {"put"} trigger_rm = {"rm"} trigger_replace = {"replace"} @@ -199,22 +200,24 @@ validity_type = {"Validity"} list_type = {"[" ~ col_type ~ (";" ~ expr)? ~ "]"} tuple_type = {"(" ~ (col_type ~ ",")* ~ col_type? ~ ")"} -imperative_stmt = { - break_stmt | continue_stmt | return_stmt | +imperative_stmt = _{ + break_stmt | continue_stmt | return_stmt | debug_stmt | query_script_inner | ignore_error_script | if_chain | while_block | do_while_block | temp_swap | remove_stmt } -if_chain = {"%if" ~ (ident | query_script_inner) +imperative_condition = _{underscore_ident | query_script_inner} +if_chain = {"%if" ~ imperative_condition ~ "%then" ~ imperative_block ~ ("%else" ~ imperative_block)? ~ "%end" } imperative_block = {imperative_stmt+} break_stmt = {"%break" ~ ident?} ignore_error_script = {"%ignore_error" ~ query_script_inner} continue_stmt = {"%continue" ~ ident?} -return_stmt = {"%return" ~ (ident | query_script_inner)?} -while_block = {("%mark" ~ ident)? ~ "%while" ~ (ident | query_script_inner) ~ "%loop" ~ imperative_block ~ "%end"} -do_while_block = {("%mark" ~ ident)? ~ "%loop" ~ imperative_block ~ "%while" ~ (query_script_inner | ident) ~ "%end"} +return_stmt = {"%return" ~ (ident | underscore_ident | query_script_inner)?} +while_block = {("%mark" ~ ident)? ~ "%while" ~ imperative_condition ~ "%loop" ~ imperative_block ~ "%end"} +do_while_block = {("%mark" ~ ident)? ~ "%loop" ~ imperative_block ~ "%while" ~ imperative_condition ~ "%end"} temp_swap = {"%swap" ~ underscore_ident ~ underscore_ident} remove_stmt = {"%remove" ~ underscore_ident} +debug_stmt = {"%debug" ~ (ident | underscore_ident)} /* diff --git a/cozo-core/src/parse/imperative.rs b/cozo-core/src/parse/imperative.rs index 6b3cf2ae..76de26eb 100644 --- a/cozo-core/src/parse/imperative.rs +++ b/cozo-core/src/parse/imperative.rs @@ -8,7 +8,7 @@ */ use crate::parse::query::parse_query; -use crate::parse::{ExtractSpan, ImperativeProgram, ImperativeStmt, Pair, Pairs, Rule, SourceSpan}; +use crate::parse::{ExtractSpan, ImperativeProgram, ImperativeStmt, Pair, Rule, SourceSpan}; use crate::{DataValue, FixedRule, ValidityTs}; use either::{Left, Right}; use itertools::Itertools; @@ -18,23 +18,24 @@ use std::collections::BTreeMap; use std::sync::Arc; use thiserror::Error; -pub(crate) fn parse_imperative( - src: Pairs<'_>, +pub(crate) fn parse_imperative_block( + src: Pair<'_>, param_pool: &BTreeMap, fixed_rules: &BTreeMap>>, cur_vld: ValidityTs, ) -> Result { let mut collected = vec![]; - for pair in src { - if pair.as_rule() != Rule::EOI { - collected.push(parse_imperative_stmt( - pair, - param_pool, - fixed_rules, - cur_vld, - )?); + for pair in src.into_inner() { + if pair.as_rule() == Rule::EOI { + break; } + collected.push(parse_imperative_stmt( + pair, + param_pool, + fixed_rules, + cur_vld, + )?); } Ok(collected) @@ -78,7 +79,7 @@ fn parse_imperative_stmt( match pair.into_inner().next() { None => ImperativeStmt::ReturnNil, Some(p) => match p.as_rule() { - Rule::ident => { + Rule::ident | Rule::underscore_ident => { let rel = SmartString::from(p.as_str()); ImperativeStmt::ReturnTemp { rel } } @@ -95,7 +96,7 @@ fn parse_imperative_stmt( let mut inner = pair.into_inner(); let condition = inner.next().unwrap(); let cond = match condition.as_rule() { - Rule::ident => Left(SmartString::from(condition.as_str())), + Rule::underscore_ident => Left(SmartString::from(condition.as_str())), Rule::query_script_inner => Right(parse_query( condition.into_inner(), param_pool, @@ -134,7 +135,7 @@ fn parse_imperative_stmt( nxt = inner.next().unwrap(); } let cond = match nxt.as_rule() { - Rule::ident => Left(SmartString::from(nxt.as_str())), + Rule::underscore_ident => Left(SmartString::from(nxt.as_str())), Rule::query_script_inner => Right(parse_query( nxt.into_inner(), param_pool, @@ -143,8 +144,8 @@ fn parse_imperative_stmt( )?), _ => unreachable!(), }; - let body = parse_imperative( - inner.next().unwrap().into_inner(), + let body = parse_imperative_block( + inner.next().unwrap(), param_pool, fixed_rules, cur_vld, @@ -165,14 +166,14 @@ fn parse_imperative_stmt( mark = Some(SmartString::from(nxt.as_str())); nxt = inner.next().unwrap(); } - let body = parse_imperative( - inner.next().unwrap().into_inner(), + let body = parse_imperative_block( + inner.next().unwrap(), param_pool, fixed_rules, cur_vld, )?; let cond = match nxt.as_rule() { - Rule::ident => Left(SmartString::from(nxt.as_str())), + Rule::underscore_ident => Left(SmartString::from(nxt.as_str())), Rule::query_script_inner => Right(parse_query( nxt.into_inner(), param_pool, @@ -210,6 +211,15 @@ fn parse_imperative_stmt( temp: SmartString::from(name), } } + Rule::debug_stmt => { + // let span = pair.extract_span(); + let name_p = pair.into_inner().next().unwrap(); + let name = name_p.as_str(); + + ImperativeStmt::TempDebug { + temp: SmartString::from(name), + } + } Rule::query_script_inner => { let prog = parse_query(pair.into_inner(), param_pool, fixed_rules, cur_vld)?; ImperativeStmt::Program { prog } @@ -219,6 +229,6 @@ fn parse_imperative_stmt( let prog = parse_query(pair.into_inner(), param_pool, fixed_rules, cur_vld)?; ImperativeStmt::IgnoreErrorProgram { prog } } - _ => unreachable!(), + r => unreachable!("{r:?}"), }) } diff --git a/cozo-core/src/parse/mod.rs b/cozo-core/src/parse/mod.rs index f14c5194..292917bf 100644 --- a/cozo-core/src/parse/mod.rs +++ b/cozo-core/src/parse/mod.rs @@ -20,7 +20,7 @@ use thiserror::Error; use crate::data::program::InputProgram; use crate::data::relation::NullableColType; use crate::data::value::{DataValue, ValidityTs}; -use crate::parse::imperative::parse_imperative; +use crate::parse::imperative::parse_imperative_block; use crate::parse::query::parse_query; use crate::parse::schema::parse_nullable_type; use crate::parse::sys::{parse_sys, SysOp}; @@ -45,6 +45,7 @@ pub(crate) enum CozoScript { Sys(SysOp), } +#[derive(Debug)] pub(crate) enum ImperativeStmt { Break { target: Option>, @@ -60,13 +61,13 @@ pub(crate) enum ImperativeStmt { // span: SourceSpan, }, ReturnTemp { - rel: SmartString + rel: SmartString, }, Program { - prog: InputProgram + prog: InputProgram, }, IgnoreErrorProgram { - prog: InputProgram + prog: InputProgram, }, If { condition: ImperativeCondition, @@ -95,6 +96,9 @@ pub(crate) enum ImperativeStmt { temp: SmartString, // span: SourceSpan, }, + TempDebug { + temp: SmartString, + }, } pub(crate) type ImperativeCondition = Either, InputProgram>; @@ -130,7 +134,8 @@ impl ImperativeStmt { ImperativeCondition::Right(prog) => prog.needs_write_tx(), }) || body.iter().any(|p| p.needs_write_tx()) } - ImperativeStmt::ReturnTemp { .. } + ImperativeStmt::TempDebug { .. } + | ImperativeStmt::ReturnTemp { .. } | ImperativeStmt::Break { .. } | ImperativeStmt::Continue { .. } | ImperativeStmt::ReturnNil { .. } @@ -222,7 +227,7 @@ pub(crate) fn parse_script( CozoScript::Single(q) } Rule::imperative_script => { - let p = parse_imperative(parsed.into_inner(), param_pool, fixed_rules, cur_vld)?; + let p = parse_imperative_block(parsed, param_pool, fixed_rules, cur_vld)?; CozoScript::Imperative(p) } diff --git a/cozo-core/src/runtime/db.rs b/cozo-core/src/runtime/db.rs index 5150a2ba..6777e696 100644 --- a/cozo-core/src/runtime/db.rs +++ b/cozo-core/src/runtime/db.rs @@ -531,7 +531,7 @@ impl<'s, S: Storage<'s>> Db { if row.is_empty() { false } else { - op_to_bool(&row[row.len() - 2..])? + op_to_bool(&row[row.len() - 1..])? .get_bool() .ok_or_else(|| PredicateTypeError(span, row.last().cloned().unwrap()))? } @@ -657,6 +657,11 @@ impl<'s, S: Storage<'s>> Db { let relation = tx.get_relation(rel, false)?; return Ok(Right(ControlCode::Termination(relation.as_named_rows(tx)?))); } + ImperativeStmt::TempDebug { temp, .. } => { + let relation = tx.get_relation(temp, false)?; + println!("{}: {:?}", temp, relation.as_named_rows(tx)?); + ret = NamedRows::default(); + } ImperativeStmt::Program { prog, .. } => { ret = self.execute_single_program(prog.clone(), tx, cleanups, cur_vld)?; } diff --git a/cozo-core/src/runtime/relation.rs b/cozo-core/src/runtime/relation.rs index db10e785..2058c08a 100644 --- a/cozo-core/src/runtime/relation.rs +++ b/cozo-core/src/runtime/relation.rs @@ -456,12 +456,18 @@ impl<'a> SessionTx<'a> { let key = DataValue::Str(input_meta.name.name.clone()); let encoded = vec![key].encode_as_key(RelationId::SYSTEM); - if self.store_tx.exists(&encoded, true)? { - bail!(RelNameConflictError(input_meta.name.to_string())) - }; - let is_temp = input_meta.name.is_temp_store_name(); + if is_temp { + if self.store_tx.exists(&encoded, true)? { + bail!(RelNameConflictError(input_meta.name.to_string())) + }; + } else { + if self.temp_store_tx.exists(&encoded, true)? { + bail!(RelNameConflictError(input_meta.name.to_string())) + }; + } + let metadata = input_meta.metadata.clone(); let last_id = if is_temp { self.temp_store_id.fetch_add(1, Ordering::Relaxed) as u64 diff --git a/cozo-core/src/runtime/tests.rs b/cozo-core/src/runtime/tests.rs index 8ac4f060..943d9bd8 100644 --- a/cozo-core/src/runtime/tests.rs +++ b/cozo-core/src/runtime/tests.rs @@ -262,6 +262,23 @@ fn do_not_unify_underscore() { assert_eq!(res.len(), 1); } +#[test] +fn imperative_script() { + let db = new_cozo_mem().unwrap(); + let res = db.run_script(r#" + {:create _test {a}} + + %while { len[count(x)] := *_test[x]; ?[x] := len[z], x = z < 10 } + %loop + { ?[a] := a = rand_uuid_v1(); :put _test {a} } + %debug _test + %end + + %return _test + "#, Default::default()).unwrap(); + assert_eq!(res.rows.len(), 10); +} + #[test] fn returning_relations() { let db = new_cozo_mem().unwrap();