From c4528c82664ed759e122e5f5cc4e1dc5c01deffc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=CC=88zgu=CC=88r=20Akgu=CC=88n?= Date: Tue, 21 Nov 2023 20:40:46 +0000 Subject: [PATCH 01/20] rename to statement --- conjure_oxide/src/parse.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/conjure_oxide/src/parse.rs b/conjure_oxide/src/parse.rs index d7095f404..d8c91303f 100644 --- a/conjure_oxide/src/parse.rs +++ b/conjure_oxide/src/parse.rs @@ -7,12 +7,12 @@ use Error::ModelConstructError as CError; pub fn parse_json(str: &String) -> Result { let mut m = Model::new(); let v: JsonValue = serde_json::from_str(str)?; - let constraints = v["mStatements"] + let statements = v["mStatements"] .as_array() .ok_or(CError("mStatements is not an array".to_owned()))?; - for con in constraints { - let entry = con + for statement in statements { + let entry = statement .as_object() .ok_or(CError("mStatements contains a non-object".to_owned()))? .iter() From e0df474ba91ab8aab321a08d16d43d2653e3c0b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=CC=88zgu=CC=88r=20Akgu=CC=88n?= Date: Tue, 21 Nov 2023 21:00:27 +0000 Subject: [PATCH 02/20] consistently sorted json output --- conjure_oxide/src/ast.rs | 4 +- conjure_oxide/src/parse.rs | 9 ++++- conjure_oxide/tests/generated_tests.rs | 39 ++++++++++++++++--- .../bool-01/bool-01.expected.serialised.json | 4 +- .../bool-02/bool-02.expected.serialised.json | 8 ++-- .../bool-03/bool-03.expected.serialised.json | 4 +- .../xyz/input.expected.serialised.json | 8 ++-- 7 files changed, 55 insertions(+), 21 deletions(-) diff --git a/conjure_oxide/src/ast.rs b/conjure_oxide/src/ast.rs index cbe95cbf4..4a03b2d97 100644 --- a/conjure_oxide/src/ast.rs +++ b/conjure_oxide/src/ast.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use std::fmt::Display; #[serde_as] -#[derive(Debug, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Model { #[serde_as(as = "Vec<(_, _)>")] pub variables: HashMap, @@ -42,7 +42,7 @@ pub enum Name { MachineName(i32), } -#[derive(Debug, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct DecisionVariable { pub domain: Domain, } diff --git a/conjure_oxide/src/parse.rs b/conjure_oxide/src/parse.rs index d8c91303f..4daece974 100644 --- a/conjure_oxide/src/parse.rs +++ b/conjure_oxide/src/parse.rs @@ -1,4 +1,11 @@ -use crate::ast::{DecisionVariable, Domain, Model, Name, Range}; +#![allow(non_snake_case)] + +// we disable non_snake_case in this file becasue we want to use the constructor names of Conjure as variables. +// just in this file, don't get wrong ideas! + +use serde_json::Value; + +use crate::ast::{DecisionVariable, Domain, Expression, Model, Name, Range}; use crate::error::{Error, Result}; use serde_json::Value as JsonValue; diff --git a/conjure_oxide/tests/generated_tests.rs b/conjure_oxide/tests/generated_tests.rs index 03d86a8c8..4e1a15fc0 100644 --- a/conjure_oxide/tests/generated_tests.rs +++ b/conjure_oxide/tests/generated_tests.rs @@ -1,10 +1,10 @@ +use conjure_oxide::ast::Model; +use serde_json::{Map, Value}; use std::env; use std::error::Error; use std::fs::File; use std::io::prelude::*; -use conjure_oxide::ast::Model; - use std::path::Path; fn main() { @@ -37,10 +37,15 @@ fn integration_test(path: &str, essence_base: &str) -> Result<(), Box // "parsing" astjson as Model let generated_mdl = Model::from_json(&astjson)?; + // a consistent sorting of the keys of json objects + // only required for the generated version + // since the expected version will already be sorted + let generated_json = sort_json_object(&serde_json::to_value(generated_mdl.clone())?); + // serialise to file - let generated_json = serde_json::to_string_pretty(&generated_mdl)?; + let generated_json_str = serde_json::to_string_pretty(&generated_json)?; File::create(format!("{path}/{essence_base}.generated.serialised.json"))? - .write_all(generated_json.as_bytes())?; + .write_all(generated_json_str.as_bytes())?; if std::env::var("ACCEPT").map_or(false, |v| v == "true") { std::fs::copy( @@ -55,8 +60,7 @@ fn integration_test(path: &str, essence_base: &str) -> Result<(), Box let expected_str = std::fs::read_to_string(format!("{path}/{essence_base}.expected.serialised.json"))?; - let mut expected_mdl: Model = serde_json::from_str(&expected_str)?; - expected_mdl.constraints = Vec::new(); // TODO - remove this line once we parse constraints + let expected_mdl: Model = serde_json::from_str(&expected_str)?; // -------------------------------------------------------------------------------- // assert that they are the same model @@ -66,4 +70,27 @@ fn integration_test(path: &str, essence_base: &str) -> Result<(), Box Ok(()) } +/// Recursively sorts the keys of all JSON objects within the provided JSON value. +/// +/// serde_json will output JSON objects in an arbitrary key order. +/// this is normally fine, except in our use case we wouldn't want to update the expected output again and again. +/// so a consistent (sorted) ordering of the keys is desirable. +/// +/// sorting is done in-place. +fn sort_json_object(value: &Value) -> Value { + match value { + Value::Object(obj) => { + let mut ordered: Vec<(String, Value)> = obj + .iter() + .map(|(k, v)| (k.clone(), sort_json_object(v))) + .collect(); + ordered.sort_by(|a, b| a.0.cmp(&b.0)); + + Value::Object(ordered.into_iter().collect()) + } + Value::Array(arr) => Value::Array(arr.iter().map(sort_json_object).collect()), + _ => value.clone(), + } +} + include!(concat!(env!("OUT_DIR"), "/gen_tests.rs")); diff --git a/conjure_oxide/tests/integration/basic/bool-01/bool-01.expected.serialised.json b/conjure_oxide/tests/integration/basic/bool-01/bool-01.expected.serialised.json index 06dbb91f2..102e718db 100644 --- a/conjure_oxide/tests/integration/basic/bool-01/bool-01.expected.serialised.json +++ b/conjure_oxide/tests/integration/basic/bool-01/bool-01.expected.serialised.json @@ -1,4 +1,5 @@ { + "constraints": [], "variables": [ [ { @@ -8,6 +9,5 @@ "domain": "BoolDomain" } ] - ], - "constraints": [] + ] } \ No newline at end of file diff --git a/conjure_oxide/tests/integration/basic/bool-02/bool-02.expected.serialised.json b/conjure_oxide/tests/integration/basic/bool-02/bool-02.expected.serialised.json index d5883b4ff..c652dff47 100644 --- a/conjure_oxide/tests/integration/basic/bool-02/bool-02.expected.serialised.json +++ b/conjure_oxide/tests/integration/basic/bool-02/bool-02.expected.serialised.json @@ -1,8 +1,9 @@ { + "constraints": [], "variables": [ [ { - "UserName": "y" + "UserName": "x" }, { "domain": "BoolDomain" @@ -10,12 +11,11 @@ ], [ { - "UserName": "x" + "UserName": "y" }, { "domain": "BoolDomain" } ] - ], - "constraints": [] + ] } \ No newline at end of file diff --git a/conjure_oxide/tests/integration/basic/bool-03/bool-03.expected.serialised.json b/conjure_oxide/tests/integration/basic/bool-03/bool-03.expected.serialised.json index 2ef697aed..c652dff47 100644 --- a/conjure_oxide/tests/integration/basic/bool-03/bool-03.expected.serialised.json +++ b/conjure_oxide/tests/integration/basic/bool-03/bool-03.expected.serialised.json @@ -1,4 +1,5 @@ { + "constraints": [], "variables": [ [ { @@ -16,6 +17,5 @@ "domain": "BoolDomain" } ] - ], - "constraints": [] + ] } \ No newline at end of file diff --git a/conjure_oxide/tests/integration/xyz/input.expected.serialised.json b/conjure_oxide/tests/integration/xyz/input.expected.serialised.json index f7a71e68e..d35477320 100644 --- a/conjure_oxide/tests/integration/xyz/input.expected.serialised.json +++ b/conjure_oxide/tests/integration/xyz/input.expected.serialised.json @@ -1,8 +1,9 @@ { + "constraints": [], "variables": [ [ { - "UserName": "a" + "UserName": "b" }, { "domain": { @@ -19,7 +20,7 @@ ], [ { - "UserName": "b" + "UserName": "a" }, { "domain": { @@ -51,6 +52,5 @@ } } ] - ], - "constraints": [] + ] } \ No newline at end of file From 22024b70e944b08181fe9bfa7e5ed0d4804a5059 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=CC=88zgu=CC=88r=20Akgu=CC=88n?= Date: Tue, 21 Nov 2023 21:22:18 +0000 Subject: [PATCH 03/20] parsing MkOpEq and MkOpNeq --- conjure_oxide/src/ast.rs | 6 ++++ conjure_oxide/src/parse.rs | 62 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/conjure_oxide/src/ast.rs b/conjure_oxide/src/ast.rs index 4a03b2d97..c9ce9fb0b 100644 --- a/conjure_oxide/src/ast.rs +++ b/conjure_oxide/src/ast.rs @@ -87,9 +87,15 @@ pub enum Range { pub enum Expression { ConstantInt(i32), Reference(Name), + Sum(Vec), + Eq(Box, Box), + Neq(Box, Box), Geq(Box, Box), + Leq(Box, Box), + Gt(Box, Box), + Lt(Box, Box), // Flattened Constraints SumGeq(Vec, Box), diff --git a/conjure_oxide/src/parse.rs b/conjure_oxide/src/parse.rs index 4daece974..9503b8d1a 100644 --- a/conjure_oxide/src/parse.rs +++ b/conjure_oxide/src/parse.rs @@ -30,7 +30,16 @@ pub fn parse_json(str: &String) -> Result { let (name, var) = parse_variable(entry.1)?; m.add_variable(name, var); } - "SuchThat" => parse_constraint(entry.1)?, + "SuchThat" => { + m.constraints = entry + .1 + .as_array() + .unwrap() + .iter() + .flat_map(parse_expression) + .collect(); + println!("Nb constraints {}", m.constraints.len()); + } _ => return Err(CError("mStatements contains an unknown object".to_owned())), } } @@ -109,8 +118,55 @@ fn parse_int_domain(v: &JsonValue) -> Result { Ok(Domain::IntDomain(ranges)) } -fn parse_constraint(obj: &JsonValue) -> Result<()> { - Ok(()) +fn parse_expression(obj: &JsonValue) -> Option { + println!("{}", " ----- ----- 1"); + match obj { + Value::Object(op) if (op.contains_key("Op")) => { + println!("{}", " ----- ----- 2"); + match &op["Op"] { + Value::Object(MkOpEq) if MkOpEq.contains_key("MkOpEq") => { + println!("{}", " ----- ----- 3"); + match &MkOpEq["MkOpEq"] { + Value::Array(MkOpEq_args) if MkOpEq_args.len() == 2 => { + println!("{}", " ----- ----- 4"); + let arg1 = parse_expression(&MkOpEq_args[0])?; + let arg2 = parse_expression(&MkOpEq_args[1])?; + Some(Expression::Eq(Box::new(arg1), Box::new(arg2))) + } + otherwise => { + println!("Unhandled {}", otherwise); + None + } + } + } + Value::Object(MkOpNeq) if MkOpNeq.contains_key("MkOpNeq") => { + println!("{}", " ----- ----- 3"); + match &MkOpNeq["MkOpNeq"] { + Value::Array(MkOpNeq_args) if MkOpNeq_args.len() == 2 => { + println!("{}", " ----- ----- 4"); + let arg1 = parse_expression(&MkOpNeq_args[0])?; + let arg2 = parse_expression(&MkOpNeq_args[1])?; + Some(Expression::Neq(Box::new(arg1), Box::new(arg2))) + } + otherwise => { + println!("Unhandled {}", otherwise); + None + } + } + } + otherwise => { + println!("Unhandled {}", otherwise); + None + } + } + } + otherwise => { + println!("Unhandled {}", otherwise); + None + } + } + // println!("{}", obj); + // Ok(()) } impl Model { From acf4930250225da00a6437f0f474767fa35271d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=CC=88zgu=CC=88r=20Akgu=CC=88n?= Date: Tue, 21 Nov 2023 21:53:14 +0000 Subject: [PATCH 04/20] getting into some advanced rust types to convince the compiler that this lookup table is ok --- conjure_oxide/src/parse.rs | 51 ++++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/conjure_oxide/src/parse.rs b/conjure_oxide/src/parse.rs index 9503b8d1a..e87bd0548 100644 --- a/conjure_oxide/src/parse.rs +++ b/conjure_oxide/src/parse.rs @@ -3,6 +3,8 @@ // we disable non_snake_case in this file becasue we want to use the constructor names of Conjure as variables. // just in this file, don't get wrong ideas! +use std::collections::HashMap; + use serde_json::Value; use crate::ast::{DecisionVariable, Domain, Expression, Model, Name, Range}; @@ -120,33 +122,46 @@ fn parse_int_domain(v: &JsonValue) -> Result { fn parse_expression(obj: &JsonValue) -> Option { println!("{}", " ----- ----- 1"); + + // this needs an explicit type signature to force the closures to have the same type + let binary_operators: HashMap< + &str, + Box, Box) -> Expression>, + > = [ + ( + "MkOpEq", + Box::new(|x, y| Expression::Eq(x, y)) as Box _>, + ), + ( + "MkOpNeq", + Box::new(|x, y| Expression::Neq(x, y)) as Box _>, + ), + ] + .into_iter() + .collect(); + + let mut binary_operator_names = binary_operators.iter().map(|x| x.0); + match obj { Value::Object(op) if (op.contains_key("Op")) => { println!("{}", " ----- ----- 2"); match &op["Op"] { - Value::Object(MkOpEq) if MkOpEq.contains_key("MkOpEq") => { + Value::Object(bin_op) + if binary_operator_names.any(|key| bin_op.contains_key(*key)) => + { + // we know there is a single key value pair in this object + // extract the value, ignore the key + let (key, value) = bin_op.into_iter().next()?; + + let constructor = binary_operators.get(key.as_str())?; + println!("{}", " ----- ----- 3"); - match &MkOpEq["MkOpEq"] { + match &value { Value::Array(MkOpEq_args) if MkOpEq_args.len() == 2 => { println!("{}", " ----- ----- 4"); let arg1 = parse_expression(&MkOpEq_args[0])?; let arg2 = parse_expression(&MkOpEq_args[1])?; - Some(Expression::Eq(Box::new(arg1), Box::new(arg2))) - } - otherwise => { - println!("Unhandled {}", otherwise); - None - } - } - } - Value::Object(MkOpNeq) if MkOpNeq.contains_key("MkOpNeq") => { - println!("{}", " ----- ----- 3"); - match &MkOpNeq["MkOpNeq"] { - Value::Array(MkOpNeq_args) if MkOpNeq_args.len() == 2 => { - println!("{}", " ----- ----- 4"); - let arg1 = parse_expression(&MkOpNeq_args[0])?; - let arg2 = parse_expression(&MkOpNeq_args[1])?; - Some(Expression::Neq(Box::new(arg1), Box::new(arg2))) + Some(constructor(Box::new(arg1), Box::new(arg2))) } otherwise => { println!("Unhandled {}", otherwise); From 4857eef405b2e916734629f670ea6130ff5be205 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=CC=88zgu=CC=88r=20Akgu=CC=88n?= Date: Tue, 21 Nov 2023 21:53:58 +0000 Subject: [PATCH 05/20] but now adding MkOpGeq is trivial... --- conjure_oxide/src/parse.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/conjure_oxide/src/parse.rs b/conjure_oxide/src/parse.rs index e87bd0548..cfe11ba2c 100644 --- a/conjure_oxide/src/parse.rs +++ b/conjure_oxide/src/parse.rs @@ -136,6 +136,10 @@ fn parse_expression(obj: &JsonValue) -> Option { "MkOpNeq", Box::new(|x, y| Expression::Neq(x, y)) as Box _>, ), + ( + "MkOpGeq", + Box::new(|x, y| Expression::Geq(x, y)) as Box _>, + ), ] .into_iter() .collect(); From 482970432c1a9eaa397e9bc89b1c6b11925cda2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=CC=88zgu=CC=88r=20Akgu=CC=88n?= Date: Tue, 21 Nov 2023 21:54:41 +0000 Subject: [PATCH 06/20] and Leq, Gt, Lt --- conjure_oxide/src/parse.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/conjure_oxide/src/parse.rs b/conjure_oxide/src/parse.rs index cfe11ba2c..897ac419e 100644 --- a/conjure_oxide/src/parse.rs +++ b/conjure_oxide/src/parse.rs @@ -140,6 +140,18 @@ fn parse_expression(obj: &JsonValue) -> Option { "MkOpGeq", Box::new(|x, y| Expression::Geq(x, y)) as Box _>, ), + ( + "MkOpLeq", + Box::new(|x, y| Expression::Leq(x, y)) as Box _>, + ), + ( + "MkOpGt", + Box::new(|x, y| Expression::Gt(x, y)) as Box _>, + ), + ( + "MkOpLt", + Box::new(|x, y| Expression::Lt(x, y)) as Box _>, + ), ] .into_iter() .collect(); From 09869400e01fcefea0b3fc08f49f0973d06d44f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=CC=88zgu=CC=88r=20Akgu=CC=88n?= Date: Tue, 21 Nov 2023 21:59:02 +0000 Subject: [PATCH 07/20] added Reference "parsing" --- conjure_oxide/src/parse.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/conjure_oxide/src/parse.rs b/conjure_oxide/src/parse.rs index 897ac419e..281771975 100644 --- a/conjure_oxide/src/parse.rs +++ b/conjure_oxide/src/parse.rs @@ -159,7 +159,7 @@ fn parse_expression(obj: &JsonValue) -> Option { let mut binary_operator_names = binary_operators.iter().map(|x| x.0); match obj { - Value::Object(op) if (op.contains_key("Op")) => { + Value::Object(op) if op.contains_key("Op") => { println!("{}", " ----- ----- 2"); match &op["Op"] { Value::Object(bin_op) @@ -191,6 +191,10 @@ fn parse_expression(obj: &JsonValue) -> Option { } } } + Value::Object(refe) if refe.contains_key("Reference") => { + let name = refe["Reference"].as_array()?[0].as_object()?["Name"].as_str()?; + Some(Expression::Reference(Name::UserName(name.to_string()))) + } otherwise => { println!("Unhandled {}", otherwise); None From 15b32cd5acd78c52acb20eca4eb51c6991247019 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=CC=88zgu=CC=88r=20Akgu=CC=88n?= Date: Tue, 21 Nov 2023 22:22:22 +0000 Subject: [PATCH 08/20] parsing sums --- conjure_oxide/src/parse.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/conjure_oxide/src/parse.rs b/conjure_oxide/src/parse.rs index 281771975..1eb4e3af0 100644 --- a/conjure_oxide/src/parse.rs +++ b/conjure_oxide/src/parse.rs @@ -185,6 +185,14 @@ fn parse_expression(obj: &JsonValue) -> Option { } } } + Value::Object(op_sum) if op_sum.contains_key("MkOpSum") => { + let args = &op_sum["MkOpSum"]["AbstractLiteral"]["AbsLitMatrix"][1]; + let args_parsed = args + .as_array()? + .iter() + .map(|x| parse_expression(x).unwrap()); + Some(Expression::Sum(args_parsed.collect())) + } otherwise => { println!("Unhandled {}", otherwise); None From 44cdeaec5c0067a6a3f79e7ccba6552be63dbc02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=CC=88zgu=CC=88r=20Akgu=CC=88n?= Date: Tue, 21 Nov 2023 22:22:39 +0000 Subject: [PATCH 09/20] parsing ints --- conjure_oxide/src/parse.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/conjure_oxide/src/parse.rs b/conjure_oxide/src/parse.rs index 1eb4e3af0..72713b0af 100644 --- a/conjure_oxide/src/parse.rs +++ b/conjure_oxide/src/parse.rs @@ -203,6 +203,22 @@ fn parse_expression(obj: &JsonValue) -> Option { let name = refe["Reference"].as_array()?[0].as_object()?["Name"].as_str()?; Some(Expression::Reference(Name::UserName(name.to_string()))) } + Value::Object(constant) if constant.contains_key("Constant") => { + match &constant["Constant"] { + Value::Object(int) if int.contains_key("ConstantInt") => { + Some(Expression::ConstantInt( + int["ConstantInt"].as_array()?[1] + .as_i64()? + .try_into() + .unwrap(), + )) + } + otherwise => { + println!("Unhandled 4 {}", otherwise); + None + } + } + } otherwise => { println!("Unhandled {}", otherwise); None From 37a05d66f86f3b3d8fe97058d8226e23e283e04a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=CC=88zgu=CC=88r=20Akgu=CC=88n?= Date: Tue, 21 Nov 2023 22:22:54 +0000 Subject: [PATCH 10/20] labeling the unhandled cases --- conjure_oxide/src/parse.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/conjure_oxide/src/parse.rs b/conjure_oxide/src/parse.rs index 72713b0af..d1432a2bd 100644 --- a/conjure_oxide/src/parse.rs +++ b/conjure_oxide/src/parse.rs @@ -180,7 +180,7 @@ fn parse_expression(obj: &JsonValue) -> Option { Some(constructor(Box::new(arg1), Box::new(arg2))) } otherwise => { - println!("Unhandled {}", otherwise); + println!("Unhandled 3 {}", otherwise); None } } @@ -194,7 +194,7 @@ fn parse_expression(obj: &JsonValue) -> Option { Some(Expression::Sum(args_parsed.collect())) } otherwise => { - println!("Unhandled {}", otherwise); + println!("Unhandled 2 {}", otherwise); None } } @@ -220,7 +220,7 @@ fn parse_expression(obj: &JsonValue) -> Option { } } otherwise => { - println!("Unhandled {}", otherwise); + println!("Unhandled 1 {}", otherwise); None } } From 0738c7daa5fb425d2b5f87144ab1cae99c17c110 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=CC=88zgu=CC=88r=20Akgu=CC=88n?= Date: Tue, 21 Nov 2023 22:47:11 +0000 Subject: [PATCH 11/20] fix: extend the constraints instead of overwriting them --- conjure_oxide/src/parse.rs | 50 +++++++++++--------------------------- 1 file changed, 14 insertions(+), 36 deletions(-) diff --git a/conjure_oxide/src/parse.rs b/conjure_oxide/src/parse.rs index d1432a2bd..79980f9a1 100644 --- a/conjure_oxide/src/parse.rs +++ b/conjure_oxide/src/parse.rs @@ -1,8 +1,3 @@ -#![allow(non_snake_case)] - -// we disable non_snake_case in this file becasue we want to use the constructor names of Conjure as variables. -// just in this file, don't get wrong ideas! - use std::collections::HashMap; use serde_json::Value; @@ -33,14 +28,15 @@ pub fn parse_json(str: &String) -> Result { m.add_variable(name, var); } "SuchThat" => { - m.constraints = entry + let constraints: Vec = entry .1 .as_array() .unwrap() .iter() .flat_map(parse_expression) .collect(); - println!("Nb constraints {}", m.constraints.len()); + m.constraints.extend(constraints); + // println!("Nb constraints {}", m.constraints.len()); } _ => return Err(CError("mStatements contains an unknown object".to_owned())), } @@ -121,8 +117,6 @@ fn parse_int_domain(v: &JsonValue) -> Result { } fn parse_expression(obj: &JsonValue) -> Option { - println!("{}", " ----- ----- 1"); - // this needs an explicit type signature to force the closures to have the same type let binary_operators: HashMap< &str, @@ -160,7 +154,6 @@ fn parse_expression(obj: &JsonValue) -> Option { match obj { Value::Object(op) if op.contains_key("Op") => { - println!("{}", " ----- ----- 2"); match &op["Op"] { Value::Object(bin_op) if binary_operator_names.any(|key| bin_op.contains_key(*key)) => @@ -171,32 +164,25 @@ fn parse_expression(obj: &JsonValue) -> Option { let constructor = binary_operators.get(key.as_str())?; - println!("{}", " ----- ----- 3"); match &value { - Value::Array(MkOpEq_args) if MkOpEq_args.len() == 2 => { - println!("{}", " ----- ----- 4"); - let arg1 = parse_expression(&MkOpEq_args[0])?; - let arg2 = parse_expression(&MkOpEq_args[1])?; + Value::Array(bin_op_args) if bin_op_args.len() == 2 => { + let arg1 = parse_expression(&bin_op_args[0])?; + let arg2 = parse_expression(&bin_op_args[1])?; Some(constructor(Box::new(arg1), Box::new(arg2))) } - otherwise => { - println!("Unhandled 3 {}", otherwise); - None - } + otherwise => panic!("Unhandled 3 {}", otherwise), } } Value::Object(op_sum) if op_sum.contains_key("MkOpSum") => { let args = &op_sum["MkOpSum"]["AbstractLiteral"]["AbsLitMatrix"][1]; - let args_parsed = args + let args_parsed: Vec = args .as_array()? .iter() - .map(|x| parse_expression(x).unwrap()); - Some(Expression::Sum(args_parsed.collect())) - } - otherwise => { - println!("Unhandled 2 {}", otherwise); - None + .map(|x| parse_expression(x).unwrap()) + .collect(); + Some(Expression::Sum(args_parsed)) } + otherwise => panic!("Unhandled 2 {}", otherwise), } } Value::Object(refe) if refe.contains_key("Reference") => { @@ -213,19 +199,11 @@ fn parse_expression(obj: &JsonValue) -> Option { .unwrap(), )) } - otherwise => { - println!("Unhandled 4 {}", otherwise); - None - } + otherwise => panic!("Unhandled 4 {}", otherwise), } } - otherwise => { - println!("Unhandled 1 {}", otherwise); - None - } + otherwise => panic!("Unhandled 1 {}", otherwise), } - // println!("{}", obj); - // Ok(()) } impl Model { From 6dc4e76ad042ee11960b684cb987f19af959339f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=CC=88zgu=CC=88r=20Akgu=CC=88n?= Date: Tue, 21 Nov 2023 22:47:30 +0000 Subject: [PATCH 12/20] update test files: constraints parsed --- .../bool-03/bool-03.expected.serialised.json | 17 ++++++- .../xyz/input.expected.serialised.json | 47 ++++++++++++++++++- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/conjure_oxide/tests/integration/basic/bool-03/bool-03.expected.serialised.json b/conjure_oxide/tests/integration/basic/bool-03/bool-03.expected.serialised.json index c652dff47..46c572cb7 100644 --- a/conjure_oxide/tests/integration/basic/bool-03/bool-03.expected.serialised.json +++ b/conjure_oxide/tests/integration/basic/bool-03/bool-03.expected.serialised.json @@ -1,5 +1,20 @@ { - "constraints": [], + "constraints": [ + { + "Neq": [ + { + "Reference": { + "UserName": "x" + } + }, + { + "Reference": { + "UserName": "y" + } + } + ] + } + ], "variables": [ [ { diff --git a/conjure_oxide/tests/integration/xyz/input.expected.serialised.json b/conjure_oxide/tests/integration/xyz/input.expected.serialised.json index d35477320..f82478226 100644 --- a/conjure_oxide/tests/integration/xyz/input.expected.serialised.json +++ b/conjure_oxide/tests/integration/xyz/input.expected.serialised.json @@ -1,5 +1,50 @@ { - "constraints": [], + "constraints": [ + { + "Eq": [ + { + "Sum": [ + { + "Sum": [ + { + "Reference": { + "UserName": "a" + } + }, + { + "Reference": { + "UserName": "b" + } + } + ] + }, + { + "Reference": { + "UserName": "c" + } + } + ] + }, + { + "ConstantInt": 4 + } + ] + }, + { + "Geq": [ + { + "Reference": { + "UserName": "a" + } + }, + { + "Reference": { + "UserName": "b" + } + } + ] + } + ], "variables": [ [ { From 6a6b82463e88dcfe0956cffde7e0e9ded613be77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=CC=88zgu=CC=88r=20Akgu=CC=88n?= Date: Tue, 21 Nov 2023 22:48:37 +0000 Subject: [PATCH 13/20] remove unused value --- conjure_oxide/tests/generated_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conjure_oxide/tests/generated_tests.rs b/conjure_oxide/tests/generated_tests.rs index 4e1a15fc0..0166d6119 100644 --- a/conjure_oxide/tests/generated_tests.rs +++ b/conjure_oxide/tests/generated_tests.rs @@ -1,5 +1,5 @@ use conjure_oxide::ast::Model; -use serde_json::{Map, Value}; +use serde_json::{Value}; use std::env; use std::error::Error; use std::fs::File; From 3b670136e126f0bcf976d5cc203d9219b2db4890 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=CC=88zgu=CC=88r=20Akgu=CC=88n?= Date: Tue, 21 Nov 2023 23:14:10 +0000 Subject: [PATCH 14/20] Sort the "variables" field by name. We have to do this separately becasue that field is not a JSON object, instead it's an array of tuples. --- conjure_oxide/src/ast.rs | 2 +- conjure_oxide/tests/generated_tests.rs | 35 +++++++++++++++++-- .../xyz/input.expected.serialised.json | 4 +-- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/conjure_oxide/src/ast.rs b/conjure_oxide/src/ast.rs index c9ce9fb0b..b5bb941bd 100644 --- a/conjure_oxide/src/ast.rs +++ b/conjure_oxide/src/ast.rs @@ -36,7 +36,7 @@ impl Default for Model { } } -#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] pub enum Name { UserName(String), MachineName(i32), diff --git a/conjure_oxide/tests/generated_tests.rs b/conjure_oxide/tests/generated_tests.rs index 0166d6119..c9f343829 100644 --- a/conjure_oxide/tests/generated_tests.rs +++ b/conjure_oxide/tests/generated_tests.rs @@ -1,9 +1,9 @@ use conjure_oxide::ast::Model; -use serde_json::{Value}; -use std::env; +use serde_json::Value; use std::error::Error; use std::fs::File; use std::io::prelude::*; +use std::{clone, env}; use std::path::Path; @@ -82,7 +82,14 @@ fn sort_json_object(value: &Value) -> Value { Value::Object(obj) => { let mut ordered: Vec<(String, Value)> = obj .iter() - .map(|(k, v)| (k.clone(), sort_json_object(v))) + .map(|(k, v)| { + if k == "variables" { + (k.clone(), sort_json_variables(v)) + } else { + (k.clone(), sort_json_object(v)) + } + }) + // .map(|(k, v)| (k.clone(), sort_json_object(v))) .collect(); ordered.sort_by(|a, b| a.0.cmp(&b.0)); @@ -93,4 +100,26 @@ fn sort_json_object(value: &Value) -> Value { } } +/// Sort the "variables" field by name. +/// We have to do this separately becasue that field is not a JSON object, instead it's an array of tuples. +fn sort_json_variables(value: &Value) -> Value { + match value { + Value::Array(vars) => { + let mut vars_sorted = vars.clone(); + vars_sorted.sort_by(|a, b| { + let a_obj = &a.as_array().unwrap()[0]; + let a_name : conjure_oxide::ast::Name = serde_json::from_value(a_obj.clone()).unwrap(); + + let b_obj = &b.as_array().unwrap()[0]; + let b_name : conjure_oxide::ast::Name = serde_json::from_value(b_obj.clone()).unwrap(); + + a_name.cmp(&b_name) + + }); + Value::Array(vars_sorted) + } + _ => value.clone(), + } +} + include!(concat!(env!("OUT_DIR"), "/gen_tests.rs")); diff --git a/conjure_oxide/tests/integration/xyz/input.expected.serialised.json b/conjure_oxide/tests/integration/xyz/input.expected.serialised.json index f82478226..249f50953 100644 --- a/conjure_oxide/tests/integration/xyz/input.expected.serialised.json +++ b/conjure_oxide/tests/integration/xyz/input.expected.serialised.json @@ -48,7 +48,7 @@ "variables": [ [ { - "UserName": "b" + "UserName": "a" }, { "domain": { @@ -65,7 +65,7 @@ ], [ { - "UserName": "a" + "UserName": "b" }, { "domain": { From 7743eb386147b665bfed869550af889bef75d75e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=CC=88zgu=CC=88r=20Akgu=CC=88n?= Date: Tue, 21 Nov 2023 23:21:35 +0000 Subject: [PATCH 15/20] clippy my new friend --- conjure_oxide/src/parse.rs | 21 ++++++--------------- conjure_oxide/tests/generated_tests.rs | 9 +++++---- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/conjure_oxide/src/parse.rs b/conjure_oxide/src/parse.rs index 79980f9a1..1fca5fee7 100644 --- a/conjure_oxide/src/parse.rs +++ b/conjure_oxide/src/parse.rs @@ -122,30 +122,21 @@ fn parse_expression(obj: &JsonValue) -> Option { &str, Box, Box) -> Expression>, > = [ - ( - "MkOpEq", - Box::new(|x, y| Expression::Eq(x, y)) as Box _>, - ), + ("MkOpEq", Box::new(Expression::Eq) as Box _>), ( "MkOpNeq", - Box::new(|x, y| Expression::Neq(x, y)) as Box _>, + Box::new(Expression::Neq) as Box _>, ), ( "MkOpGeq", - Box::new(|x, y| Expression::Geq(x, y)) as Box _>, + Box::new(Expression::Geq) as Box _>, ), ( "MkOpLeq", - Box::new(|x, y| Expression::Leq(x, y)) as Box _>, - ), - ( - "MkOpGt", - Box::new(|x, y| Expression::Gt(x, y)) as Box _>, - ), - ( - "MkOpLt", - Box::new(|x, y| Expression::Lt(x, y)) as Box _>, + Box::new(Expression::Leq) as Box _>, ), + ("MkOpGt", Box::new(Expression::Gt) as Box _>), + ("MkOpLt", Box::new(Expression::Lt) as Box _>), ] .into_iter() .collect(); diff --git a/conjure_oxide/tests/generated_tests.rs b/conjure_oxide/tests/generated_tests.rs index c9f343829..908690fa3 100644 --- a/conjure_oxide/tests/generated_tests.rs +++ b/conjure_oxide/tests/generated_tests.rs @@ -1,9 +1,9 @@ use conjure_oxide::ast::Model; use serde_json::Value; +use std::env; use std::error::Error; use std::fs::File; use std::io::prelude::*; -use std::{clone, env}; use std::path::Path; @@ -108,13 +108,14 @@ fn sort_json_variables(value: &Value) -> Value { let mut vars_sorted = vars.clone(); vars_sorted.sort_by(|a, b| { let a_obj = &a.as_array().unwrap()[0]; - let a_name : conjure_oxide::ast::Name = serde_json::from_value(a_obj.clone()).unwrap(); + let a_name: conjure_oxide::ast::Name = + serde_json::from_value(a_obj.clone()).unwrap(); let b_obj = &b.as_array().unwrap()[0]; - let b_name : conjure_oxide::ast::Name = serde_json::from_value(b_obj.clone()).unwrap(); + let b_name: conjure_oxide::ast::Name = + serde_json::from_value(b_obj.clone()).unwrap(); a_name.cmp(&b_name) - }); Value::Array(vars_sorted) } From 378bbe48a05e6df6a5af518683e7b5b73414729c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=CC=88zgu=CC=88r=20Akgu=CC=88n?= Date: Tue, 21 Nov 2023 23:29:48 +0000 Subject: [PATCH 16/20] more clippy, more panics --- conjure_oxide/src/parse.rs | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/conjure_oxide/src/parse.rs b/conjure_oxide/src/parse.rs index 45ab4988c..33b6ff033 100644 --- a/conjure_oxide/src/parse.rs +++ b/conjure_oxide/src/parse.rs @@ -38,7 +38,7 @@ pub fn parse_json(str: &str) -> Result { m.constraints.extend(constraints); // println!("Nb constraints {}", m.constraints.len()); } - _ => return Err(CError("mStatements contains an unknown object".to_owned())), + otherwise => panic!("Unhandled 0 {}", otherwise), } } @@ -100,14 +100,13 @@ fn parse_int_domain(v: &JsonValue) -> Result { .as_array() .ok_or(Error::Parse("RangeBounded is not an array".to_owned()))?; let mut nums = Vec::new(); - for i in 0..2 { - let num = - &arr[i]["Constant"]["ConstantInt"][1] - .as_i64() - .ok_or(Error::Parse( - "Could not parse int domain constant".to_owned(), - ))?; - let num32 = i32::try_from(*num).map_err(|_| { + for item in arr.iter() { + let num = item["Constant"]["ConstantInt"][1] + .as_i64() + .ok_or(Error::Parse( + "Could not parse int domain constant".to_owned(), + ))?; + let num32 = i32::try_from(num).map_err(|_| { Error::Parse("Could not parse int domain constant".to_owned()) })?; nums.push(num32); @@ -136,10 +135,8 @@ fn parse_int_domain(v: &JsonValue) -> Result { fn parse_expression(obj: &JsonValue) -> Option { // this needs an explicit type signature to force the closures to have the same type - let binary_operators: HashMap< - &str, - Box, Box) -> Expression>, - > = [ + type BinOp = Box, Box) -> Expression>; + let binary_operators: HashMap<&str, BinOp> = [ ("MkOpEq", Box::new(Expression::Eq) as Box _>), ( "MkOpNeq", @@ -216,7 +213,7 @@ fn parse_expression(obj: &JsonValue) -> Option { } impl Model { - pub fn from_json(str: &String) -> Result { + pub fn from_json(str: &str) -> Result { parse_json(str) } } From 069cd5f82da02141afa4c2b71a21903e1ae85406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=CC=88zgu=CC=88r=20Akgu=CC=88n?= Date: Tue, 21 Nov 2023 23:58:41 +0000 Subject: [PATCH 17/20] debug format the otherwise cases --- conjure_oxide/src/parse.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/conjure_oxide/src/parse.rs b/conjure_oxide/src/parse.rs index 33b6ff033..a85316610 100644 --- a/conjure_oxide/src/parse.rs +++ b/conjure_oxide/src/parse.rs @@ -38,7 +38,7 @@ pub fn parse_json(str: &str) -> Result { m.constraints.extend(constraints); // println!("Nb constraints {}", m.constraints.len()); } - otherwise => panic!("Unhandled 0 {}", otherwise), + otherwise => panic!("Unhandled 0 {:#?}", otherwise), } } @@ -176,7 +176,7 @@ fn parse_expression(obj: &JsonValue) -> Option { let arg2 = parse_expression(&bin_op_args[1])?; Some(constructor(Box::new(arg1), Box::new(arg2))) } - otherwise => panic!("Unhandled 3 {}", otherwise), + otherwise => panic!("Unhandled 3 {:#?}", otherwise), } } Value::Object(op_sum) if op_sum.contains_key("MkOpSum") => { @@ -188,7 +188,7 @@ fn parse_expression(obj: &JsonValue) -> Option { .collect(); Some(Expression::Sum(args_parsed)) } - otherwise => panic!("Unhandled 2 {}", otherwise), + otherwise => panic!("Unhandled 2 {:#?}", otherwise), } } Value::Object(refe) if refe.contains_key("Reference") => { @@ -205,10 +205,10 @@ fn parse_expression(obj: &JsonValue) -> Option { .unwrap(), )) } - otherwise => panic!("Unhandled 4 {}", otherwise), + otherwise => panic!("Unhandled 4 {:#?}", otherwise), } } - otherwise => panic!("Unhandled 1 {}", otherwise), + otherwise => panic!("Unhandled 1 {:#?}", otherwise), } } From 54958a6d923562f7508a994bbcd6f18402b20a5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=CC=88zgu=CC=88r=20Akgu=CC=88n?= Date: Wed, 22 Nov 2023 09:28:50 +0000 Subject: [PATCH 18/20] remove comment about in-place sorting --- conjure_oxide/tests/generated_tests.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/conjure_oxide/tests/generated_tests.rs b/conjure_oxide/tests/generated_tests.rs index 908690fa3..fbb8d49ea 100644 --- a/conjure_oxide/tests/generated_tests.rs +++ b/conjure_oxide/tests/generated_tests.rs @@ -75,8 +75,6 @@ fn integration_test(path: &str, essence_base: &str) -> Result<(), Box /// serde_json will output JSON objects in an arbitrary key order. /// this is normally fine, except in our use case we wouldn't want to update the expected output again and again. /// so a consistent (sorted) ordering of the keys is desirable. -/// -/// sorting is done in-place. fn sort_json_object(value: &Value) -> Value { match value { Value::Object(obj) => { From eafaa27efba232100af4bf6289fad4b8cee6bca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=CC=88zgu=CC=88r=20Akgu=CC=88n?= Date: Wed, 22 Nov 2023 09:36:37 +0000 Subject: [PATCH 19/20] refactor --- conjure_oxide/src/parse.rs | 95 ++++++++++++++++++++------------------ 1 file changed, 50 insertions(+), 45 deletions(-) diff --git a/conjure_oxide/src/parse.rs b/conjure_oxide/src/parse.rs index a85316610..427b93a7e 100644 --- a/conjure_oxide/src/parse.rs +++ b/conjure_oxide/src/parse.rs @@ -38,7 +38,7 @@ pub fn parse_json(str: &str) -> Result { m.constraints.extend(constraints); // println!("Nb constraints {}", m.constraints.len()); } - otherwise => panic!("Unhandled 0 {:#?}", otherwise), + otherwise => panic!("Unhandled Statement {:#?}", otherwise), } } @@ -159,56 +159,61 @@ fn parse_expression(obj: &JsonValue) -> Option { let mut binary_operator_names = binary_operators.iter().map(|x| x.0); match obj { - Value::Object(op) if op.contains_key("Op") => { - match &op["Op"] { - Value::Object(bin_op) - if binary_operator_names.any(|key| bin_op.contains_key(*key)) => - { - // we know there is a single key value pair in this object - // extract the value, ignore the key - let (key, value) = bin_op.into_iter().next()?; - - let constructor = binary_operators.get(key.as_str())?; - - match &value { - Value::Array(bin_op_args) if bin_op_args.len() == 2 => { - let arg1 = parse_expression(&bin_op_args[0])?; - let arg2 = parse_expression(&bin_op_args[1])?; - Some(constructor(Box::new(arg1), Box::new(arg2))) - } - otherwise => panic!("Unhandled 3 {:#?}", otherwise), - } - } - Value::Object(op_sum) if op_sum.contains_key("MkOpSum") => { - let args = &op_sum["MkOpSum"]["AbstractLiteral"]["AbsLitMatrix"][1]; - let args_parsed: Vec = args - .as_array()? - .iter() - .map(|x| parse_expression(x).unwrap()) - .collect(); - Some(Expression::Sum(args_parsed)) - } - otherwise => panic!("Unhandled 2 {:#?}", otherwise), + Value::Object(op) if op.contains_key("Op") => match &op["Op"] { + Value::Object(bin_op) if binary_operator_names.any(|key| bin_op.contains_key(*key)) => { + parse_bin_op(bin_op, binary_operators) } - } + Value::Object(op_sum) if op_sum.contains_key("MkOpSum") => parse_sum(op_sum), + otherwise => panic!("Unhandled Op {:#?}", otherwise), + }, Value::Object(refe) if refe.contains_key("Reference") => { let name = refe["Reference"].as_array()?[0].as_object()?["Name"].as_str()?; Some(Expression::Reference(Name::UserName(name.to_string()))) } - Value::Object(constant) if constant.contains_key("Constant") => { - match &constant["Constant"] { - Value::Object(int) if int.contains_key("ConstantInt") => { - Some(Expression::ConstantInt( - int["ConstantInt"].as_array()?[1] - .as_i64()? - .try_into() - .unwrap(), - )) - } - otherwise => panic!("Unhandled 4 {:#?}", otherwise), - } + Value::Object(constant) if constant.contains_key("Constant") => parse_constant(constant), + otherwise => panic!("Unhandled Expression {:#?}", otherwise), + } +} + +fn parse_sum(op_sum: &serde_json::Map) -> Option { + let args = &op_sum["MkOpSum"]["AbstractLiteral"]["AbsLitMatrix"][1]; + let args_parsed: Vec = args + .as_array()? + .iter() + .map(|x| parse_expression(x).unwrap()) + .collect(); + Some(Expression::Sum(args_parsed)) +} + +fn parse_bin_op( + bin_op: &serde_json::Map, + binary_operators: HashMap<&str, Box, Box) -> Expression>>, +) -> Option { + // we know there is a single key value pair in this object + // extract the value, ignore the key + let (key, value) = bin_op.into_iter().next()?; + + let constructor = binary_operators.get(key.as_str())?; + + match &value { + Value::Array(bin_op_args) if bin_op_args.len() == 2 => { + let arg1 = parse_expression(&bin_op_args[0])?; + let arg2 = parse_expression(&bin_op_args[1])?; + Some(constructor(Box::new(arg1), Box::new(arg2))) } - otherwise => panic!("Unhandled 1 {:#?}", otherwise), + otherwise => panic!("Unhandled parse_bin_op {:#?}", otherwise), + } +} + +fn parse_constant(constant: &serde_json::Map) -> Option { + match &constant["Constant"] { + Value::Object(int) if int.contains_key("ConstantInt") => Some(Expression::ConstantInt( + int["ConstantInt"].as_array()?[1] + .as_i64()? + .try_into() + .unwrap(), + )), + otherwise => panic!("Unhandled parse_constant {:#?}", otherwise), } } From 718d1bccd9363b8e226ea5ccf8c7c35c9bbd2db0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=CC=88zgu=CC=88r=20Akgu=CC=88n?= Date: Wed, 22 Nov 2023 09:41:51 +0000 Subject: [PATCH 20/20] move BinOp to top level --- conjure_oxide/src/parse.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/conjure_oxide/src/parse.rs b/conjure_oxide/src/parse.rs index 427b93a7e..0bfc7bcfd 100644 --- a/conjure_oxide/src/parse.rs +++ b/conjure_oxide/src/parse.rs @@ -133,9 +133,10 @@ fn parse_int_domain(v: &JsonValue) -> Result { Ok(Domain::IntDomain(ranges)) } +// this needs an explicit type signature to force the closures to have the same type +type BinOp = Box, Box) -> Expression>; + fn parse_expression(obj: &JsonValue) -> Option { - // this needs an explicit type signature to force the closures to have the same type - type BinOp = Box, Box) -> Expression>; let binary_operators: HashMap<&str, BinOp> = [ ("MkOpEq", Box::new(Expression::Eq) as Box _>), ( @@ -187,7 +188,7 @@ fn parse_sum(op_sum: &serde_json::Map) -> Option { fn parse_bin_op( bin_op: &serde_json::Map, - binary_operators: HashMap<&str, Box, Box) -> Expression>>, + binary_operators: HashMap<&str, BinOp>, ) -> Option { // we know there is a single key value pair in this object // extract the value, ignore the key