-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Export/import of JSON metadata #1622
Changes from all commits
28aaab4
25bc7d9
55e3d97
572b466
acf927d
7a61908
3eeb9f7
bf401c1
9801860
7eab2e2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,26 @@ | ||
--- | ||
source: hugr-core/tests/model.rs | ||
expression: "roundtrip(include_str!(\"fixtures/model-call.edn\"))" | ||
expression: "roundtrip(include_str!(\"../../hugr-model/tests/fixtures/model-call.edn\"))" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a shame, is it possible to arrange things so that this cross-crate reference is removed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could move the tests that are in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that means yes, we could duplicate them. I prefer duplication, up to you. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll merge this for now but create an issue so we can figure out if there is some elegant approach to this. |
||
--- | ||
(hugr 0) | ||
|
||
(declare-func example.callee | ||
(forall ?0 ext-set) | ||
[(@ arithmetic.int.types.int)] | ||
[(@ arithmetic.int.types.int)] | ||
(ext arithmetic.int . ?0)) | ||
(ext arithmetic.int . ?0) | ||
(meta doc.description (@ prelude.json "\"This is a function declaration.\"")) | ||
(meta doc.title (@ prelude.json "\"Callee\""))) | ||
|
||
(define-func example.caller | ||
[(@ arithmetic.int.types.int)] | ||
[(@ arithmetic.int.types.int)] | ||
(ext arithmetic.int) | ||
(meta doc.description | ||
(@ | ||
prelude.json | ||
"\"This defines a function that calls the function which we declared earlier.\"")) | ||
(meta doc.title (@ prelude.json "\"Caller\"")) | ||
(dfg | ||
[%0] [%1] | ||
(signature | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
use bumpalo::Bump; | ||
use bumpalo::{collections::String as BumpString, Bump}; | ||
use pest::{ | ||
iterators::{Pair, Pairs}, | ||
Parser, RuleType, | ||
|
@@ -60,7 +60,7 @@ impl<'a> ParseContext<'a> { | |
} | ||
|
||
fn parse_module(&mut self, pair: Pair<'a, Rule>) -> ParseResult<()> { | ||
debug_assert!(matches!(pair.as_rule(), Rule::module)); | ||
debug_assert_eq!(pair.as_rule(), Rule::module); | ||
let mut inner = pair.into_inner(); | ||
let meta = self.parse_meta(&mut inner)?; | ||
|
||
|
@@ -81,7 +81,7 @@ impl<'a> ParseContext<'a> { | |
} | ||
|
||
fn parse_term(&mut self, pair: Pair<'a, Rule>) -> ParseResult<TermId> { | ||
debug_assert!(matches!(pair.as_rule(), Rule::term)); | ||
debug_assert_eq!(pair.as_rule(), Rule::term); | ||
let pair = pair.into_inner().next().unwrap(); | ||
let rule = pair.as_rule(); | ||
let mut inner = pair.into_inner(); | ||
|
@@ -160,9 +160,7 @@ impl<'a> ParseContext<'a> { | |
} | ||
|
||
Rule::term_str => { | ||
// TODO: Escaping? | ||
let value = inner.next().unwrap().as_str(); | ||
let value = &value[1..value.len() - 1]; | ||
let value = self.parse_string(inner.next().unwrap())?; | ||
Term::Str(value) | ||
} | ||
|
||
|
@@ -218,7 +216,7 @@ impl<'a> ParseContext<'a> { | |
} | ||
|
||
fn parse_node(&mut self, pair: Pair<'a, Rule>) -> ParseResult<NodeId> { | ||
debug_assert!(matches!(pair.as_rule(), Rule::node)); | ||
debug_assert_eq!(pair.as_rule(), Rule::node); | ||
let pair = pair.into_inner().next().unwrap(); | ||
let rule = pair.as_rule(); | ||
|
||
|
@@ -503,7 +501,7 @@ impl<'a> ParseContext<'a> { | |
} | ||
|
||
fn parse_region(&mut self, pair: Pair<'a, Rule>) -> ParseResult<RegionId> { | ||
debug_assert!(matches!(pair.as_rule(), Rule::region)); | ||
debug_assert_eq!(pair.as_rule(), Rule::region); | ||
let pair = pair.into_inner().next().unwrap(); | ||
let rule = pair.as_rule(); | ||
let mut inner = pair.into_inner(); | ||
|
@@ -541,7 +539,7 @@ impl<'a> ParseContext<'a> { | |
} | ||
|
||
fn parse_func_header(&mut self, pair: Pair<'a, Rule>) -> ParseResult<&'a FuncDecl<'a>> { | ||
debug_assert!(matches!(pair.as_rule(), Rule::func_header)); | ||
debug_assert_eq!(pair.as_rule(), Rule::func_header); | ||
|
||
let mut inner = pair.into_inner(); | ||
let name = self.parse_symbol(&mut inner)?; | ||
|
@@ -566,7 +564,7 @@ impl<'a> ParseContext<'a> { | |
} | ||
|
||
fn parse_alias_header(&mut self, pair: Pair<'a, Rule>) -> ParseResult<&'a AliasDecl<'a>> { | ||
debug_assert!(matches!(pair.as_rule(), Rule::alias_header)); | ||
debug_assert_eq!(pair.as_rule(), Rule::alias_header); | ||
|
||
let mut inner = pair.into_inner(); | ||
let name = self.parse_symbol(&mut inner)?; | ||
|
@@ -581,7 +579,7 @@ impl<'a> ParseContext<'a> { | |
} | ||
|
||
fn parse_ctr_header(&mut self, pair: Pair<'a, Rule>) -> ParseResult<&'a ConstructorDecl<'a>> { | ||
debug_assert!(matches!(pair.as_rule(), Rule::ctr_header)); | ||
debug_assert_eq!(pair.as_rule(), Rule::ctr_header); | ||
|
||
let mut inner = pair.into_inner(); | ||
let name = self.parse_symbol(&mut inner)?; | ||
|
@@ -596,7 +594,7 @@ impl<'a> ParseContext<'a> { | |
} | ||
|
||
fn parse_op_header(&mut self, pair: Pair<'a, Rule>) -> ParseResult<&'a OperationDecl<'a>> { | ||
debug_assert!(matches!(pair.as_rule(), Rule::operation_header)); | ||
debug_assert_eq!(pair.as_rule(), Rule::operation_header); | ||
|
||
let mut inner = pair.into_inner(); | ||
let name = self.parse_symbol(&mut inner)?; | ||
|
@@ -670,7 +668,7 @@ impl<'a> ParseContext<'a> { | |
} | ||
|
||
fn parse_port(&mut self, pair: Pair<'a, Rule>) -> ParseResult<LinkRef<'a>> { | ||
debug_assert!(matches!(pair.as_rule(), Rule::port)); | ||
debug_assert_eq!(pair.as_rule(), Rule::port); | ||
let mut inner = pair.into_inner(); | ||
let link = LinkRef::Named(&inner.next().unwrap().as_str()[1..]); | ||
Ok(link) | ||
|
@@ -697,6 +695,47 @@ impl<'a> ParseContext<'a> { | |
unreachable!("expected a symbol"); | ||
} | ||
} | ||
|
||
fn parse_string(&self, token: Pair<'a, Rule>) -> ParseResult<&'a str> { | ||
debug_assert_eq!(token.as_rule(), Rule::string); | ||
|
||
// Any escape sequence is longer than the character it represents. | ||
// Therefore the length of this token (minus 2 for the quotes on either | ||
// side) is an upper bound for the length of the string. | ||
let capacity = token.as_str().len() - 2; | ||
let mut string = BumpString::with_capacity_in(capacity, self.bump); | ||
let tokens = token.into_inner(); | ||
|
||
for token in tokens { | ||
match token.as_rule() { | ||
Rule::string_raw => string.push_str(token.as_str()), | ||
Rule::string_escape => match token.as_str().chars().nth(1).unwrap() { | ||
'"' => string.push('"'), | ||
'\\' => string.push('\\'), | ||
'n' => string.push('\n'), | ||
'r' => string.push('\r'), | ||
't' => string.push('\t'), | ||
_ => unreachable!(), | ||
}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the criteria for allowed escapes? https://www.ietf.org/rfc/rfc4627.txt says any character can be escaped (i.e. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. https://www.ietf.org/rfc/rfc4627.txt describes how JSON does string escaping, which is not the only way. Note that we use the string to store JSON but the string is not itself a JSON string. These escape codes are closer to how Rust does escaping (which has a nicer unicode escape sequence). More precisely it's a subset of what Rust does (not deliberately). It's also a subset of what the webassembly text format does (which is very similar to Rust strings but not entirely the same). I don't care that much about which escaping system we use so this was picked rather arbitrarily; we could adjust it to taste if you wish. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is fine, I did not understand that the string is not itself a json string. |
||
Rule::string_unicode => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The way that the
So that case is indeed unreachable. This is similar to the other uses of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the explanation. I agree that the case is unreachable. Nevertheless it is better to throw an error here, because there are two sources-of-truth for allowed escapes, which may diverge over time. If you feel strongly feel free to leave this as is. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When these diverge we have a bug. A parse error would indicate that there's something wrong with the input, when in truth the error is in the code. So I think the panic is appropriate. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A panic aborts the program, it's very user hostile. We should aim to minimize the impact of bugs, e.g. throw an error here. |
||
let token_str = token.as_str(); | ||
debug_assert_eq!(&token_str[0..3], r"\u{"); | ||
debug_assert_eq!(&token_str[token_str.len() - 1..], "}"); | ||
let code_str = &token_str[3..token_str.len() - 1]; | ||
let code = u32::from_str_radix(code_str, 16).map_err(|_| { | ||
ParseError::custom("invalid unicode escape sequence", token.as_span()) | ||
})?; | ||
let char = std::char::from_u32(code).ok_or_else(|| { | ||
ParseError::custom("invalid unicode code point", token.as_span()) | ||
})?; | ||
string.push(char); | ||
} | ||
_ => unreachable!(), | ||
} | ||
} | ||
|
||
Ok(string.into_bump_str()) | ||
} | ||
} | ||
|
||
/// Draw from a pest pair iterator only the pairs that match a given rule. | ||
|
@@ -750,6 +789,16 @@ impl ParseError { | |
InputLocation::Span((offset, _)) => offset, | ||
} | ||
} | ||
|
||
fn custom(message: &str, span: pest::Span) -> Self { | ||
let error = pest::error::Error::new_from_span( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no coverage for this function There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see what to test about this function. It constructs an error. |
||
pest::error::ErrorVariant::CustomError { | ||
message: message.to_string(), | ||
}, | ||
span, | ||
); | ||
ParseError(Box::new(error)) | ||
} | ||
} | ||
|
||
// NOTE: `ParseError` does not implement `From<pest::error::Error<Rule>>` so that | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps theses should these be public? Downstream clients should not have to memorise the string.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At some point yes, and there should be some extension declarations that define these term constructors. I've not made the names public yet since they'll probably change as we move closer to stabilising.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Their propensity to change is the precise reason they should be public now.