Compare commits
25
Commits
v0.1.2
...
46b2b3b1b6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46b2b3b1b6 | ||
|
|
5a05da6fe4 | ||
|
|
aa0f464320 | ||
|
|
fc1f3e0b25 | ||
|
|
8eac5c5cb4 | ||
|
|
59cb8f31e4 | ||
|
|
4eee2cd2c9 | ||
|
|
44b6041e60 | ||
|
|
a95afe0463 | ||
|
|
43ab6244ef | ||
|
|
1d4503b015 | ||
|
|
250e29e00c | ||
|
|
1080081cec | ||
|
|
43f692e75c | ||
|
|
5b26d071ca | ||
|
|
a653f0b7c6 | ||
|
|
384d17b539 | ||
|
|
09a84264ab | ||
|
|
0a8c261545 | ||
|
|
a8a46998a8 | ||
|
|
e4a9f80617 | ||
|
|
0e2a7b35d2 | ||
|
|
ac99c4cb4e | ||
|
|
22f6bc1ab9 | ||
|
|
16006d000a |
Generated
+51
@@ -17,6 +17,16 @@ version = "3.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
@@ -48,6 +58,17 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "decodal-language-tools"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"decodal",
|
||||
"serde_json",
|
||||
"tree-sitter",
|
||||
"tree-sitter-decodal",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "decodal-wasm"
|
||||
version = "0.1.2"
|
||||
@@ -57,6 +78,12 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
@@ -170,6 +197,12 @@ dependencies = [
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
@@ -181,6 +214,24 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter"
|
||||
version = "0.22.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df7cc499ceadd4dcdf7ec6d4cbc34ece92c3fa07821e287aedecd4416c516dca"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-decodal"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
|
||||
@@ -4,6 +4,7 @@ members = [
|
||||
"crates/decodal-cli",
|
||||
"crates/decodal-wasm",
|
||||
"crates/decodal-derive",
|
||||
"crates/decodal-language-tools",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
|
||||
@@ -50,6 +50,9 @@ pub enum Expr {
|
||||
Ident(String),
|
||||
Object(Vec<Field>),
|
||||
Array(Vec<ExprId>),
|
||||
ArrayConstraint {
|
||||
item: ExprId,
|
||||
},
|
||||
Let {
|
||||
bindings: Vec<Field>,
|
||||
body: ExprId,
|
||||
|
||||
@@ -11,6 +11,7 @@ pub fn normalize_constraints(
|
||||
span: Span,
|
||||
) -> crate::Result<Vec<ConstraintEntry>> {
|
||||
let mut primitive: Option<(PrimitiveType, Span)> = None;
|
||||
let mut array_span: Option<Span> = None;
|
||||
let mut lower: Option<(Bound, Span)> = None;
|
||||
let mut upper: Option<(Bound, Span)> = None;
|
||||
let mut rest = Vec::new();
|
||||
@@ -61,6 +62,13 @@ pub fn normalize_constraints(
|
||||
constraint: Constraint::Regex(pattern),
|
||||
span: entry.span,
|
||||
}),
|
||||
Constraint::ArrayItems(item) => {
|
||||
array_span.get_or_insert(entry.span);
|
||||
rest.push(ConstraintEntry {
|
||||
constraint: Constraint::ArrayItems(item),
|
||||
span: entry.span,
|
||||
});
|
||||
}
|
||||
Constraint::BuiltinPredicate(name) => rest.push(ConstraintEntry {
|
||||
constraint: Constraint::BuiltinPredicate(name),
|
||||
span: entry.span,
|
||||
@@ -68,12 +76,37 @@ pub fn normalize_constraints(
|
||||
}
|
||||
}
|
||||
|
||||
if let (Some((_, primitive_span)), Some(array_span)) = (primitive, array_span) {
|
||||
return Err(Diagnostic::new(
|
||||
DiagnosticKind::Conflict,
|
||||
span,
|
||||
"array element constraints conflict with primitive type constraints",
|
||||
)
|
||||
.with_label(primitive_span, "primitive type constraint")
|
||||
.with_label(array_span, "array element constraint"));
|
||||
}
|
||||
|
||||
if let Some(array_span) = array_span
|
||||
&& (lower.is_some() || upper.is_some())
|
||||
{
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
DiagnosticKind::Conflict,
|
||||
span,
|
||||
"numeric comparison constraints conflict with array element constraints",
|
||||
)
|
||||
.with_label(array_span, "array element constraint");
|
||||
if let Some((_, lower_span)) = lower {
|
||||
diagnostic = diagnostic.with_label(lower_span, "numeric comparison constraint");
|
||||
}
|
||||
if let Some((_, upper_span)) = upper {
|
||||
diagnostic = diagnostic.with_label(upper_span, "numeric comparison constraint");
|
||||
}
|
||||
return Err(diagnostic);
|
||||
}
|
||||
|
||||
if matches!(
|
||||
primitive,
|
||||
Some((
|
||||
PrimitiveType::String | PrimitiveType::Bool | PrimitiveType::Array,
|
||||
_
|
||||
))
|
||||
Some((PrimitiveType::String | PrimitiveType::Bool, _))
|
||||
) && (lower.is_some() || upper.is_some())
|
||||
{
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
|
||||
@@ -10,6 +10,11 @@ pub enum HostValue {
|
||||
Float(f64),
|
||||
Bool(bool),
|
||||
Array(Vec<HostValue>),
|
||||
ArrayConstraint {
|
||||
item: Box<HostValue>,
|
||||
constraints: Vec<Constraint>,
|
||||
default: Option<Box<HostValue>>,
|
||||
},
|
||||
Object(Vec<HostField>),
|
||||
Abstract {
|
||||
constraints: Vec<Constraint>,
|
||||
@@ -91,8 +96,12 @@ impl HostValue {
|
||||
Self::abstract_with_constraint(Constraint::Type(PrimitiveType::Bool))
|
||||
}
|
||||
|
||||
pub fn array_type() -> Self {
|
||||
Self::abstract_with_constraint(Constraint::Type(PrimitiveType::Array))
|
||||
pub fn array_of(item: HostValue) -> Self {
|
||||
Self::ArrayConstraint {
|
||||
item: Box::new(item),
|
||||
constraints: Vec::new(),
|
||||
default: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn builtin_predicate(name: impl Into<String>) -> Self {
|
||||
@@ -109,6 +118,7 @@ impl HostValue {
|
||||
pub fn with_constraint(mut self, constraint: Constraint) -> Self {
|
||||
match &mut self {
|
||||
Self::Abstract { constraints, .. } => constraints.push(constraint),
|
||||
Self::ArrayConstraint { constraints, .. } => constraints.push(constraint),
|
||||
_ => {
|
||||
self = Self::Abstract {
|
||||
constraints: alloc::vec![constraint],
|
||||
@@ -143,6 +153,22 @@ impl HostValue {
|
||||
|
||||
pub fn default(self, value: HostValue) -> Result<Self> {
|
||||
match self {
|
||||
Self::ArrayConstraint {
|
||||
item,
|
||||
constraints,
|
||||
default: None,
|
||||
} => Ok(Self::ArrayConstraint {
|
||||
item,
|
||||
constraints,
|
||||
default: Some(Box::new(value)),
|
||||
}),
|
||||
Self::ArrayConstraint {
|
||||
default: Some(_), ..
|
||||
} => Err(Diagnostic::new(
|
||||
DiagnosticKind::DefaultConflict,
|
||||
Span::default(),
|
||||
"host value already has a default",
|
||||
)),
|
||||
Self::Abstract {
|
||||
constraints,
|
||||
default: None,
|
||||
|
||||
+234
-32
@@ -111,9 +111,12 @@ impl<L: SourceLoader> Engine<L> {
|
||||
ConcreteValue::Bool(value) => Ok(Data::Bool(*value)),
|
||||
ConcreteValue::Array(items) => {
|
||||
let mut data = Vec::new();
|
||||
for item in items {
|
||||
for (index, item) in items.iter().enumerate() {
|
||||
let value = self.force(*item)?;
|
||||
data.push(self.materialize_with_path(&value, path)?);
|
||||
path.push(format!("[{index}]"));
|
||||
let value = self.materialize_with_path(&value, path);
|
||||
path.pop();
|
||||
data.push(value?);
|
||||
}
|
||||
Ok(Data::Array(data))
|
||||
}
|
||||
@@ -158,13 +161,14 @@ impl<L: SourceLoader> Engine<L> {
|
||||
};
|
||||
let default_span = self.thunk_span(default);
|
||||
let value = self.force(default)?;
|
||||
self.ensure_satisfies(
|
||||
&value,
|
||||
&abstract_value.constraints,
|
||||
default_span,
|
||||
Some((default_span, "default value checked here")),
|
||||
)
|
||||
.map_err(|diag| self.with_path_context(diag, "materializing", path))?;
|
||||
let value = self
|
||||
.apply_constraints(
|
||||
value,
|
||||
&abstract_value.constraints,
|
||||
default_span,
|
||||
Some((default_span, "default value checked here")),
|
||||
)
|
||||
.map_err(|diag| self.with_path_context(diag, "materializing", path))?;
|
||||
self.materialize_with_path(&value, path)
|
||||
}
|
||||
}
|
||||
@@ -262,6 +266,22 @@ impl<L: SourceLoader> Engine<L> {
|
||||
.collect();
|
||||
Ok(RuntimeValue::Concrete(ConcreteValue::Array(thunks)))
|
||||
}
|
||||
Expr::ArrayConstraint { item } => {
|
||||
let item = self.add_expr_thunk(
|
||||
ExprRef {
|
||||
module: reference.module,
|
||||
expr: item,
|
||||
},
|
||||
env,
|
||||
);
|
||||
Ok(RuntimeValue::Abstract(AbstractValue {
|
||||
constraints: vec![ConstraintEntry {
|
||||
constraint: Constraint::ArrayItems(item),
|
||||
span,
|
||||
}],
|
||||
default: None,
|
||||
}))
|
||||
}
|
||||
Expr::Let { bindings, body } => {
|
||||
let let_env = self.new_env(Some(env));
|
||||
for binding in bindings {
|
||||
@@ -550,6 +570,13 @@ impl<L: SourceLoader> Engine<L> {
|
||||
if let Some(thunk) = self.lookup(env, name) {
|
||||
return self.force(thunk);
|
||||
}
|
||||
if name == "Array" {
|
||||
return Err(Diagnostic::new(
|
||||
DiagnosticKind::UnresolvedIdentifier,
|
||||
span,
|
||||
"`Array` is not a valid constraint; use an element constraint such as `[...String]`",
|
||||
));
|
||||
}
|
||||
if name.chars().next().is_some_and(char::is_uppercase) {
|
||||
return Ok(RuntimeValue::Abstract(AbstractValue {
|
||||
constraints: vec![ConstraintEntry {
|
||||
@@ -697,7 +724,10 @@ impl<L: SourceLoader> Engine<L> {
|
||||
) -> Result<bool> {
|
||||
match self.expr(pattern).clone() {
|
||||
Expr::Wildcard => Ok(true),
|
||||
Expr::CompareConstraint { .. } | Expr::RegexConstraint(_) | Expr::Ident(_) => {
|
||||
Expr::ArrayConstraint { .. }
|
||||
| Expr::CompareConstraint { .. }
|
||||
| Expr::RegexConstraint(_)
|
||||
| Expr::Ident(_) => {
|
||||
let constraint = self.eval_expr(pattern, env)?;
|
||||
match constraint {
|
||||
RuntimeValue::Abstract(abstract_value) => self
|
||||
@@ -738,13 +768,12 @@ impl<L: SourceLoader> Engine<L> {
|
||||
}
|
||||
(RuntimeValue::Abstract(abstract_value), concrete @ RuntimeValue::Concrete(_))
|
||||
| (concrete @ RuntimeValue::Concrete(_), RuntimeValue::Abstract(abstract_value)) => {
|
||||
self.ensure_satisfies(
|
||||
&concrete,
|
||||
self.apply_constraints(
|
||||
concrete,
|
||||
&abstract_value.constraints,
|
||||
span,
|
||||
Some((span, "concrete value being composed here")),
|
||||
)?;
|
||||
Ok(concrete)
|
||||
)
|
||||
}
|
||||
(
|
||||
RuntimeValue::Concrete(ConcreteValue::Object(lhs)),
|
||||
@@ -830,24 +859,35 @@ impl<L: SourceLoader> Engine<L> {
|
||||
span: Span,
|
||||
value_label: Option<(Span, &'static str)>,
|
||||
) -> Result<()> {
|
||||
for constraint in constraints {
|
||||
self.satisfies(value, constraint, span, value_label)?;
|
||||
}
|
||||
Ok(())
|
||||
self.apply_constraints(value.clone(), constraints, span, value_label)
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
fn satisfies(
|
||||
fn apply_constraints(
|
||||
&mut self,
|
||||
value: &RuntimeValue,
|
||||
mut value: RuntimeValue,
|
||||
constraints: &[ConstraintEntry],
|
||||
span: Span,
|
||||
value_label: Option<(Span, &'static str)>,
|
||||
) -> Result<RuntimeValue> {
|
||||
for constraint in constraints {
|
||||
value = self.apply_constraint(value, constraint, span, value_label)?;
|
||||
}
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
fn apply_constraint(
|
||||
&mut self,
|
||||
value: RuntimeValue,
|
||||
constraint: &ConstraintEntry,
|
||||
span: Span,
|
||||
value_label: Option<(Span, &'static str)>,
|
||||
) -> Result<()> {
|
||||
) -> Result<RuntimeValue> {
|
||||
let constraint_value = &constraint.constraint;
|
||||
match constraint_value {
|
||||
Constraint::Type(primitive) => {
|
||||
if value_matches_primitive(value, *primitive) {
|
||||
Ok(())
|
||||
if value_matches_primitive(&value, *primitive) {
|
||||
Ok(value)
|
||||
} else {
|
||||
Err(self.constraint_violation(
|
||||
constraint,
|
||||
@@ -857,8 +897,41 @@ impl<L: SourceLoader> Engine<L> {
|
||||
))
|
||||
}
|
||||
}
|
||||
Constraint::Compare(op, expected) => compare_value(value, *op, expected)
|
||||
.then_some(())
|
||||
Constraint::ArrayItems(item_constraint) => {
|
||||
let RuntimeValue::Concrete(ConcreteValue::Array(items)) = value else {
|
||||
return Err(self.constraint_violation(
|
||||
constraint,
|
||||
span,
|
||||
value_label,
|
||||
"value does not satisfy array element constraint",
|
||||
));
|
||||
};
|
||||
let item_constraint_value = self.force(*item_constraint)?;
|
||||
let mut constrained_items = Vec::with_capacity(items.len());
|
||||
for (index, item) in items.into_iter().enumerate() {
|
||||
let item_span = self.thunk_span(item);
|
||||
let item_value = self.force(item)?;
|
||||
let constrained = self
|
||||
.compose_and(item_constraint_value.clone(), item_value, item_span)
|
||||
.map_err(|diagnostic| {
|
||||
diagnostic
|
||||
.with_label(
|
||||
constraint.span,
|
||||
"array element constraint declared here",
|
||||
)
|
||||
.with_label(
|
||||
item_span,
|
||||
format!("array element [{index}] checked here"),
|
||||
)
|
||||
})?;
|
||||
constrained_items.push(self.add_value_thunk_with_span(constrained, item_span));
|
||||
}
|
||||
Ok(RuntimeValue::Concrete(ConcreteValue::Array(
|
||||
constrained_items,
|
||||
)))
|
||||
}
|
||||
Constraint::Compare(op, expected) => compare_value(&value, *op, expected)
|
||||
.then_some(value)
|
||||
.ok_or_else(|| {
|
||||
self.constraint_violation(
|
||||
constraint,
|
||||
@@ -868,14 +941,15 @@ impl<L: SourceLoader> Engine<L> {
|
||||
)
|
||||
}),
|
||||
Constraint::Regex(pattern) => {
|
||||
satisfies_regex(value, pattern, constraint.span).map_err(|diag| {
|
||||
satisfies_regex(&value, pattern, constraint.span).map_err(|diag| {
|
||||
let diag = diag.with_label(constraint.span, "regex constraint declared here");
|
||||
if let Some((span, message)) = value_label {
|
||||
diag.with_label(span, message)
|
||||
} else {
|
||||
diag
|
||||
}
|
||||
})
|
||||
})?;
|
||||
Ok(value)
|
||||
}
|
||||
Constraint::BuiltinPredicate(name) => Err(Diagnostic::new(
|
||||
DiagnosticKind::UnsupportedFeature,
|
||||
@@ -940,6 +1014,33 @@ impl<L: SourceLoader> Engine<L> {
|
||||
}
|
||||
Ok(RuntimeValue::Concrete(ConcreteValue::Array(thunks)))
|
||||
}
|
||||
HostValue::ArrayConstraint {
|
||||
item,
|
||||
mut constraints,
|
||||
default,
|
||||
} => {
|
||||
let item = self.internalize_host_value(*item)?;
|
||||
let item = self.add_value_thunk(item);
|
||||
let default = if let Some(default) = default {
|
||||
let value = self.internalize_host_value(*default)?;
|
||||
Some(self.add_value_thunk(value))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
constraints.push(Constraint::ArrayItems(item));
|
||||
let constraints = constraints
|
||||
.into_iter()
|
||||
.map(|constraint| ConstraintEntry {
|
||||
constraint,
|
||||
span: Span::default(),
|
||||
})
|
||||
.collect();
|
||||
let constraints = normalize_constraints(constraints, Span::default())?;
|
||||
Ok(RuntimeValue::Abstract(AbstractValue {
|
||||
constraints,
|
||||
default,
|
||||
}))
|
||||
}
|
||||
HostValue::Object(fields) => {
|
||||
let mut object = ObjectValue { fields: Vec::new() };
|
||||
for field in fields {
|
||||
@@ -1147,7 +1248,7 @@ impl<L: SourceLoader> Engine<L> {
|
||||
diagnostic
|
||||
} else {
|
||||
let span = diagnostic.span;
|
||||
diagnostic.with_label(span, format!("while {phase} `{}`", path.join(".")))
|
||||
diagnostic.with_label(span, format!("while {phase} `{}`", format_path(path)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1170,7 +1271,6 @@ fn primitive_type(name: &str) -> Option<PrimitiveType> {
|
||||
"Int" => Some(PrimitiveType::Int),
|
||||
"Float" => Some(PrimitiveType::Float),
|
||||
"Bool" => Some(PrimitiveType::Bool),
|
||||
"Array" => Some(PrimitiveType::Array),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -1251,13 +1351,25 @@ fn value_matches_primitive(value: &RuntimeValue, primitive: PrimitiveType) -> bo
|
||||
) | (
|
||||
RuntimeValue::Concrete(ConcreteValue::Bool(_)),
|
||||
PrimitiveType::Bool
|
||||
) | (
|
||||
RuntimeValue::Concrete(ConcreteValue::Array(_)),
|
||||
PrimitiveType::Array
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fn format_path(path: &[String]) -> String {
|
||||
let mut output = String::new();
|
||||
for part in path {
|
||||
if part.starts_with('[') {
|
||||
output.push_str(part);
|
||||
} else {
|
||||
if !output.is_empty() {
|
||||
output.push('.');
|
||||
}
|
||||
output.push_str(part);
|
||||
}
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
#[cfg(feature = "regex")]
|
||||
fn satisfies_regex(value: &RuntimeValue, pattern: &str, span: Span) -> Result<()> {
|
||||
let RuntimeValue::Concrete(ConcreteValue::String(value)) = value else {
|
||||
@@ -1678,6 +1790,96 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_constraint_accepts_matching_items() {
|
||||
let data = eval_data("[...(Int & >= 1)] & [1, 2, 3]");
|
||||
assert_eq!(
|
||||
data,
|
||||
Data::Array(vec![Data::Int(1), Data::Int(2), Data::Int(3)])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_constraint_accepts_empty_array() {
|
||||
let data = eval_data("[...Int] & []");
|
||||
assert_eq!(data, Data::Array(vec![]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_constraints_compose_per_item() {
|
||||
let data = eval_data("[...Int] & [...> 0] & [1, 2, 3]");
|
||||
assert_eq!(
|
||||
data,
|
||||
Data::Array(vec![Data::Int(1), Data::Int(2), Data::Int(3)])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_array_constraints_validate_recursively() {
|
||||
let data = eval_data(r#"[...[...String]] & [["api"], [], ["worker"]]"#);
|
||||
assert_eq!(
|
||||
data,
|
||||
Data::Array(vec![
|
||||
Data::Array(vec![Data::String(String::from("api"))]),
|
||||
Data::Array(vec![]),
|
||||
Data::Array(vec![Data::String(String::from("worker"))]),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_constraint_rejects_invalid_item_with_index() {
|
||||
let parsed = parse_source("[...Int] & [1, \"two\", 3]").unwrap();
|
||||
let mut engine = Engine::from_parse(parsed.ast, parsed.root);
|
||||
let error = engine.eval_root().unwrap_err();
|
||||
assert_eq!(error.kind, DiagnosticKind::ConstraintViolation);
|
||||
assert!(
|
||||
error
|
||||
.labels
|
||||
.iter()
|
||||
.any(|label| label.message.contains("array element [1]"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_constraint_applies_object_defaults() {
|
||||
let data = eval_data(
|
||||
r#"
|
||||
[...{
|
||||
name = String;
|
||||
enabled = Bool default true;
|
||||
}] & [{ name = "api"; }]
|
||||
"#,
|
||||
);
|
||||
let Data::Array(items) = data else { panic!() };
|
||||
let Data::Object(fields) = &items[0] else {
|
||||
panic!()
|
||||
};
|
||||
assert_eq!(fields[0].value, Data::String(String::from("api")));
|
||||
assert_eq!(fields[1].value, Data::Bool(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_constraint_validates_default_array() {
|
||||
let data = eval_data("[...String] default [\"api\", \"worker\"]");
|
||||
assert_eq!(
|
||||
data,
|
||||
Data::Array(vec![
|
||||
Data::String(String::from("api")),
|
||||
Data::String(String::from("worker")),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn removed_array_primitive_reports_migration() {
|
||||
let parsed = parse_source("Array").unwrap();
|
||||
let mut engine = Engine::from_parse(parsed.ast, parsed.root);
|
||||
let error = engine.eval_root().unwrap_err();
|
||||
assert_eq!(error.kind, DiagnosticKind::UnresolvedIdentifier);
|
||||
assert!(error.message.contains("[...String]"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_concat_has_lower_precedence_than_arithmetic() {
|
||||
let data = eval_data("[1 + 1] ++ [2 * 2]");
|
||||
|
||||
@@ -32,6 +32,7 @@ pub enum TokenKind {
|
||||
Semicolon,
|
||||
Comma,
|
||||
Dot,
|
||||
Ellipsis,
|
||||
Colon,
|
||||
Equal,
|
||||
EqualEqual,
|
||||
@@ -141,7 +142,18 @@ impl<'a> Lexer<'a> {
|
||||
}
|
||||
b'.' => {
|
||||
self.pos += 1;
|
||||
TokenKind::Dot
|
||||
if self.consume(b'.') {
|
||||
if self.consume(b'.') {
|
||||
TokenKind::Ellipsis
|
||||
} else {
|
||||
return Err(Diagnostic::syntax(
|
||||
self.span(start, self.pos),
|
||||
"expected third '.' in ellipsis",
|
||||
));
|
||||
}
|
||||
} else {
|
||||
TokenKind::Dot
|
||||
}
|
||||
}
|
||||
b':' => {
|
||||
self.pos += 1;
|
||||
@@ -432,4 +444,13 @@ mod tests {
|
||||
assert_eq!(tokens[3].kind, TokenKind::Amp);
|
||||
assert_eq!(tokens[4].kind, TokenKind::Gte);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tokenizes_array_constraint_ellipsis() {
|
||||
let tokens = Lexer::new("[...String]").tokenize().unwrap();
|
||||
assert_eq!(tokens[0].kind, TokenKind::LBracket);
|
||||
assert_eq!(tokens[1].kind, TokenKind::Ellipsis);
|
||||
assert!(matches!(tokens[2].kind, TokenKind::Ident(_)));
|
||||
assert_eq!(tokens[3].kind, TokenKind::RBracket);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,6 +338,17 @@ impl Parser {
|
||||
}
|
||||
|
||||
fn parse_array_after_lbracket(&mut self, start_span: Span) -> Result<ExprId> {
|
||||
if self.consume_kind(&TokenKind::Ellipsis).is_some() {
|
||||
let item = self.parse_expr(0)?;
|
||||
let _ = self.consume_kind(&TokenKind::Comma);
|
||||
self.expect_kind(
|
||||
&TokenKind::RBracket,
|
||||
"expected ']' after array element constraint",
|
||||
)?;
|
||||
let span = start_span.join(self.previous_span());
|
||||
return Ok(self.ast.push(Expr::ArrayConstraint { item }, span));
|
||||
}
|
||||
|
||||
let mut items = Vec::new();
|
||||
if self.consume_kind(&TokenKind::RBracket).is_some() {
|
||||
return Ok(self
|
||||
@@ -674,4 +685,18 @@ mod tests {
|
||||
};
|
||||
assert_eq!(fields[0].path, ["feature", "enable"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_array_constraint() {
|
||||
let parsed = parse_source("[...(String & /^api-/)]").unwrap();
|
||||
let Expr::ArrayConstraint { item } = parsed.ast.get(parsed.root).expr else {
|
||||
panic!()
|
||||
};
|
||||
assert!(matches!(parsed.ast.get(item).expr, Expr::Binary { .. }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_array_constraint_without_element_constraint() {
|
||||
assert!(parse_source("[...]").is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ pub struct ConstraintEntry {
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Constraint {
|
||||
Type(PrimitiveType),
|
||||
ArrayItems(ThunkId),
|
||||
Compare(CompareOp, LiteralValue),
|
||||
Regex(String),
|
||||
BuiltinPredicate(String),
|
||||
@@ -79,7 +80,6 @@ pub enum PrimitiveType {
|
||||
Int,
|
||||
Float,
|
||||
Bool,
|
||||
Array,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
|
||||
@@ -198,9 +198,9 @@ impl IntoHostValue for bool {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DecodalSchema for Vec<T> {
|
||||
impl<T: DecodalSchema> DecodalSchema for Vec<T> {
|
||||
fn decodal_schema() -> HostValue {
|
||||
HostValue::array_type()
|
||||
HostValue::array_of(T::decodal_schema())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,18 @@ struct Service {
|
||||
tags: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Decodal)]
|
||||
struct Worker {
|
||||
name: String,
|
||||
#[decodal(default = true)]
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Decodal)]
|
||||
struct Fleet {
|
||||
workers: Vec<Worker>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derives_schema_and_decode() {
|
||||
let mut engine = Engine::new(EmptyLoader);
|
||||
@@ -77,3 +89,62 @@ fn decode_reports_field_path() {
|
||||
let error = Service::decodal_decode(&data).unwrap_err();
|
||||
assert_eq!(error.path, "name");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vec_schema_rejects_invalid_element_before_decode() {
|
||||
let mut engine = Engine::new(EmptyLoader);
|
||||
engine
|
||||
.bind_global("Service", Service::decodal_schema())
|
||||
.unwrap();
|
||||
let module = engine
|
||||
.add_root_source(
|
||||
"test",
|
||||
"test",
|
||||
r#"
|
||||
Service & {
|
||||
name = "api";
|
||||
port = 9443;
|
||||
tags = ["web", 1];
|
||||
}
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
let error = engine.eval_module(module).unwrap_err();
|
||||
assert!(
|
||||
error
|
||||
.labels
|
||||
.iter()
|
||||
.any(|label| label.message.contains("array element [1]"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vec_schema_applies_nested_struct_defaults() {
|
||||
let mut engine = Engine::new(EmptyLoader);
|
||||
engine
|
||||
.bind_global("Fleet", Fleet::decodal_schema())
|
||||
.unwrap();
|
||||
let module = engine
|
||||
.add_root_source(
|
||||
"test",
|
||||
"test",
|
||||
r#"
|
||||
Fleet & {
|
||||
workers = [{ name = "api"; }];
|
||||
}
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
let value = engine.eval_module(module).unwrap();
|
||||
let data = engine.materialize(&value).unwrap();
|
||||
let fleet = Fleet::decodal_decode(&data).unwrap();
|
||||
assert_eq!(
|
||||
fleet,
|
||||
Fleet {
|
||||
workers: vec![Worker {
|
||||
name: "api".into(),
|
||||
enabled: true,
|
||||
}],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "decodal-language-tools"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
readme.workspace = true
|
||||
description = "Source-level language tooling for Decodal."
|
||||
keywords = ["decodal", "formatter", "lsp", "tree-sitter"]
|
||||
categories = ["development-tools", "text-processing"]
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
decodal = { version = "0.1.2", path = "../decodal-core" }
|
||||
serde_json.workspace = true
|
||||
tree-sitter = "0.22.6"
|
||||
tree-sitter-decodal = { path = "../../editors/tree-sitter-decodal" }
|
||||
wasm-bindgen.workspace = true
|
||||
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
wasm-opt = false
|
||||
@@ -0,0 +1,870 @@
|
||||
use std::{error::Error, fmt};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tree_sitter::{Node, Parser, TreeCursor};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
const INDENT: usize = 4;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct FormatError {
|
||||
message: String,
|
||||
}
|
||||
|
||||
impl FormatError {
|
||||
fn new(message: impl Into<String>) -> Self {
|
||||
Self {
|
||||
message: message.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn message(&self) -> &str {
|
||||
&self.message
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for FormatError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(&self.message)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for FormatError {}
|
||||
|
||||
pub fn format_source(source: &str) -> Result<String, FormatError> {
|
||||
decodal::parse_source(source)
|
||||
.map_err(|diagnostic| FormatError::new(format!("parse error: {}", diagnostic.message)))?;
|
||||
format_source_impl(source)
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn format_source_impl(source: &str) -> Result<String, FormatError> {
|
||||
let mut parser = Parser::new();
|
||||
parser
|
||||
.set_language(&tree_sitter_decodal::language())
|
||||
.map_err(|error| {
|
||||
FormatError::new(format!("failed to load tree-sitter grammar: {error}"))
|
||||
})?;
|
||||
let tree = parser
|
||||
.parse(source, None)
|
||||
.ok_or_else(|| FormatError::new("tree-sitter failed to parse source"))?;
|
||||
let root = tree.root_node();
|
||||
if root.has_error() {
|
||||
return Err(FormatError::new(
|
||||
"parse error: source contains tree-sitter errors",
|
||||
));
|
||||
}
|
||||
|
||||
let mut formatter = Formatter::new(source);
|
||||
let mut out = formatter.format_source_file(root);
|
||||
if !out.ends_with('\n') {
|
||||
out.push('\n');
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = formatSource)]
|
||||
pub fn format_source_json(source: &str) -> String {
|
||||
match format_source(source) {
|
||||
Ok(formatted) => serde_json::json!({ "ok": true, "source": formatted }).to_string(),
|
||||
Err(error) => serde_json::json!({ "ok": false, "error": error.message() }).to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn format_source_impl(source: &str) -> Result<String, FormatError> {
|
||||
Ok(LexicalFormatter::new(source).format())
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
struct LexicalFormatter<'a> {
|
||||
chars: core::iter::Peekable<core::str::Chars<'a>>,
|
||||
out: String,
|
||||
indent: usize,
|
||||
line_start: bool,
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
impl<'a> LexicalFormatter<'a> {
|
||||
fn new(source: &'a str) -> Self {
|
||||
Self {
|
||||
chars: source.chars().peekable(),
|
||||
out: String::new(),
|
||||
indent: 0,
|
||||
line_start: true,
|
||||
}
|
||||
}
|
||||
|
||||
fn format(mut self) -> String {
|
||||
while let Some(ch) = self.chars.next() {
|
||||
match ch {
|
||||
' ' | '\t' | '\r' | '\n' => self.consume_whitespace(ch),
|
||||
'#' => self.write_comment(),
|
||||
'"' => self.write_string(),
|
||||
'{' => self.open_block('{'),
|
||||
'[' => self.open_block('['),
|
||||
'}' => self.close_block('}'),
|
||||
']' => self.close_block(']'),
|
||||
';' => self.end_statement(),
|
||||
',' => self.write_comma(),
|
||||
'.' => self.write_compact('.'),
|
||||
':' => self.write_spaced_operator(":"),
|
||||
'(' => self.write_compact('('),
|
||||
')' => self.write_compact(')'),
|
||||
'+' | '-' | '*' | '/' | '=' | '!' | '&' | '|' | '>' | '<' => {
|
||||
self.write_operator(ch)
|
||||
}
|
||||
_ => self.write_word(ch),
|
||||
}
|
||||
}
|
||||
self.trim_trailing_spaces();
|
||||
if !self.out.ends_with('\n') {
|
||||
self.out.push('\n');
|
||||
}
|
||||
self.out
|
||||
}
|
||||
|
||||
fn consume_whitespace(&mut self, ch: char) {
|
||||
if ch == '\n' && self.out.ends_with('\n') && !self.out.ends_with("\n\n") {
|
||||
self.out.push('\n');
|
||||
self.line_start = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn write_comment(&mut self) {
|
||||
if self.line_start {
|
||||
self.write_indent();
|
||||
} else {
|
||||
self.ensure_space();
|
||||
}
|
||||
self.out.push('#');
|
||||
for ch in self.chars.by_ref() {
|
||||
if ch == '\n' {
|
||||
break;
|
||||
}
|
||||
self.out.push(ch);
|
||||
}
|
||||
self.trim_trailing_spaces();
|
||||
self.out.push('\n');
|
||||
self.line_start = true;
|
||||
}
|
||||
|
||||
fn write_string(&mut self) {
|
||||
self.write_indent_if_needed();
|
||||
self.ensure_word_boundary();
|
||||
self.out.push('"');
|
||||
let mut escaped = false;
|
||||
for ch in self.chars.by_ref() {
|
||||
self.out.push(ch);
|
||||
if escaped {
|
||||
escaped = false;
|
||||
} else if ch == '\\' {
|
||||
escaped = true;
|
||||
} else if ch == '"' {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn open_block(&mut self, ch: char) {
|
||||
self.trim_trailing_spaces();
|
||||
self.out.push(' ');
|
||||
self.out.push(ch);
|
||||
self.out.push('\n');
|
||||
self.indent += INDENT;
|
||||
self.line_start = true;
|
||||
}
|
||||
|
||||
fn close_block(&mut self, ch: char) {
|
||||
self.trim_trailing_spaces();
|
||||
if !self.out.ends_with('\n') {
|
||||
self.out.push('\n');
|
||||
}
|
||||
self.indent = self.indent.saturating_sub(INDENT);
|
||||
self.write_indent();
|
||||
self.out.push(ch);
|
||||
self.line_start = false;
|
||||
}
|
||||
|
||||
fn end_statement(&mut self) {
|
||||
self.trim_trailing_spaces();
|
||||
self.out.push(';');
|
||||
self.out.push('\n');
|
||||
self.line_start = true;
|
||||
}
|
||||
|
||||
fn write_comma(&mut self) {
|
||||
self.trim_trailing_spaces();
|
||||
self.out.push(',');
|
||||
self.out.push(' ');
|
||||
self.line_start = false;
|
||||
}
|
||||
|
||||
fn write_operator(&mut self, ch: char) {
|
||||
let mut op = String::new();
|
||||
op.push(ch);
|
||||
if let Some(next) = self.chars.peek().copied() {
|
||||
let two_char = matches!(
|
||||
(ch, next),
|
||||
('=', '=')
|
||||
| ('!', '=')
|
||||
| ('>', '=')
|
||||
| ('<', '=')
|
||||
| ('+', '+')
|
||||
| ('/', '/')
|
||||
| ('&', '&')
|
||||
| ('|', '|')
|
||||
| ('=', '>')
|
||||
);
|
||||
if two_char {
|
||||
op.push(next);
|
||||
self.chars.next();
|
||||
}
|
||||
}
|
||||
self.write_spaced_operator(&op);
|
||||
}
|
||||
|
||||
fn write_spaced_operator(&mut self, op: &str) {
|
||||
self.trim_trailing_spaces();
|
||||
self.ensure_space();
|
||||
self.out.push_str(op);
|
||||
self.out.push(' ');
|
||||
self.line_start = false;
|
||||
}
|
||||
|
||||
fn write_compact(&mut self, ch: char) {
|
||||
self.trim_trailing_spaces();
|
||||
self.write_indent_if_needed();
|
||||
self.out.push(ch);
|
||||
self.line_start = false;
|
||||
}
|
||||
|
||||
fn write_word(&mut self, first: char) {
|
||||
self.write_indent_if_needed();
|
||||
self.ensure_word_boundary();
|
||||
self.out.push(first);
|
||||
while let Some(next) = self.chars.peek().copied() {
|
||||
if next.is_alphanumeric() || next == '_' {
|
||||
self.out.push(next);
|
||||
self.chars.next();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.line_start = false;
|
||||
}
|
||||
|
||||
fn write_indent_if_needed(&mut self) {
|
||||
if self.line_start {
|
||||
self.write_indent();
|
||||
self.line_start = false;
|
||||
}
|
||||
}
|
||||
|
||||
fn write_indent(&mut self) {
|
||||
for _ in 0..self.indent {
|
||||
self.out.push(' ');
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_word_boundary(&mut self) {
|
||||
let needs_space = self
|
||||
.out
|
||||
.chars()
|
||||
.last()
|
||||
.is_some_and(|ch| ch.is_alphanumeric() || matches!(ch, '_' | '"'));
|
||||
if needs_space {
|
||||
self.out.push(' ');
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_space(&mut self) {
|
||||
let needs_space = self
|
||||
.out
|
||||
.chars()
|
||||
.last()
|
||||
.is_some_and(|ch| !matches!(ch, ' ' | '\n' | '.' | '(' | '['));
|
||||
if needs_space {
|
||||
self.out.push(' ');
|
||||
}
|
||||
}
|
||||
|
||||
fn trim_trailing_spaces(&mut self) {
|
||||
while self.out.ends_with(' ') || self.out.ends_with('\t') {
|
||||
self.out.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
struct Formatter<'a> {
|
||||
source: &'a str,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl<'a> Formatter<'a> {
|
||||
fn new(source: &'a str) -> Self {
|
||||
Self { source }
|
||||
}
|
||||
|
||||
fn format_source_file(&mut self, node: Node<'a>) -> String {
|
||||
let mut out = String::new();
|
||||
let children = named_children(node);
|
||||
self.write_statement_list(&mut out, &children, 0);
|
||||
out
|
||||
}
|
||||
|
||||
fn write_statement_list(&mut self, out: &mut String, children: &[Node<'a>], indent: usize) {
|
||||
let mut pending_comments = Vec::new();
|
||||
let mut pending_blank = false;
|
||||
let mut previous_end = 0;
|
||||
let mut previous_statement_end_row = None;
|
||||
|
||||
for &child in children {
|
||||
let is_trailing_comment = child.kind() == "comment"
|
||||
&& previous_statement_end_row == Some(child.start_position().row);
|
||||
if previous_statement_end_row.is_some() && !is_trailing_comment {
|
||||
if !out.ends_with('\n') {
|
||||
out.push('\n');
|
||||
}
|
||||
previous_statement_end_row = None;
|
||||
}
|
||||
|
||||
if previous_end > 0 && has_blank_line(self.slice(previous_end, child.start_byte())) {
|
||||
pending_blank = true;
|
||||
}
|
||||
|
||||
if child.kind() == "comment" {
|
||||
if is_trailing_comment {
|
||||
if !out.ends_with(' ') {
|
||||
out.push(' ');
|
||||
}
|
||||
out.push_str(self.raw_trimmed(child));
|
||||
out.push('\n');
|
||||
previous_statement_end_row = None;
|
||||
} else {
|
||||
pending_comments.push(child);
|
||||
}
|
||||
previous_end = child.end_byte();
|
||||
continue;
|
||||
}
|
||||
|
||||
if pending_blank && !out.is_empty() && !out.ends_with("\n\n") {
|
||||
out.push('\n');
|
||||
}
|
||||
pending_blank = false;
|
||||
|
||||
for comment in pending_comments.drain(..) {
|
||||
write_indent(out, indent);
|
||||
out.push_str(self.raw_trimmed(comment));
|
||||
out.push('\n');
|
||||
}
|
||||
|
||||
write_indent(out, indent);
|
||||
self.write_statement(out, child, indent);
|
||||
previous_statement_end_row = Some(child.end_position().row);
|
||||
previous_end = child.end_byte();
|
||||
}
|
||||
|
||||
if previous_statement_end_row.is_some() && !out.ends_with('\n') {
|
||||
out.push('\n');
|
||||
}
|
||||
|
||||
if pending_blank && !out.is_empty() && !out.ends_with("\n\n") {
|
||||
out.push('\n');
|
||||
}
|
||||
for comment in pending_comments {
|
||||
write_indent(out, indent);
|
||||
out.push_str(self.raw_trimmed(comment));
|
||||
out.push('\n');
|
||||
}
|
||||
}
|
||||
|
||||
fn write_statement(&mut self, out: &mut String, node: Node<'a>, indent: usize) {
|
||||
match node.kind() {
|
||||
"field_definition" => {
|
||||
self.write_field_definition(out, node, indent);
|
||||
out.push(';');
|
||||
}
|
||||
_ => {
|
||||
self.write_expr(out, node, indent, 0);
|
||||
out.push(';');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_field_definition(&mut self, out: &mut String, node: Node<'a>, indent: usize) {
|
||||
if let Some(path) = node.child_by_field_name("path") {
|
||||
self.write_field_path(out, path);
|
||||
} else {
|
||||
out.push_str(self.raw_trimmed(node));
|
||||
return;
|
||||
}
|
||||
out.push_str(" = ");
|
||||
if let Some(value) = node.child_by_field_name("value") {
|
||||
self.write_expr(out, value, indent, 0);
|
||||
}
|
||||
}
|
||||
|
||||
fn write_field_path(&mut self, out: &mut String, node: Node<'a>) {
|
||||
let identifiers: Vec<_> = named_children(node)
|
||||
.into_iter()
|
||||
.filter(|child| child.kind() == "identifier")
|
||||
.collect();
|
||||
if identifiers.is_empty() {
|
||||
out.push_str(self.raw_trimmed(node));
|
||||
return;
|
||||
}
|
||||
for (index, identifier) in identifiers.into_iter().enumerate() {
|
||||
if index > 0 {
|
||||
out.push('.');
|
||||
}
|
||||
out.push_str(self.raw_trimmed(identifier));
|
||||
}
|
||||
}
|
||||
|
||||
fn write_expr(&mut self, out: &mut String, node: Node<'a>, indent: usize, parent_prec: u8) {
|
||||
if expression_contains_comment(node)
|
||||
&& !matches!(
|
||||
node.kind(),
|
||||
"object" | "array" | "array_constraint" | "let_expression" | "match_expression"
|
||||
)
|
||||
{
|
||||
out.push_str(self.raw_trimmed(node));
|
||||
return;
|
||||
}
|
||||
|
||||
let prec = self.precedence(node);
|
||||
let parenthesize = prec < parent_prec;
|
||||
if parenthesize {
|
||||
out.push('(');
|
||||
}
|
||||
|
||||
match node.kind() {
|
||||
"literal" => {
|
||||
if let Some(child) = named_children(node).first().copied() {
|
||||
out.push_str(self.raw_trimmed(child));
|
||||
} else {
|
||||
out.push_str(self.raw_trimmed(node));
|
||||
}
|
||||
}
|
||||
"identifier" | "string" | "integer" | "float" | "boolean" | "regex_literal" => {
|
||||
out.push_str(self.raw_trimmed(node));
|
||||
}
|
||||
"comparison_constraint" => self.write_comparison_constraint(out, node, indent),
|
||||
"object" => self.write_object(out, node, indent),
|
||||
"array" => self.write_array(out, node, indent),
|
||||
"array_constraint" => self.write_array_constraint(out, node, indent),
|
||||
"let_expression" => self.write_let(out, node, indent),
|
||||
"function_expression" => self.write_function(out, node, indent),
|
||||
"match_expression" => self.write_match(out, node, indent),
|
||||
"import_expression" => self.write_import(out, node),
|
||||
"parenthesized_expression" => {
|
||||
out.push('(');
|
||||
if let Some(expr) = named_children(node)
|
||||
.into_iter()
|
||||
.find(|child| child.kind() != "comment")
|
||||
{
|
||||
self.write_expr(out, expr, indent, 0);
|
||||
}
|
||||
out.push(')');
|
||||
}
|
||||
"call_expression" => self.write_call(out, node, indent),
|
||||
"path_expression" => self.write_path(out, node, indent),
|
||||
"unary_expression" => self.write_unary(out, node, indent),
|
||||
"binary_expression" | "default_expression" => {
|
||||
self.write_binary_like(out, node, indent, prec)
|
||||
}
|
||||
_ => out.push_str(self.raw_trimmed(node)),
|
||||
}
|
||||
|
||||
if parenthesize {
|
||||
out.push(')');
|
||||
}
|
||||
}
|
||||
|
||||
fn write_object(&mut self, out: &mut String, node: Node<'a>, indent: usize) {
|
||||
let children = named_children(node);
|
||||
if children.is_empty() {
|
||||
out.push_str("{}");
|
||||
return;
|
||||
}
|
||||
out.push('{');
|
||||
out.push('\n');
|
||||
self.write_statement_list(out, &children, indent + INDENT);
|
||||
write_indent(out, indent);
|
||||
out.push('}');
|
||||
}
|
||||
|
||||
fn write_array(&mut self, out: &mut String, node: Node<'a>, indent: usize) {
|
||||
let children = named_children(node);
|
||||
if children.is_empty() {
|
||||
out.push_str("[]");
|
||||
return;
|
||||
}
|
||||
let has_comment = children.iter().any(|child| child.kind() == "comment");
|
||||
let inline = !has_comment && children.iter().all(|child| is_inline_expr(*child));
|
||||
if inline {
|
||||
out.push('[');
|
||||
for (index, child) in children.into_iter().enumerate() {
|
||||
if index > 0 {
|
||||
out.push_str(", ");
|
||||
}
|
||||
self.write_expr(out, child, indent, 0);
|
||||
}
|
||||
out.push(']');
|
||||
return;
|
||||
}
|
||||
|
||||
out.push('[');
|
||||
out.push('\n');
|
||||
let mut pending_comments = Vec::new();
|
||||
for child in children {
|
||||
if child.kind() == "comment" {
|
||||
pending_comments.push(child);
|
||||
continue;
|
||||
}
|
||||
for comment in pending_comments.drain(..) {
|
||||
write_indent(out, indent + INDENT);
|
||||
out.push_str(self.raw_trimmed(comment));
|
||||
out.push('\n');
|
||||
}
|
||||
write_indent(out, indent + INDENT);
|
||||
self.write_expr(out, child, indent + INDENT, 0);
|
||||
out.push_str(",\n");
|
||||
}
|
||||
for comment in pending_comments {
|
||||
write_indent(out, indent + INDENT);
|
||||
out.push_str(self.raw_trimmed(comment));
|
||||
out.push('\n');
|
||||
}
|
||||
write_indent(out, indent);
|
||||
out.push(']');
|
||||
}
|
||||
|
||||
fn write_array_constraint(&mut self, out: &mut String, node: Node<'a>, indent: usize) {
|
||||
out.push_str("[...");
|
||||
if let Some(element) = node.child_by_field_name("element") {
|
||||
self.write_expr(out, element, indent, 0);
|
||||
}
|
||||
out.push(']');
|
||||
}
|
||||
|
||||
fn write_let(&mut self, out: &mut String, node: Node<'a>, indent: usize) {
|
||||
let body = node.child_by_field_name("body");
|
||||
out.push_str("let");
|
||||
out.push('\n');
|
||||
let children: Vec<_> = named_children(node)
|
||||
.into_iter()
|
||||
.filter(|child| Some(*child) != body)
|
||||
.collect();
|
||||
self.write_statement_list(out, &children, indent + INDENT);
|
||||
write_indent(out, indent);
|
||||
out.push_str("in");
|
||||
if let Some(body) = body {
|
||||
out.push('\n');
|
||||
write_indent(out, indent + INDENT);
|
||||
self.write_expr(out, body, indent + INDENT, 0);
|
||||
}
|
||||
}
|
||||
|
||||
fn write_function(&mut self, out: &mut String, node: Node<'a>, indent: usize) {
|
||||
let body = node.child_by_field_name("body");
|
||||
let params: Vec<_> = named_children(node)
|
||||
.into_iter()
|
||||
.filter(|child| child.kind() == "parameter")
|
||||
.collect();
|
||||
out.push('(');
|
||||
for (index, param) in params.into_iter().enumerate() {
|
||||
if index > 0 {
|
||||
out.push_str(", ");
|
||||
}
|
||||
self.write_parameter(out, param, indent);
|
||||
}
|
||||
out.push_str(") =>");
|
||||
if let Some(body) = body {
|
||||
if is_inline_expr(body) {
|
||||
out.push(' ');
|
||||
self.write_expr(out, body, indent, 0);
|
||||
} else {
|
||||
out.push('\n');
|
||||
write_indent(out, indent + INDENT);
|
||||
self.write_expr(out, body, indent + INDENT, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_parameter(&mut self, out: &mut String, node: Node<'a>, indent: usize) {
|
||||
if let Some(name) = node.child_by_field_name("name") {
|
||||
out.push_str(self.raw_trimmed(name));
|
||||
}
|
||||
if let Some(constraint) = node.child_by_field_name("constraint") {
|
||||
out.push_str(": ");
|
||||
self.write_expr(out, constraint, indent, 0);
|
||||
}
|
||||
}
|
||||
|
||||
fn write_match(&mut self, out: &mut String, node: Node<'a>, indent: usize) {
|
||||
out.push_str("match ");
|
||||
if let Some(scrutinee) = node.child_by_field_name("scrutinee") {
|
||||
self.write_expr(out, scrutinee, indent, 0);
|
||||
}
|
||||
out.push_str(" {");
|
||||
let children: Vec<_> = named_children(node)
|
||||
.into_iter()
|
||||
.filter(|child| child.kind() == "match_arm" || child.kind() == "comment")
|
||||
.collect();
|
||||
if !children.is_empty() {
|
||||
out.push('\n');
|
||||
self.write_match_arms(out, &children, indent + INDENT);
|
||||
write_indent(out, indent);
|
||||
}
|
||||
out.push('}');
|
||||
}
|
||||
|
||||
fn write_match_arms(&mut self, out: &mut String, children: &[Node<'a>], indent: usize) {
|
||||
let mut pending_comments = Vec::new();
|
||||
for &child in children {
|
||||
if child.kind() == "comment" {
|
||||
pending_comments.push(child);
|
||||
continue;
|
||||
}
|
||||
for comment in pending_comments.drain(..) {
|
||||
write_indent(out, indent);
|
||||
out.push_str(self.raw_trimmed(comment));
|
||||
out.push('\n');
|
||||
}
|
||||
write_indent(out, indent);
|
||||
if let Some(pattern) = child.child_by_field_name("pattern") {
|
||||
self.write_expr(out, pattern, indent, 0);
|
||||
}
|
||||
out.push_str(": ");
|
||||
if let Some(body) = child.child_by_field_name("body") {
|
||||
self.write_expr(out, body, indent, 0);
|
||||
}
|
||||
out.push_str(";\n");
|
||||
}
|
||||
for comment in pending_comments {
|
||||
write_indent(out, indent);
|
||||
out.push_str(self.raw_trimmed(comment));
|
||||
out.push('\n');
|
||||
}
|
||||
}
|
||||
|
||||
fn write_import(&mut self, out: &mut String, node: Node<'a>) {
|
||||
out.push_str("import ");
|
||||
if let Some(specifier) = node.child_by_field_name("specifier") {
|
||||
out.push_str(self.raw_trimmed(specifier));
|
||||
}
|
||||
}
|
||||
|
||||
fn write_call(&mut self, out: &mut String, node: Node<'a>, indent: usize) {
|
||||
let callee = node.child_by_field_name("function");
|
||||
if let Some(callee) = callee {
|
||||
self.write_expr(out, callee, indent, self.precedence(node));
|
||||
}
|
||||
out.push('(');
|
||||
let args: Vec<_> = named_children(node)
|
||||
.into_iter()
|
||||
.filter(|child| Some(*child) != callee && child.kind() != "comment")
|
||||
.collect();
|
||||
for (index, arg) in args.into_iter().enumerate() {
|
||||
if index > 0 {
|
||||
out.push_str(", ");
|
||||
}
|
||||
self.write_expr(out, arg, indent, 0);
|
||||
}
|
||||
out.push(')');
|
||||
}
|
||||
|
||||
fn write_path(&mut self, out: &mut String, node: Node<'a>, indent: usize) {
|
||||
if let Some(object) = node.child_by_field_name("object") {
|
||||
self.write_expr(out, object, indent, self.precedence(node));
|
||||
}
|
||||
out.push('.');
|
||||
if let Some(field) = node.child_by_field_name("field") {
|
||||
out.push_str(self.raw_trimmed(field));
|
||||
}
|
||||
}
|
||||
|
||||
fn write_unary(&mut self, out: &mut String, node: Node<'a>, indent: usize) {
|
||||
if let Some(operand) = node.child_by_field_name("operand") {
|
||||
let operator = self.slice(node.start_byte(), operand.start_byte()).trim();
|
||||
out.push_str(operator);
|
||||
self.write_expr(out, operand, indent, self.precedence(node));
|
||||
} else {
|
||||
out.push_str(self.raw_trimmed(node));
|
||||
}
|
||||
}
|
||||
|
||||
fn write_comparison_constraint(&mut self, out: &mut String, node: Node<'a>, indent: usize) {
|
||||
if let Some(value) = node.child_by_field_name("value") {
|
||||
let operator = self.slice(node.start_byte(), value.start_byte()).trim();
|
||||
out.push_str(operator);
|
||||
out.push(' ');
|
||||
self.write_expr(out, value, indent, self.precedence(node));
|
||||
} else {
|
||||
out.push_str(self.raw_trimmed(node));
|
||||
}
|
||||
}
|
||||
|
||||
fn write_binary_like(&mut self, out: &mut String, node: Node<'a>, indent: usize, prec: u8) {
|
||||
let left = node
|
||||
.child_by_field_name("left")
|
||||
.or_else(|| node.child_by_field_name("base"));
|
||||
let right = node
|
||||
.child_by_field_name("right")
|
||||
.or_else(|| node.child_by_field_name("fallback"));
|
||||
let (Some(left), Some(right)) = (left, right) else {
|
||||
out.push_str(self.raw_trimmed(node));
|
||||
return;
|
||||
};
|
||||
self.write_expr(out, left, indent, prec);
|
||||
out.push(' ');
|
||||
if node.kind() == "default_expression" {
|
||||
out.push_str("default");
|
||||
} else {
|
||||
out.push_str(self.slice(left.end_byte(), right.start_byte()).trim());
|
||||
}
|
||||
out.push(' ');
|
||||
self.write_expr(out, right, indent, prec + 1);
|
||||
}
|
||||
|
||||
fn precedence(&self, node: Node<'a>) -> u8 {
|
||||
match node.kind() {
|
||||
"default_expression" => 1,
|
||||
"binary_expression" => self.binary_precedence(node),
|
||||
"unary_expression" | "comparison_constraint" => 17,
|
||||
"call_expression" | "path_expression" => 19,
|
||||
_ => 20,
|
||||
}
|
||||
}
|
||||
|
||||
fn binary_precedence(&self, node: Node<'a>) -> u8 {
|
||||
let Some(left) = node.child_by_field_name("left") else {
|
||||
return 10;
|
||||
};
|
||||
let Some(right) = node.child_by_field_name("right") else {
|
||||
return 10;
|
||||
};
|
||||
match self.slice(left.end_byte(), right.start_byte()).trim() {
|
||||
"//" => 3,
|
||||
"&" => 5,
|
||||
"||" => 7,
|
||||
"&&" => 9,
|
||||
"==" | "!=" | ">" | ">=" | "<" | "<=" => 11,
|
||||
"++" => 12,
|
||||
"+" | "-" => 13,
|
||||
"*" | "/" => 15,
|
||||
_ => 10,
|
||||
}
|
||||
}
|
||||
|
||||
fn raw_trimmed(&self, node: Node<'a>) -> &'a str {
|
||||
self.slice(node.start_byte(), node.end_byte()).trim()
|
||||
}
|
||||
|
||||
fn slice(&self, start: usize, end: usize) -> &'a str {
|
||||
&self.source[start..end]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn named_children<'a>(node: Node<'a>) -> Vec<Node<'a>> {
|
||||
let mut cursor: TreeCursor<'a> = node.walk();
|
||||
node.named_children(&mut cursor).collect()
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn expression_contains_comment(node: Node<'_>) -> bool {
|
||||
if node.kind() == "comment" {
|
||||
return true;
|
||||
}
|
||||
named_children(node)
|
||||
.into_iter()
|
||||
.any(expression_contains_comment)
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn is_inline_expr(node: Node<'_>) -> bool {
|
||||
match node.kind() {
|
||||
"literal"
|
||||
| "identifier"
|
||||
| "string"
|
||||
| "integer"
|
||||
| "float"
|
||||
| "boolean"
|
||||
| "regex_literal"
|
||||
| "comparison_constraint"
|
||||
| "import_expression" => true,
|
||||
"path_expression"
|
||||
| "call_expression"
|
||||
| "unary_expression"
|
||||
| "binary_expression"
|
||||
| "default_expression"
|
||||
| "parenthesized_expression" => {
|
||||
!expression_contains_comment(node)
|
||||
&& named_children(node).into_iter().all(is_inline_expr)
|
||||
}
|
||||
"array" | "array_constraint" => {
|
||||
!expression_contains_comment(node)
|
||||
&& named_children(node).into_iter().all(is_inline_expr)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn has_blank_line(text: &str) -> bool {
|
||||
text.bytes().filter(|byte| *byte == b'\n').count() >= 2
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn write_indent(out: &mut String, indent: usize) {
|
||||
for _ in 0..indent {
|
||||
out.push(' ');
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn formats_fields_and_preserves_comments() {
|
||||
let source =
|
||||
"Server={\n# host\nhost=String default \"localhost\"; # trailing\n\nport=Int&>0;\n};";
|
||||
let formatted = format_source(source).unwrap();
|
||||
assert_eq!(
|
||||
formatted,
|
||||
"Server = {\n # host\n host = String default \"localhost\"; # trailing\n\n port = Int & > 0;\n};\n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formats_arrays_and_match_comments() {
|
||||
let source = "result=match x{# c\n_: [1,{a=2;}];};";
|
||||
let formatted = format_source(source).unwrap();
|
||||
assert_eq!(
|
||||
formatted,
|
||||
"result = match x {\n # c\n _: [\n 1,\n {\n a = 2;\n },\n ];\n};\n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formats_array_constraints() {
|
||||
let source = "tags=[...String];ports=[...(Int&>=1)];";
|
||||
let formatted = format_source(source).unwrap();
|
||||
assert_eq!(
|
||||
formatted,
|
||||
"tags = [...String];\nports = [...(Int & >= 1)];\n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wasm_export_returns_json() {
|
||||
let output = format_source_json("value={a=1;};");
|
||||
assert!(output.contains("\"ok\":true"));
|
||||
assert!(output.contains("value = {\\n a = 1;\\n};\\n"));
|
||||
}
|
||||
}
|
||||
@@ -30,16 +30,34 @@ Important paths:
|
||||
|
||||
```text
|
||||
crates/decodal-wasm/
|
||||
site/decodal-site/src/wasm/
|
||||
packages/decodal-wasm/
|
||||
```
|
||||
|
||||
The generated files in `site/decodal-site/src/wasm/` are committed so the site can build without requiring every consumer to run `wasm-pack` first.
|
||||
The npm package is `decodal-wasm`.
|
||||
The JSR package is `@hare/decodal-wasm`.
|
||||
|
||||
The generated files in `packages/decodal-wasm/` are committed so the site can build without requiring every consumer to run `wasm-pack` first.
|
||||
The WebAssembly package is for execution, not syntax highlighting.
|
||||
|
||||
## Language tools
|
||||
|
||||
Source-level tooling lives in the Rust language tools crate.
|
||||
This component is responsible for operations that must preserve source text details such as comments and whitespace.
|
||||
It is an internal crate used by the future Rust LSP and by the CodeMirror package's bundled formatter WebAssembly.
|
||||
|
||||
Important paths:
|
||||
|
||||
```text
|
||||
crates/decodal-language-tools/
|
||||
```
|
||||
|
||||
The current language tools crate exposes the formatter.
|
||||
Future LSP functionality should build on the same source-level tooling layer rather than on the runtime core.
|
||||
|
||||
## Web editor components
|
||||
|
||||
The Web playground editor uses CodeMirror 6 with a generated Lezer parser.
|
||||
This is the source of syntax highlighting, folding, indentation, and editor syntax tree behavior in the browser UI.
|
||||
This is the source of syntax highlighting, folding, indentation, editor syntax tree behavior, and the browser formatter command in the browser UI.
|
||||
|
||||
Important paths:
|
||||
|
||||
@@ -48,6 +66,8 @@ editors/lezer-decodal/decodal.grammar
|
||||
packages/decodal-codemirror/src/decodal.js
|
||||
packages/decodal-codemirror/src/decodal-parser.js
|
||||
packages/decodal-codemirror/src/decodal-parser.terms.js
|
||||
packages/decodal-codemirror/src/format.js
|
||||
packages/decodal-codemirror/wasm/
|
||||
```
|
||||
|
||||
The npm package is `decodal-codemirror`.
|
||||
@@ -91,7 +111,8 @@ Consumers that need syntax information should use the component that matches the
|
||||
|
||||
- Rust execution and embedding: `decodal`
|
||||
- Browser execution: `decodal-wasm`
|
||||
- Web editor syntax: Lezer / CodeMirror
|
||||
- Web formatting and editor syntax: Lezer / CodeMirror
|
||||
- Rust LSP internals: `decodal-language-tools`
|
||||
- General editor syntax: Tree-sitter
|
||||
|
||||
This avoids having a separate token stream API whose behavior would have to be kept compatible with both runtime parsing and editor grammars.
|
||||
|
||||
@@ -17,7 +17,7 @@ let / function env
|
||||
```
|
||||
|
||||
Module top-level bindings shadow prelude bindings.
|
||||
Primitive type names such as `String`, `Int`, `Float`, `Bool`, and `Array` are handled before environment lookup, so they are reserved and cannot be shadowed by host bindings.
|
||||
Primitive type names such as `String`, `Int`, `Float`, and `Bool` are handled before environment lookup, so they are reserved and cannot be shadowed by host bindings.
|
||||
|
||||
## Global bindings
|
||||
|
||||
@@ -59,12 +59,16 @@ HostValue =
|
||||
Float
|
||||
Bool
|
||||
Array(Vec<HostValue>)
|
||||
ArrayConstraint { item, constraints, default }
|
||||
Object(Vec<HostField>)
|
||||
Abstract { constraints, default }
|
||||
```
|
||||
|
||||
When a host value is bound, the engine internalizes it into `RuntimeValue` and allocates value thunks for object fields, array items, and defaults.
|
||||
|
||||
`HostValue::array_of(item)` builds an array constraint with a required element schema.
|
||||
There is no host API for an unconstrained abstract array.
|
||||
|
||||
## Abstract host objects
|
||||
|
||||
A host-provided schema object is represented as a concrete object structure whose fields may contain abstract values.
|
||||
|
||||
@@ -123,12 +123,16 @@ constraint は concrete value とは別の型として扱う。
|
||||
```text
|
||||
Constraint =
|
||||
Type(PrimitiveType)
|
||||
ArrayItems(ThunkId)
|
||||
Compare(Op, Literal)
|
||||
Regex(Pattern)
|
||||
BuiltinPredicate(Symbol)
|
||||
ObjectConstraint(...)
|
||||
```
|
||||
|
||||
`ArrayItems` は配列そのものの型と、すべての要素へ合成する schema thunk を表す。
|
||||
配列用の primitive type は持たず、抽象配列には必ず要素制約が必要である。
|
||||
|
||||
初期実装では、object の形は主に `Concrete(Object)` の field に `Abstract` を置くことで表現する。
|
||||
object 全体にかかる constraint は必要になった時点で追加する。
|
||||
|
||||
|
||||
@@ -24,8 +24,9 @@ cargo run -q -p decodal-cli --features regex -- examples/regex/main.dcdl
|
||||
|
||||
The primary crates.io package is `decodal`, which contains the embeddable library.
|
||||
`decodal-derive` provides optional derive macros for Rust struct integration and is published only when the derive crate changes.
|
||||
Workspace support crates such as `decodal-cli` and the Rust source crate `decodal-wasm` are not published to crates.io.
|
||||
The generated WebAssembly package under `site/decodal-site/src/wasm/` is published to npm and JSR.
|
||||
Workspace support crates such as `decodal-cli`, the Rust source crate `decodal-wasm`, and `decodal-language-tools` are not published to crates.io.
|
||||
The generated WebAssembly package under `packages/decodal-wasm/` is published to npm and JSR.
|
||||
The CodeMirror package bundles the generated formatter WebAssembly from `decodal-language-tools`.
|
||||
The project is dual licensed as `MIT OR Apache-2.0`.
|
||||
|
||||
```sh
|
||||
@@ -58,7 +59,7 @@ site/decodal-site/
|
||||
```
|
||||
|
||||
The site imports Markdown files from `doc/manual/souce/` and renders them as mdBook-style pages.
|
||||
The playground loads `decodal-wasm` and evaluates DCDL entirely in the browser.
|
||||
The playground loads `decodal-wasm` for evaluation and `decodal-codemirror` for editor features and source formatting.
|
||||
|
||||
Important files:
|
||||
|
||||
@@ -72,10 +73,15 @@ site/decodal-site/src/lib/docs.js
|
||||
site/decodal-site/src/lib/highlight.js
|
||||
packages/decodal-codemirror/src/decodal.js
|
||||
packages/decodal-codemirror/src/decodal-parser.js
|
||||
packages/decodal-wasm/decodal_wasm.js
|
||||
packages/decodal-wasm/decodal_wasm_bg.wasm
|
||||
packages/decodal-codemirror/wasm/decodal_language_tools.js
|
||||
packages/decodal-codemirror/wasm/decodal_language_tools_bg.wasm
|
||||
crates/decodal-wasm/src/lib.rs
|
||||
crates/decodal-language-tools/src/lib.rs
|
||||
```
|
||||
|
||||
Build the WebAssembly package before building the site:
|
||||
Build the WebAssembly packages before building the site:
|
||||
|
||||
```sh
|
||||
cd site/decodal-site
|
||||
@@ -84,18 +90,16 @@ npm run build:wasm
|
||||
npm run build
|
||||
```
|
||||
|
||||
`npm run build:wasm` writes generated files into:
|
||||
`npm run build:wasm` builds both generated WebAssembly packages.
|
||||
`npm run build:runtime` writes generated runtime files into `packages/decodal-wasm/`.
|
||||
`npm run build:tools` writes generated language tools files into `packages/decodal-codemirror/wasm/`.
|
||||
|
||||
```text
|
||||
site/decodal-site/src/wasm/
|
||||
```
|
||||
|
||||
These generated files are committed so the site can be built without requiring every consumer to regenerate the wasm package first.
|
||||
These generated files are committed so the site can be built without requiring every consumer to regenerate the wasm packages first.
|
||||
|
||||
Publish the generated WebAssembly package from its package directory:
|
||||
|
||||
```sh
|
||||
cd site/decodal-site/src/wasm
|
||||
cd packages/decodal-wasm
|
||||
npm publish
|
||||
npx jsr publish
|
||||
```
|
||||
|
||||
@@ -124,6 +124,19 @@ String & /^a$/ & /^b$/
|
||||
Host = IPv4Address;
|
||||
```
|
||||
|
||||
## 配列制約
|
||||
|
||||
配列制約には要素制約が必須であり、`[...T]` と書く。
|
||||
|
||||
```dcdl
|
||||
Names = [...String];
|
||||
PositiveInts = [...(Int & > 0)];
|
||||
```
|
||||
|
||||
複数の配列制約を `&` で合成した場合、各 concrete 要素へすべての要素制約を合成する。
|
||||
要素が object schema の場合は、その schema に含まれる default も各要素へ適用される。
|
||||
要素制約のない `Array` primitive type は存在しない。
|
||||
|
||||
## default
|
||||
|
||||
`default` は制約ではない。
|
||||
|
||||
@@ -31,6 +31,23 @@ disabled_config = NewConfig & {
|
||||
enabled_config = NewConfig;
|
||||
```
|
||||
|
||||
## 配列スキーマ
|
||||
|
||||
```dcdl
|
||||
Services = [...{
|
||||
name = String;
|
||||
port = Int default 8080;
|
||||
}];
|
||||
|
||||
Services & [
|
||||
{ name = "api"; },
|
||||
{ name = "worker"; port = 9000; },
|
||||
]
|
||||
```
|
||||
|
||||
抽象配列には要素制約が必須である。
|
||||
この例では、1 番目の要素の `port` は `8080` に materialize される。
|
||||
|
||||
## 関数と制約
|
||||
|
||||
```dcdl
|
||||
|
||||
@@ -7,6 +7,46 @@ array expression は、順序付きの値の列を表す。
|
||||
["a", "b", "c"]
|
||||
```
|
||||
|
||||
配列リテラルは concrete value であり、要素型を揃える必要はない。
|
||||
|
||||
```dcdl
|
||||
["api", 8080, true]
|
||||
```
|
||||
|
||||
## Array constraint
|
||||
|
||||
配列制約は `[` の直後に ellipsis を置き、その後へ要素制約を記述する。
|
||||
|
||||
```dcdl
|
||||
Names = [...String];
|
||||
Ports = [...(Int & >= 1 & <= 65535)];
|
||||
```
|
||||
|
||||
`[...T]` は、すべての要素が `T` を満たす長さ 0 以上の配列を表す。
|
||||
空配列は任意の配列制約を満たす。
|
||||
|
||||
```dcdl
|
||||
[...String] & []
|
||||
[...String] & ["api", "worker"]
|
||||
```
|
||||
|
||||
要素制約は object schema にもできる。
|
||||
要素 schema の default は、配列へ制約を合成するときに各要素へ適用される。
|
||||
|
||||
```dcdl
|
||||
Services = [...{
|
||||
name = String;
|
||||
enabled = Bool default true;
|
||||
}];
|
||||
|
||||
Services & [{ name = "api"; }]
|
||||
```
|
||||
|
||||
要素制約のない抽象配列型は提供しない。
|
||||
旧来の `Array` primitive type は使用できず、`[...T]` の `T` は必須である。
|
||||
`[String]` は配列制約ではなく、未解決の `String` 制約を 1 要素に持つ concrete array になる。
|
||||
長さ制約、位置別の tuple 制約、unique 制約は現在サポートしない。
|
||||
|
||||
## Array concat
|
||||
|
||||
`++` は concrete array 同士を連結する。
|
||||
|
||||
@@ -11,6 +11,7 @@ Expr
|
||||
├─ path reference
|
||||
├─ object
|
||||
├─ array
|
||||
├─ array constraint
|
||||
├─ function
|
||||
├─ function call
|
||||
├─ let
|
||||
|
||||
@@ -57,6 +57,7 @@ primary_expression = literal
|
||||
| identifier
|
||||
| comparison_constraint
|
||||
| object
|
||||
| array_constraint
|
||||
| array
|
||||
| let_expression
|
||||
| function_expression
|
||||
@@ -73,6 +74,7 @@ field_definition = field_path , "=" , expression ;
|
||||
field_path = identifier , { "." , identifier } ;
|
||||
|
||||
array = "[" , [ expression , { "," , expression } , [ "," ] ] , "]" ;
|
||||
array_constraint = "[" , "..." , expression , [ "," ] , "]" ;
|
||||
|
||||
let_expression = "let" , { field_definition , ";" } , "in" , expression ;
|
||||
function_expression = "(" , [ parameter_list ] , ")" , "=>" , expression ;
|
||||
|
||||
@@ -121,6 +121,7 @@ false
|
||||
default fallback 指定
|
||||
=> 関数
|
||||
. フィールド参照 / ドットパス定義
|
||||
... 配列の要素制約
|
||||
```
|
||||
|
||||
演算子の優先順位は [合成演算子](./operators.md) で定義する。
|
||||
|
||||
@@ -9,10 +9,11 @@ name = String;
|
||||
retry = Int default 3;
|
||||
ratio = Float;
|
||||
enable = Bool default true;
|
||||
tags = Array;
|
||||
tags = [...String];
|
||||
```
|
||||
|
||||
現在の primitive type は `String`、`Int`、`Float`、`Bool`、`Array` である。
|
||||
現在の primitive type は `String`、`Int`、`Float`、`Bool` である。
|
||||
配列は primitive type ではなく、必須の要素制約を持つ `[...T]` で表現する。
|
||||
各型の個別仕様へのリンクは [Manual Index](../../index.md) に集約する。
|
||||
|
||||
primitive type と制約合成の詳細は [制約と default](../constraints-and-defaults.md) も参照する。
|
||||
|
||||
@@ -71,6 +71,7 @@ PrimaryExpression {
|
||||
Identifier |
|
||||
ComparisonConstraint |
|
||||
Object |
|
||||
ArrayConstraint |
|
||||
Array |
|
||||
LetExpression |
|
||||
FunctionExpression |
|
||||
@@ -88,6 +89,7 @@ FieldDefinition { FieldPath Equal Expression }
|
||||
FieldPath { Identifier !fieldPath (Dot Identifier)* }
|
||||
|
||||
Array { LBracket (Expression (Comma Expression)* Comma?)? RBracket }
|
||||
ArrayConstraint { LBracket Ellipsis Expression Comma? RBracket }
|
||||
|
||||
LetExpression { Let FieldDefinitionList In Expression }
|
||||
FieldDefinitionList { (FieldDefinition Semicolon)* }
|
||||
@@ -143,6 +145,7 @@ ImportExpression { Import String }
|
||||
Semicolon { ";" }
|
||||
Comma { "," }
|
||||
Dot { "." }
|
||||
Ellipsis { "..." }
|
||||
Colon { ":" }
|
||||
Equal { "=" }
|
||||
EqualEqual { "==" }
|
||||
|
||||
@@ -158,3 +158,27 @@ Array concat
|
||||
right: (literal (integer))))
|
||||
right: (array
|
||||
(literal (integer)))))))
|
||||
|
||||
==================
|
||||
Array constraints
|
||||
==================
|
||||
{
|
||||
tags = [...String];
|
||||
ports = [...(Int & >= 1),];
|
||||
}
|
||||
---
|
||||
|
||||
(source_file
|
||||
(object
|
||||
(field_definition
|
||||
path: (field_path (identifier))
|
||||
value: (array_constraint
|
||||
element: (identifier)))
|
||||
(field_definition
|
||||
path: (field_path (identifier))
|
||||
value: (array_constraint
|
||||
element: (parenthesized_expression
|
||||
(binary_expression
|
||||
left: (identifier)
|
||||
right: (comparison_constraint
|
||||
value: (integer))))))))
|
||||
|
||||
@@ -54,6 +54,7 @@ module.exports = grammar({
|
||||
$.regex_literal,
|
||||
$.comparison_constraint,
|
||||
$.object,
|
||||
$.array_constraint,
|
||||
$.array,
|
||||
$.let_expression,
|
||||
$.function_expression,
|
||||
@@ -116,6 +117,14 @@ module.exports = grammar({
|
||||
']',
|
||||
),
|
||||
|
||||
array_constraint: $ => seq(
|
||||
'[',
|
||||
'...',
|
||||
field('element', $._expression),
|
||||
optional(','),
|
||||
']',
|
||||
),
|
||||
|
||||
let_expression: $ => seq(
|
||||
'let',
|
||||
repeat(seq($.field_definition, ';')),
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"*"
|
||||
"/"
|
||||
"++"
|
||||
"..."
|
||||
"&&"
|
||||
"||"
|
||||
"!"
|
||||
|
||||
+41
@@ -95,6 +95,10 @@
|
||||
"type": "SYMBOL",
|
||||
"name": "object"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "array_constraint"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "array"
|
||||
@@ -427,6 +431,43 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"array_constraint": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "["
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "..."
|
||||
},
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "element",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "_expression"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": ","
|
||||
},
|
||||
{
|
||||
"type": "BLANK"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "]"
|
||||
}
|
||||
]
|
||||
},
|
||||
"let_expression": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
|
||||
+160
@@ -11,6 +11,10 @@
|
||||
"type": "array",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "array_constraint",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "binary_expression",
|
||||
"named": true
|
||||
@@ -74,6 +78,86 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "array_constraint",
|
||||
"named": true,
|
||||
"fields": {
|
||||
"element": {
|
||||
"multiple": false,
|
||||
"required": true,
|
||||
"types": [
|
||||
{
|
||||
"type": "array",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "array_constraint",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "binary_expression",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "call_expression",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "comparison_constraint",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "default_expression",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "function_expression",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "identifier",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "import_expression",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "let_expression",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "literal",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "match_expression",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "parenthesized_expression",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "path_expression",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "regex_literal",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "unary_expression",
|
||||
"named": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "binary_expression",
|
||||
"named": true,
|
||||
@@ -86,6 +170,10 @@
|
||||
"type": "array",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "array_constraint",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "binary_expression",
|
||||
"named": true
|
||||
@@ -222,6 +310,10 @@
|
||||
"type": "array",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "array_constraint",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "binary_expression",
|
||||
"named": true
|
||||
@@ -303,6 +395,10 @@
|
||||
"type": "array",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "array_constraint",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "binary_expression",
|
||||
"named": true
|
||||
@@ -374,6 +470,10 @@
|
||||
"type": "array",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "array_constraint",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "binary_expression",
|
||||
"named": true
|
||||
@@ -471,6 +571,10 @@
|
||||
"type": "array",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "array_constraint",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "binary_expression",
|
||||
"named": true
|
||||
@@ -547,6 +651,10 @@
|
||||
"type": "array",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "array_constraint",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "binary_expression",
|
||||
"named": true
|
||||
@@ -617,6 +725,10 @@
|
||||
"type": "array",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "array_constraint",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "binary_expression",
|
||||
"named": true
|
||||
@@ -703,6 +815,10 @@
|
||||
"type": "array",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "array_constraint",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "binary_expression",
|
||||
"named": true
|
||||
@@ -794,6 +910,10 @@
|
||||
"type": "array",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "array_constraint",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "binary_expression",
|
||||
"named": true
|
||||
@@ -896,6 +1016,10 @@
|
||||
"type": "array",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "array_constraint",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "binary_expression",
|
||||
"named": true
|
||||
@@ -1009,6 +1133,10 @@
|
||||
"type": "array",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "array_constraint",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "binary_expression",
|
||||
"named": true
|
||||
@@ -1083,6 +1211,10 @@
|
||||
"type": "array",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "array_constraint",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "binary_expression",
|
||||
"named": true
|
||||
@@ -1159,6 +1291,10 @@
|
||||
"type": "array",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "array_constraint",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "binary_expression",
|
||||
"named": true
|
||||
@@ -1260,6 +1396,10 @@
|
||||
"type": "array",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "array_constraint",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "binary_expression",
|
||||
"named": true
|
||||
@@ -1346,6 +1486,10 @@
|
||||
"type": "array",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "array_constraint",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "binary_expression",
|
||||
"named": true
|
||||
@@ -1431,6 +1575,10 @@
|
||||
"type": "array",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "array_constraint",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "binary_expression",
|
||||
"named": true
|
||||
@@ -1507,6 +1655,10 @@
|
||||
"type": "array",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "array_constraint",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "binary_expression",
|
||||
"named": true
|
||||
@@ -1586,6 +1738,10 @@
|
||||
"type": "array",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "array_constraint",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "binary_expression",
|
||||
"named": true
|
||||
@@ -1712,6 +1868,10 @@
|
||||
"type": ".",
|
||||
"named": false
|
||||
},
|
||||
{
|
||||
"type": "...",
|
||||
"named": false
|
||||
},
|
||||
{
|
||||
"type": "/",
|
||||
"named": false
|
||||
|
||||
Generated
+5487
-4941
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
let
|
||||
Service = {
|
||||
name = String;
|
||||
enabled = Bool default true;
|
||||
};
|
||||
Services = [...Service];
|
||||
in
|
||||
Services & [
|
||||
{ name = "api"; },
|
||||
{ name = "worker"; enabled = false; },
|
||||
]
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
CodeMirror 6 language support for Decodal.
|
||||
|
||||
This package provides the Lezer parser metadata, highlighting tags, indentation rules, and block folding used by the Decodal Web playground.
|
||||
It does not include the Decodal runtime or WebAssembly evaluator.
|
||||
This package provides the Lezer parser metadata, highlighting tags, indentation rules, block folding, and formatter command used by the Decodal Web playground.
|
||||
It does not include the Decodal runtime evaluator.
|
||||
Use `decodal-wasm` when you want to evaluate Decodal in a browser.
|
||||
|
||||
## Install
|
||||
@@ -52,3 +52,23 @@ decodal({ highlight: false })
|
||||
```
|
||||
|
||||
Use this when you want the language support without the bundled highlight style.
|
||||
|
||||
## Formatting
|
||||
|
||||
The formatter is exposed as a separate subpath so plain syntax highlighting does not force callers to initialize WebAssembly.
|
||||
|
||||
```js
|
||||
import {
|
||||
formatDecodal,
|
||||
formatDecodalCommand,
|
||||
initDecodalFormatter,
|
||||
} from 'decodal-codemirror/format';
|
||||
import formatterWasm from 'decodal-codemirror/wasm/decodal_language_tools_bg.wasm?url';
|
||||
|
||||
await initDecodalFormatter(formatterWasm);
|
||||
|
||||
const result = formatDecodal('Server={port=Int default 8080;};');
|
||||
if (result.ok) console.log(result.source);
|
||||
|
||||
formatDecodalCommand(view);
|
||||
```
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
{
|
||||
"name": "@hare/decodal-codemirror",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.5",
|
||||
"license": "MIT",
|
||||
"exports": "./src/mod.ts",
|
||||
"exports": {
|
||||
".": "./src/mod.ts",
|
||||
"./format": "./src/format.ts"
|
||||
},
|
||||
"imports": {
|
||||
"@codemirror/language": "npm:@codemirror/language@^6.12.4",
|
||||
"@codemirror/view": "npm:@codemirror/view@^6.43.6",
|
||||
"@lezer/highlight": "npm:@lezer/highlight@^1.2.3",
|
||||
"@lezer/lr": "npm:@lezer/lr@^1.4.10"
|
||||
},
|
||||
@@ -12,12 +16,20 @@
|
||||
"include": [
|
||||
"README.md",
|
||||
"src/mod.ts",
|
||||
"src/format.ts",
|
||||
"src/decodal.js",
|
||||
"src/decodal.d.ts",
|
||||
"src/decodal.js.d.ts",
|
||||
"src/format.js",
|
||||
"src/format.d.ts",
|
||||
"src/format.js.d.ts",
|
||||
"src/decodal-parser-jsr.js",
|
||||
"src/decodal-parser.js",
|
||||
"src/decodal-parser.terms.js",
|
||||
"wasm/decodal_language_tools.js",
|
||||
"wasm/decodal_language_tools.d.ts",
|
||||
"wasm/decodal_language_tools_bg.wasm",
|
||||
"wasm/decodal_language_tools_bg.wasm.d.ts",
|
||||
"package.json"
|
||||
]
|
||||
}
|
||||
|
||||
+20
-3
@@ -1,15 +1,22 @@
|
||||
{
|
||||
"name": "decodal-codemirror",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "decodal-codemirror",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.5",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"devDependencies": {
|
||||
"@codemirror/language": "^6.12.4",
|
||||
"@codemirror/view": "^6.43.6",
|
||||
"@lezer/highlight": "^1.2.3",
|
||||
"@lezer/lr": "^1.4.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@codemirror/language": "^6.12.4",
|
||||
"@codemirror/view": "^6.43.6",
|
||||
"@lezer/highlight": "^1.2.3",
|
||||
"@lezer/lr": "^1.4.10"
|
||||
}
|
||||
@@ -18,6 +25,7 @@
|
||||
"version": "6.12.4",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.4.tgz",
|
||||
"integrity": "sha512-1q4PaT+o6PbgpkJt4Q8Fv5XJxTy4FUZ4MWETtyiDw3J0Pyr9E2vqcKL+k9wcvjNTIsauxvE7OfmWj3FRPHQ76A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
@@ -32,6 +40,7 @@
|
||||
"version": "6.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.7.1.tgz",
|
||||
"integrity": "sha512-9QzNDgE4EYDnAHfrTlR2lwiPciiOymLtwKK+8yHQzCc7GXhAP9xdEbEJFy2IWB1j9UGUl9BsgMmTo/ImA02T7A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@marijn/find-cluster-break": "^1.0.0"
|
||||
@@ -41,6 +50,7 @@
|
||||
"version": "6.43.6",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.6.tgz",
|
||||
"integrity": "sha512-EVunGSYN1wz1p75WY1s3Xg7t3i8Yol0kGZGizNdX9BUFgMFILYVe8/u6EVpo7Ff5PwbZuILb4QAq7IZoKzIEQA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.7.0",
|
||||
@@ -53,12 +63,14 @@
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.2.tgz",
|
||||
"integrity": "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@lezer/highlight": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz",
|
||||
"integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.3.0"
|
||||
@@ -68,6 +80,7 @@
|
||||
"version": "1.4.10",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.10.tgz",
|
||||
"integrity": "sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.0.0"
|
||||
@@ -77,24 +90,28 @@
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.3.tgz",
|
||||
"integrity": "sha512-FY+MKLBoTsLNJF/eLWaOsXGdz6uh3Iu1axjPf6TUq92IYumcTcXWHoS747JARLkcdlJ/Waiaxc5wQfFO8jC6NA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/crelt": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.7.tgz",
|
||||
"integrity": "sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/style-mod": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz",
|
||||
"integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/w3c-keyname": {
|
||||
"version": "2.2.8",
|
||||
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "decodal-codemirror",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.5",
|
||||
"description": "CodeMirror 6 language support for Decodal.",
|
||||
"type": "module",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
@@ -14,21 +14,37 @@
|
||||
"types": "./src/decodal.d.ts",
|
||||
"import": "./src/decodal.js"
|
||||
},
|
||||
"./format": {
|
||||
"types": "./src/format.d.ts",
|
||||
"import": "./src/format.js"
|
||||
},
|
||||
"./parser": "./src/decodal-parser.js",
|
||||
"./terms": "./src/decodal-parser.terms.js"
|
||||
"./terms": "./src/decodal-parser.terms.js",
|
||||
"./wasm/decodal_language_tools_bg.wasm": "./wasm/decodal_language_tools_bg.wasm"
|
||||
},
|
||||
"types": "./src/decodal.d.ts",
|
||||
"files": [
|
||||
"README.md",
|
||||
"src/"
|
||||
"src/",
|
||||
"wasm/decodal_language_tools.js",
|
||||
"wasm/decodal_language_tools.d.ts",
|
||||
"wasm/decodal_language_tools_bg.wasm",
|
||||
"wasm/decodal_language_tools_bg.wasm.d.ts"
|
||||
],
|
||||
"keywords": [
|
||||
"decodal",
|
||||
"codemirror",
|
||||
"lezer"
|
||||
],
|
||||
"dependencies": {
|
||||
"peerDependencies": {
|
||||
"@codemirror/language": "^6.12.4",
|
||||
"@codemirror/view": "^6.43.6",
|
||||
"@lezer/highlight": "^1.2.3",
|
||||
"@lezer/lr": "^1.4.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@codemirror/language": "^6.12.4",
|
||||
"@codemirror/view": "^6.43.6",
|
||||
"@lezer/highlight": "^1.2.3",
|
||||
"@lezer/lr": "^1.4.10"
|
||||
}
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
import {LRParser} from "npm:@lezer/lr@^1.4.10"
|
||||
export const parser = LRParser.deserialize({
|
||||
version: 14,
|
||||
states: "7vQYQPOOO#nQPO'#CaOOQO'#Cr'#CrO$wQPO'#CyO&UQPO'#DOO&^QPO'#DSO&eQPO'#DWO&mQPO'#CqO$wQPO'#DcO&wQPO'#DhOOQO'#Cq'#CqOOQO'#Cp'#CpO&|QPO'#CoO$wQPO'#CoOOQO'#Cn'#CnO)fQPO'#CmO.XQPO'#ClO0]QPO'#CkOOQO'#Cj'#CjO2TQPO'#CiO3xQPO'#ChO5jQPO'#CgO7UQPO'#CfOOQO'#Ce'#CeO7`QPO'#C`O7eQPO'#C_OOQO'#Dz'#DzQYQPOOO8xQPO'#D{O8}QPO,58{OOQO,59e,59eO9VQPO'#CaOOQO,59j,59jO9_QPO,59jOOQO,59n,59nO9gQPO,59nO9oQPO'#EOO9tQPO'#DYO9|QPO,59rO:RQPO'#CqO:`QPO'#D^O:hQQO,59vO:mQPO,59vO:rQPO,59]O:wQPO,59}OOQO,5:S,5:SO:|QPO'#DjOOQO,59[,59[O;TQPO,59[OOQO,59Z,59ZO$wQPO,59YO$wQPO,59XO$wQPO,59WOOQO'#Dr'#DrO$wQPO,59VO$wQPO,59UO$wQPO,59TO$wQPO,59SO$wQPO,59RO$wQPO,59QO$wQPO,58zOOQO,58y,58yOOQO-E7x-E7xOOQO,5:g,5:gOOQO-E7y-E7yO;YQPO1G/UO;bQPO1G/UOOQO1G/U1G/UO;jQPO1G/YO;qQPO1G/YOOQO1G/Y1G/YOOQO,5:j,5:jOOQO-E7|-E7|O$wQPO1G/^O$wQPO,59yO;yQPO,59xO<RQPO,59xO$wQPO1G/bO<ZQQO1G/bOOQO1G.w1G.wO<`QPO1G/iO<jQPO'#DkOOQO,5:U,5:UO<rQPO,5:UOOQO1G.v1G.vOOQO1G.t1G.tO<wQPO1G.sO@_QPO1G.rOAyQPO1G.qOOQO1G.p1G.pOCqQPO1G.oOEfQPO1G.nOFxQPO1G.mOOQO1G.l1G.lOOQO1G.f1G.fOOQO1G.s1G.sOOQO1G.r1G.rOOQO1G.q1G.qOOQO1G.o1G.oOOQO1G.n1G.nOOQO1G.m1G.mOOQO,5:h,5:hOOQO7+$p7+$pOHfQPO7+$pOOQO-E7z-E7zOOQO,5:i,5:iOOQO7+$t7+$tOHnQPO7+$tOOQO-E7{-E7{OOQO7+$x7+$xOOQO1G/e1G/eOHuQPO'#D_OOQO,5:k,5:kOHzQPO1G/dOOQO-E7}-E7}OOQO7+$|7+$|O$wQPO7+$|OOQO'#Df'#DfOISQPO'#DeOOQO7+%T7+%TOIXQPO7+%TOIaQPO,5:VOIhQPO,5:VOOQO1G/p1G/pOOQO<<H[<<H[PIpQPO'#D|OOQO<<H`<<H`P$wQPO'#D}PIuQPO'#EPOOQO<<Hh<<HhO$wQPO,5:POIzQPO<<HoOJUQPO<<HoOOQO<<Ho<<HoOJ^QPO1G/qOOQO1G/k1G/kOOQO,5:l,5:lOOQOAN>ZAN>ZOJeQPOAN>ZOOQO-E8O-E8OOOQOG23uG23uP<cQPO'#EQO$wQPO,59XO$wQPO,59WO$wQPO,59VO$wQPO,59TO$wQPO,59SO$wQPO,59ROJoQPO1G.rOKVQPO'#ClOKjQPO'#CkOL^QPO'#ChOM^QPO'#CgONaQPO'#CfO$wQPO,59WO$wQPO,59UO$wQPO,59QO! jQPO1G.qO!!QQPO'#CkO!#_QPO'#CiO$wQPO,59VO$wQPO,59UO$wQPO,59TO$wQPO,59SO$wQPO,59RO$wQPO,59QO$wQPO,58zO!$[QPO1G.oO!$rQPO1G.nO!%YQPO1G.mO!%zQPO'#CiO!&rQPO'#ChO!'gQPO'#CgO!(XQPO'#CfO!(vQPO'#C`",
|
||||
stateData: "!)Y~O!wOSPOS~OUPOgQOhQOiQOjQOkQOlQOnROoROpROqROsSOwTO{UO!PVO!WWO!]XO!`]O!a]O~OneXoeXpeXqeX!PeX!aeX!beX!ceX!deX!eeX!geX!heX!ieX!jeX!keX!leX!meX~OVlOUeXWTXgeXheXieXjeXkeXleXseXteXweX{eX!WeX!]eX!`eX!ueX~P!gOUYOgQOhQOiQOjQOkQOlQOnROoROpROqROsSOwTO{UO!PVO!WWO!]XO!`]O!a]O~OUoOupO~OyrO~P$wOUoO}|P~OUwO!TyO~P]Og}O~OV!QO!P!OOUcXgcXhcXicXjcXkcXlcXncXocXpcXqcXscXtcXwcX{cX!WcX!]cX!`cX!acX!bcX!ccX!dcX!ecX!gcX!hcX!icX!jcX!kcX!lcX!mcX!ucXxcXycX!TcXucX!ScX~O!b!SO!c!SOUaXgaXhaXiaXjaXkaXlaXnaXoaXpaXqaXsaXtaXwaX{aX!PaX!WaX!]aX!`aX!aaX!daX!eaX!gaX!haX!iaX!jaX!kaX!laX!maX!uaXVaXxaXyaX!TaXuaX!SaX~OU`Xg`Xh`Xi`Xj`Xk`Xl`Xn`Xo`Xp`Xq`Xs`Xt`Xw`X{`X!P`X!W`X!]`X!``X!e`X!g`X!h`X!i`X!j`X!k`X!l`X!m`X!u`Xx`Xy`X!T`Xu`X!S`X~O!a!TO!d!TO~P,OOn!VOo!VOp!VOq!VO!g!VO!h!VOU_Xg_Xh_Xi_Xj_Xk_Xl_Xs_Xt_Xw_X{_X!P_X!W_X!]_X!`_X!a_X!i_X!j_X!k_X!l_X!m_X!u_X~O!e!UO~P.cOU]Xg]Xh]Xi]Xj]Xk]Xl]Xn]Xo]Xp]Xq]Xs]Xt]Xw]X{]X!P]X!W]X!]]X!`]X!a]X!j]X!k]X!l]X!m]X!u]X~O!i!XO~P0dOU[Xg[Xh[Xi[Xj[Xk[Xl[Xn[Xo[Xp[Xq[Xs[Xt[Xw[X{[X!P[X!W[X!][X!`[X!a[X!k[X!l[X!m[X!u[X~O!j!YO~P2[OUZXgZXhZXiZXjZXkZXlZXnZXoZXpZXqZXsZXtZXwZX{ZX!PZX!WZX!]ZX!`ZX!aZX!lZX!mZX!uZX~O!k!ZO~P4POUYXgYXhYXiYXjYXkYXlYXnYXoYXpYXqYXsYXtYXwYX{YX!PYX!WYX!]YX!`YX!aYX!uYX~O!l![O!m!]O~P5qOW!^O~Ot!_OURXgRXhRXiRXjRXkRXlRXnRXoRXpRXqRXsRXwRX{RX!PRX!WRX!]RX!`RX!aRX!uRX~OU!aO~OVlOWTa~OVlOWTX~Ot!cOu!eO~Ox!fOy!hO~Ot!iO~OUoO}|X~O}!kO~O!S!lOVeX!TeX~P!gOx!mO!T!QX~O!U!oO~O!T!pO~O!T!qO~Os!rO~O!T!tO~P$wOU!vO~OUoOu#YO~Ot#ZOu#YO~Oy#^O~P$wOx#_Oy#^O~OU#cO!T!Qa~Ox#eO!T!Qa~O!U#hO~Ou#kO!Z#iO~P$wOx#mO!T!_X~O!T#oO~O!b!SO!c!SOUaigaihaiiaijaikailainaioaipaiqaisaitaiwai{ai!Pai!Wai!]ai!`ai!aai!dai!eai!gai!hai!iai!jai!kai!lai!mai!uaixaiyai!Taiuai!Sai~O!d!TOn`io`ip`iq`is`it`i!e`i!g`i!h`i!i`i!j`i!k`i!l`i!m`i~OU`ig`ih`ii`ij`ik`il`iw`i{`i!P`i!W`i!]`i!``i!a`i!u`i~P?^Os_it_i!i_i!j_i!k_i!l_i!m_i~O!e!UOU_ig_ih_ii_ij_ik_il_in_io_ip_iq_iw_i{_i!P_i!W_i!]_i!`_i!a_i!u_i~PAbOs]it]i!j]i!k]i!l]i!m]i~O!i!XOU]ig]ih]ii]ij]ik]il]in]io]ip]iq]iw]i{]i!P]i!W]i!]]i!`]i!a]i!u]i~PC]Os[it[i!k[i!l[i!m[i~O!j!YOU[ig[ih[ii[ij[ik[il[in[io[ip[iq[iw[i{[i!P[i!W[i!][i!`[i!a[i!u[i~PETO!k!ZOUZigZihZiiZijZikZilZinZioZipZiqZisZitZiwZi{Zi!PZi!WZi!]Zi!`Zi!aZi!lZi!mZi!uZi~OUoOu#pO~Oy#rO~P$wO!S!lO~OU#cO!T!Qi~O!S#vO~Ot#wOu#yO~O!T!_a~P$wOx#zO!T!_a~OUoO~OU#cO~Ou#}O!Z#iO~P$wOt$OOu#}O~O!T!_i~P$wOu$QO!Z#iO~P$wO!a!TOx`iy`i!T`iu`i!S`i~P?^O!a$SO!d$SOV`X!b`X!c`X~P,OO!e$TOV_X!b_X!c_X!d_Xx_Xy_X!T_Xu_X!S_X~P.cO!j$VOV[X!b[X!c[X!d[X!e[X!g[X!h[X!i[Xx[Xy[X!T[Xu[X!S[X~P2[O!k$WOVZX!bZX!cZX!dZX!eZX!gZX!hZX!iZX!jZXxZXyZX!TZXuZX!SZX~P4PO!l$XO!m$bOVYX!bYX!cYX!dYX!eYX!gYX!hYX!iYX!jYX!kYXxYXyYX!TYXuYX!SYX~P5qO!e$`Ox_iy_i!T_iu_i!S_i~PAbOn!VOo!VOp!VOq!VO!e$`O!g!VO!h!VOx_Xy_X!i_X!j_X!k_X!l_X!m_X!T_Xs_Xt_Xu_X!S_X~O!i$aOV]X!b]X!c]X!d]X!e]X!g]X!h]Xx]Xy]X!T]Xu]X!S]X~P0dO!i$gOx]iy]i!T]iu]i!S]i~PC]O!j$hOx[iy[i!T[iu[i!S[i~PETO!k$iOxZiyZi!lZi!mZi!TZisZitZiuZi!SZi~O!i$gOx]Xy]X!j]X!k]X!l]X!m]X!T]Xs]Xt]Xu]X!S]X~O!j$hOx[Xy[X!k[X!l[X!m[X!T[Xs[Xt[Xu[X!S[X~O!k$iOxZXyZX!lZX!mZX!TZXsZXtZXuZX!SZX~O!l$jO!m$kOxYXyYX!TYXsYXtYXuYX!SYX~OW$lO~O{}!W!]!mjkih!ZUj~",
|
||||
goto: "3l!uPPP!v!z#[PPP#h$m%]%}&x'v(w)s*z,S-Y.b/f0jPPPPPP0jPPPP0jPPP0jPPP0jP1nP0jP1q1tPPP0jP1|2UP0jP2[2_PPPPPP2bPPPPPPP2k2q2x3O3Y3`3fTjOkSiOkQqSStUuV#X!c#Z#qShOk]$tSUu!c#Z#qSiOkQnRQsTQ{VQ|WQ!s!OS#Q!^$lY#]!f#_#m#s#zQ#a!kQ#b!lQ#g!oW#i!r#w$O$RQ#u#hR#{#v!OgORTVWk!O!^!f!k!l!o!r#_#h#m#s#v#w#z$O$R$lV#P!]$b$kWfOk!]!^Y$_R!k!o#h$bs$sTVW!O!f!l!r#_#m#s#v#w#z$O$R$k$lWeOk!]!^Q#O![Q#W$XY$^R!k!o#h$bQ$o$js$rTVW!O!f!l!r#_#m#s#v#w#z$O$R$k$lYdOk![!]!^Q!}!ZQ#V$W[$]R!k!o#h$X$bQ$n$iu$qTVW!O!f!l!r#_#m#s#v#w#z$O$R$j$k$l[cOk!Z![!]!^Q!|!YQ#U$V^$eR!k!o#h$W$X$bQ$m$hw$pTVW!O!f!l!r#_#m#s#v#w#z$O$R$i$j$k$l!hbORTVWk!O!Y!Z![!]!^!f!k!l!o!r#_#h#m#s#v#w#z$O$R$V$W$X$b$h$i$j$k$lV!{!X$a$g`aOk!X!Y!Z![!]!^Q!z!WQ#T$Ub$[R!k!o#h$V$W$X$a$bQ$c$f{$dTVW!O!f!l!r#_#m#s#v#w#z$O$R$g$h$i$j$k$l!``OTVWk!O!W!X!Y!Z![!]!^!f!l!r#_#m#s#v#w#z$O$R$f$g$h$i$j$k$lQ!y!UQ#S$TQ$Y$`e$ZR!k!o#h$U$V$W$X$a$b!z_ORTVWk!O!U!W!X!Y!Z![!]!^!f!k!l!o!r#_#h#m#s#v#w#z$O$R$T$U$V$W$X$`$a$b$f$g$h$i$j$k$lQ!x!TR#R$S#O^ORTVWk!O!T!U!W!X!Y!Z![!]!^!f!k!l!o!r#_#h#m#s#v#w#z$O$R$S$T$U$V$W$X$`$a$b$f$g$h$i$j$k$lQ!R]R!w!S#T[ORTVW]k!O!S!T!U!W!X!Y!Z![!]!^!f!k!l!o!r#_#h#m#s#v#w#z$O$R$S$T$U$V$W$X$`$a$b$f$g$h$i$j$k$l#TZORTVW]k!O!S!T!U!W!X!Y!Z![!]!^!f!k!l!o!r#_#h#m#s#v#w#z$O$R$S$T$U$V$W$X$`$a$b$f$g$h$i$j$k$l#TYORTVW]k!O!S!T!U!W!X!Y!Z![!]!^!f!k!l!o!r#_#h#m#s#v#w#z$O$R$S$T$U$V$W$X$`$a$b$f$g$h$i$j$k$lRvURzVQxVV#d!m#e#tQ#l!rV#|#w$O$RX#j!r#w$O$RR!P[R!u!OQ!WaQ$U$[R$f$dQkOR!`kSmPoR!bmQ!dqR#[!dQ!gsS#`!g#nR#n!sQuUR!juQ!nxR#f!nQ#x#lR$P#x",
|
||||
nodeNames: "⚠ Comment Source Statement FieldDefinition FieldPath Identifier Dot Equal Expression DefaultExpression PatchExpression ComposeExpression LogicalOrExpression LogicalAndExpression ComparisonExpression ConcatExpression AdditiveExpression MultiplicativeExpression UnaryExpression PostfixExpression PrimaryExpression Literal String Integer Float True False Regex ComparisonConstraint Lt Lte Gt Gte Object LBrace Semicolon RBrace Array LBracket Comma RBracket LetExpression Let FieldDefinitionList In FunctionExpression LParen ParameterList Parameter Colon RParen Arrow MatchExpression Match MatchArm Pattern Underscore ImportExpression Import CallSuffix ArgumentList Bang Minus Star Slash Plus PlusPlus CompareOperator EqualEqual BangEqual AmpAmp PipePipe Amp SlashSlash Default",
|
||||
maxTerm: 85,
|
||||
states: "8fQYQPOOO#nQPO'#CaOOQO'#Cr'#CrO$wQPO'#CyO&UQPO'#DOO&^QQO'#DXO&hQPO'#DYO&pQPO'#CqO$wQPO'#DeO&zQPO'#DjOOQO'#Cq'#CqOOQO'#Cp'#CpO'PQPO'#CoO$wQPO'#CoOOQO'#Cn'#CnO)iQPO'#CmO.[QPO'#ClO0`QPO'#CkOOQO'#Cj'#CjO2WQPO'#CiO3{QPO'#ChO5mQPO'#CgO7XQPO'#CfOOQO'#Ce'#CeO7cQPO'#C`O7hQPO'#C_OOQO'#D|'#D|QYQPOOO8{QPO'#D}O9QQPO,58{OOQO,59e,59eO9YQPO'#CaOOQO,59j,59jO9bQPO,59jO$wQPO,59nOOQO,59s,59sO9jQPO,59sO9rQPO'#EQO9wQPO'#D[O:PQPO,59tO:UQPO'#CqO:cQPO'#D`O:kQQO,59xO:pQPO,59xO:uQPO,59]O:zQPO,5:POOQO,5:U,5:UO;PQPO'#DlOOQO,59[,59[O;WQPO,59[OOQO,59Z,59ZO$wQPO,59YO$wQPO,59XO$wQPO,59WOOQO'#Dt'#DtO$wQPO,59VO$wQPO,59UO$wQPO,59TO$wQPO,59SO$wQPO,59RO$wQPO,59QO$wQPO,58zOOQO,58y,58yOOQO-E7z-E7zOOQO,5:i,5:iOOQO-E7{-E7{O;]QPO1G/UO;eQPO1G/UOOQO1G/U1G/UO;mQPO1G/YO;uQPO1G/_O;|QPO1G/_OOQO1G/_1G/_OOQO,5:l,5:lOOQO-E8O-E8OO$wQPO1G/`O$wQPO,59{O<UQPO,59zO<^QPO,59zO$wQPO1G/dO<fQQO1G/dOOQO1G.w1G.wO<kQPO1G/kO<uQPO'#DmOOQO,5:W,5:WO<}QPO,5:WOOQO1G.v1G.vOOQO1G.t1G.tO=SQPO1G.sO@jQPO1G.rOBUQPO1G.qOOQO1G.p1G.pOC|QPO1G.oOEqQPO1G.nOGTQPO1G.mOOQO1G.l1G.lOOQO1G.f1G.fOOQO1G.s1G.sOOQO1G.r1G.rOOQO1G.q1G.qOOQO1G.o1G.oOOQO1G.n1G.nOOQO1G.m1G.mOOQO,5:j,5:jOOQO7+$p7+$pOHqQPO7+$pOOQO-E7|-E7|OOQO7+$t7+$tOHyQPO7+$tOOQO,5:k,5:kOOQO7+$y7+$yOIOQPO7+$yOOQO-E7}-E7}OOQO7+$z7+$zOOQO1G/g1G/gOIVQPO'#DaOOQO,5:m,5:mOI[QPO1G/fOOQO-E8P-E8POOQO7+%O7+%OO$wQPO7+%OOOQO'#Dh'#DhOIdQPO'#DgOOQO7+%V7+%VOIiQPO7+%VOIqQPO,5:XOIxQPO,5:XOOQO1G/r1G/rOOQO<<H[<<H[PJQQPO'#EOOOQO<<H`<<H`OOQO<<He<<HeP$wQPO'#EPPJVQPO'#EROOQO<<Hj<<HjO$wQPO,5:ROJ[QPO<<HqOJfQPO<<HqOOQO<<Hq<<HqOJnQPO1G/sOOQO1G/m1G/mOOQO,5:n,5:nOOQOAN>]AN>]OJuQPOAN>]OOQO-E8Q-E8QOOQOG23wG23wP<nQPO'#ESO$wQPO,59XO$wQPO,59WO$wQPO,59VO$wQPO,59TO$wQPO,59SO$wQPO,59ROKPQPO1G.rOKgQPO'#ClOKzQPO'#CkOLnQPO'#ChOMnQPO'#CgONqQPO'#CfO$wQPO,59WO$wQPO,59UO$wQPO,59QO! zQPO1G.qO!!bQPO'#CkO!#oQPO'#CiO$wQPO,59VO$wQPO,59UO$wQPO,59TO$wQPO,59SO$wQPO,59RO$wQPO,59QO$wQPO,58zO!$lQPO1G.oO!%SQPO1G.nO!%jQPO1G.mO!&[QPO'#CiO!'SQPO'#ChO!'wQPO'#CgO!(iQPO'#CfO!)WQPO'#C`",
|
||||
stateData: "!)j~O!yOSPOS~OUPOgQOhQOiQOjQOkQOlQOnROoROpROqROsSOwTO}UO!RVO!YWO!_XO!b]O!c]O~OneXoeXpeXqeX!ReX!ceX!deX!eeX!feX!geX!ieX!jeX!keX!leX!meX!neX!oeX~OVlOUeXWTXgeXheXieXjeXkeXleXseXteXweX}eX!YeX!_eX!beX!weX~P!gOUYOgQOhQOiQOjQOkQOlQOnROoROpROqROsSOwTO}UO!RVO!YWO!_XO!b]O!c]O~OUoOupO~OxrOzsO~P$wOUoO!P!OP~OUxO!VzO~P]Og!OO~OV!RO!R!POUcXgcXhcXicXjcXkcXlcXncXocXpcXqcXscXtcXwcX}cX!YcX!_cX!bcX!ccX!dcX!ecX!fcX!gcX!icX!jcX!kcX!lcX!mcX!ncX!ocX!wcXycXzcX!VcXucX!UcX~O!d!TO!e!TOUaXgaXhaXiaXjaXkaXlaXnaXoaXpaXqaXsaXtaXwaX}aX!RaX!YaX!_aX!baX!caX!faX!gaX!iaX!jaX!kaX!laX!maX!naX!oaX!waXVaXyaXzaX!VaXuaX!UaX~OU`Xg`Xh`Xi`Xj`Xk`Xl`Xn`Xo`Xp`Xq`Xs`Xt`Xw`X}`X!R`X!Y`X!_`X!b`X!g`X!i`X!j`X!k`X!l`X!m`X!n`X!o`X!w`Xy`Xz`X!V`Xu`X!U`X~O!c!UO!f!UO~P,ROn!WOo!WOp!WOq!WO!i!WO!j!WOU_Xg_Xh_Xi_Xj_Xk_Xl_Xs_Xt_Xw_X}_X!R_X!Y_X!__X!b_X!c_X!k_X!l_X!m_X!n_X!o_X!w_X~O!g!VO~P.fOU]Xg]Xh]Xi]Xj]Xk]Xl]Xn]Xo]Xp]Xq]Xs]Xt]Xw]X}]X!R]X!Y]X!_]X!b]X!c]X!l]X!m]X!n]X!o]X!w]X~O!k!YO~P0gOU[Xg[Xh[Xi[Xj[Xk[Xl[Xn[Xo[Xp[Xq[Xs[Xt[Xw[X}[X!R[X!Y[X!_[X!b[X!c[X!m[X!n[X!o[X!w[X~O!l!ZO~P2_OUZXgZXhZXiZXjZXkZXlZXnZXoZXpZXqZXsZXtZXwZX}ZX!RZX!YZX!_ZX!bZX!cZX!nZX!oZX!wZX~O!m![O~P4SOUYXgYXhYXiYXjYXkYXlYXnYXoYXpYXqYXsYXtYXwYX}YX!RYX!YYX!_YX!bYX!cYX!wYX~O!n!]O!o!^O~P5tOW!_O~Ot!`OURXgRXhRXiRXjRXkRXlRXnRXoRXpRXqRXsRXwRX}RX!RRX!YRX!_RX!bRX!cRX!wRX~OU!bO~OVlOWTa~OVlOWTX~Ot!dOu!fO~Oy!hOz!jO~Ot!kO~OUoO!P!OX~O!P!mO~O!U!nOVeX!VeX~P!gOy!oO!V!SX~O!W!qO~O!V!rO~O!V!sO~Os!tO~O!V!vO~P$wOU!xO~OUoOu#[O~Ot#]Ou#[O~Oy#`Oz#_O~Oz#bO~P$wOy#cOz#bO~OU#gO!V!Sa~Oy#iO!V!Sa~O!W#lO~Ou#oO!]#mO~P$wOy#qO!V!aX~O!V#sO~O!d!TO!e!TOUaigaihaiiaijaikailainaioaipaiqaisaitaiwai}ai!Rai!Yai!_ai!bai!cai!fai!gai!iai!jai!kai!lai!mai!nai!oai!waiyaizai!Vaiuai!Uai~O!f!UOn`io`ip`iq`is`it`i!g`i!i`i!j`i!k`i!l`i!m`i!n`i!o`i~OU`ig`ih`ii`ij`ik`il`iw`i}`i!R`i!Y`i!_`i!b`i!c`i!w`i~P?iOs_it_i!k_i!l_i!m_i!n_i!o_i~O!g!VOU_ig_ih_ii_ij_ik_il_in_io_ip_iq_iw_i}_i!R_i!Y_i!__i!b_i!c_i!w_i~PAmOs]it]i!l]i!m]i!n]i!o]i~O!k!YOU]ig]ih]ii]ij]ik]il]in]io]ip]iq]iw]i}]i!R]i!Y]i!_]i!b]i!c]i!w]i~PChOs[it[i!m[i!n[i!o[i~O!l!ZOU[ig[ih[ii[ij[ik[il[in[io[ip[iq[iw[i}[i!R[i!Y[i!_[i!b[i!c[i!w[i~PE`O!m![OUZigZihZiiZijZikZilZinZioZipZiqZisZitZiwZi}Zi!RZi!YZi!_Zi!bZi!cZi!nZi!oZi!wZi~OUoOu#tO~Oz#vO~Oz#wO~P$wO!U!nO~OU#gO!V!Si~O!U#{O~Ot#|Ou$OO~O!V!aa~P$wOy$PO!V!aa~OUoO~OU#gO~Ou$SO!]#mO~P$wOt$TOu$SO~O!V!ai~P$wOu$VO!]#mO~P$wO!c!UOy`iz`i!V`iu`i!U`i~P?iO!c$XO!f$XOV`X!d`X!e`X~P,RO!g$YOV_X!d_X!e_X!f_Xy_Xz_X!V_Xu_X!U_X~P.fO!l$[OV[X!d[X!e[X!f[X!g[X!i[X!j[X!k[Xy[Xz[X!V[Xu[X!U[X~P2_O!m$]OVZX!dZX!eZX!fZX!gZX!iZX!jZX!kZX!lZXyZXzZX!VZXuZX!UZX~P4SO!n$^O!o$gOVYX!dYX!eYX!fYX!gYX!iYX!jYX!kYX!lYX!mYXyYXzYX!VYXuYX!UYX~P5tO!g$eOy_iz_i!V_iu_i!U_i~PAmOn!WOo!WOp!WOq!WO!g$eO!i!WO!j!WOy_Xz_X!k_X!l_X!m_X!n_X!o_X!V_Xs_Xt_Xu_X!U_X~O!k$fOV]X!d]X!e]X!f]X!g]X!i]X!j]Xy]Xz]X!V]Xu]X!U]X~P0gO!k$lOy]iz]i!V]iu]i!U]i~PChO!l$mOy[iz[i!V[iu[i!U[i~PE`O!m$nOyZizZi!nZi!oZi!VZisZitZiuZi!UZi~O!k$lOy]Xz]X!l]X!m]X!n]X!o]X!V]Xs]Xt]Xu]X!U]X~O!l$mOy[Xz[X!m[X!n[X!o[X!V[Xs[Xt[Xu[X!U[X~O!m$nOyZXzZX!nZX!oZX!VZXsZXtZXuZX!UZX~O!n$oO!o$pOyYXzYX!VYXsYXtYXuYX!UYX~OW$qO~O}!P!Y!_!ojkih!]Uj~",
|
||||
goto: "4O!wPPP!x!|#^PPP#j$r%c&U'Q(P)R*O+W,a-h.q/v0{PPPPPP0{PPPP0{PPP0{PPPP0{0{P2QP0{P2T2WPPP0{P2`2hP0{P2n2qPPPPPP2tPPPPPPP2}3T3[3b3l3r3xTjOkSiOkQqSSuUvV#Z!d#]#uShOk]$ySUv!d#]#uSiOkQnRQtTQ|VQ}WQ!grQ!u!PS#S!_$qY#a!h#c#q#x$PQ#e!mQ#f!nQ#k!qW#m!t#|$T$WQ#z#lR$Q#{!QgORTVWkr!P!_!h!m!n!q!t#c#l#q#x#{#|$P$T$W$qV#R!^$g$pWfOk!^!_Y$dR!m!q#l$gu$xTVWr!P!h!n!t#c#q#x#{#|$P$T$W$p$qWeOk!^!_Q#Q!]Q#Y$^Y$cR!m!q#l$gQ$t$ou$wTVWr!P!h!n!t#c#q#x#{#|$P$T$W$p$qYdOk!]!^!_Q#P![Q#X$][$bR!m!q#l$^$gQ$s$nw$vTVWr!P!h!n!t#c#q#x#{#|$P$T$W$o$p$q[cOk![!]!^!_Q#O!ZQ#W$[^$jR!m!q#l$]$^$gQ$r$my$uTVWr!P!h!n!t#c#q#x#{#|$P$T$W$n$o$p$q!jbORTVWkr!P!Z![!]!^!_!h!m!n!q!t#c#l#q#x#{#|$P$T$W$[$]$^$g$m$n$o$p$qV!}!Y$f$l`aOk!Y!Z![!]!^!_Q!|!XQ#V$Zb$aR!m!q#l$[$]$^$f$gQ$h$k}$iTVWr!P!h!n!t#c#q#x#{#|$P$T$W$l$m$n$o$p$q!b`OTVWkr!P!X!Y!Z![!]!^!_!h!n!t#c#q#x#{#|$P$T$W$k$l$m$n$o$p$qQ!{!VQ#U$YQ$_$ee$`R!m!q#l$Z$[$]$^$f$g!|_ORTVWkr!P!V!X!Y!Z![!]!^!_!h!m!n!q!t#c#l#q#x#{#|$P$T$W$Y$Z$[$]$^$e$f$g$k$l$m$n$o$p$qQ!z!UR#T$X#Q^ORTVWkr!P!U!V!X!Y!Z![!]!^!_!h!m!n!q!t#c#l#q#x#{#|$P$T$W$X$Y$Z$[$]$^$e$f$g$k$l$m$n$o$p$qQ!S]R!y!T#V[ORTVW]kr!P!T!U!V!X!Y!Z![!]!^!_!h!m!n!q!t#c#l#q#x#{#|$P$T$W$X$Y$Z$[$]$^$e$f$g$k$l$m$n$o$p$q#VZORTVW]kr!P!T!U!V!X!Y!Z![!]!^!_!h!m!n!q!t#c#l#q#x#{#|$P$T$W$X$Y$Z$[$]$^$e$f$g$k$l$m$n$o$p$q#VYORTVW]kr!P!T!U!V!X!Y!Z![!]!^!_!h!m!n!q!t#c#l#q#x#{#|$P$T$W$X$Y$Z$[$]$^$e$f$g$k$l$m$n$o$p$qRwUR{VQyVV#h!o#i#yQ#p!tV$R#|$T$WX#n!t#|$T$WR!Q[R!w!PQ!XaQ$Z$aR$k$iQkOR!akSmPoR!cmQ!eqR#^!eQ!itS#d!i#rR#r!uQvUR!lvQ!pyR#j!pQ#}#pR$U#}",
|
||||
nodeNames: "⚠ Comment Source Statement FieldDefinition FieldPath Identifier Dot Equal Expression DefaultExpression PatchExpression ComposeExpression LogicalOrExpression LogicalAndExpression ComparisonExpression ConcatExpression AdditiveExpression MultiplicativeExpression UnaryExpression PostfixExpression PrimaryExpression Literal String Integer Float True False Regex ComparisonConstraint Lt Lte Gt Gte Object LBrace Semicolon RBrace ArrayConstraint LBracket Ellipsis Comma RBracket Array LetExpression Let FieldDefinitionList In FunctionExpression LParen ParameterList Parameter Colon RParen Arrow MatchExpression Match MatchArm Pattern Underscore ImportExpression Import CallSuffix ArgumentList Bang Minus Star Slash Plus PlusPlus CompareOperator EqualEqual BangEqual AmpAmp PipePipe Amp SlashSlash Default",
|
||||
maxTerm: 87,
|
||||
skippedNodes: [0,1],
|
||||
repeatNodeCount: 7,
|
||||
tokenData: "=T~R!PX^$Upq$Uqr$yrs%Wst&zvw'cxy'pyz'uz{'z{|(P|}(^}!O(c!O!P(h!P!Q(m!Q![+T![!]+n!]!^+s!^!_+x!_!`,V!`!a,l!c!},y!}#O-[#P#Q-a#R#S-f#T#W,y#W#X-y#X#Y,y#Y#Z1Z#Z#],y#]#^3k#^#`,y#`#a6}#a#b8b#b#h,y#h#i:r#i#o,y#o#p<n#p#q<s#q#r=O#y#z$U$f$g$U#BY#BZ$U$IS$I_$U$I|$JO$U$JT$JU$U$KV$KW$U&FU&FV$U~$ZY!w~X^$Upq$U#y#z$U$f$g$U#BY#BZ$U$IS$I_$U$I|$JO$U$JT$JU$U$KV$KW$U&FU&FV$U~%OP!`~!_!`%R~%WO!h~~%ZWOY%WZr%Wrs%ss#O%W#O#P%x#P;'S%W;'S;=`&t<%lO%W~%xOg~~%{RO;'S%W;'S;=`&U;=`O%W~&XXOY%WZr%Wrs%ss#O%W#O#P%x#P;'S%W;'S;=`&t;=`<%l%W<%lO%W~&wP;=`<%l%W~'PSP~OY&zZ;'S&z;'S;=`']<%lO&z~'`P;=`<%l&z~'hP!k~vw'k~'pO!i~~'uO!P~~'zO!T~~(PO!b~~(UP!d~{|(X~(^O!e~~(cOx~~(hO!a~~(mOV~~(rW!c~OY)[Z!P)[!P!Q+O!Q#O)[#O#P)|#P;'S)[;'S;=`*x<%lO)[~)_WOY)[Z!P)[!P!Q)w!Q#O)[#O#P)|#P;'S)[;'S;=`*x<%lO)[~)|Ol~~*PRO;'S)[;'S;=`*Y;=`O)[~*]XOY)[Z!P)[!P!Q)w!Q#O)[#O#P)|#P;'S)[;'S;=`*x;=`<%l)[<%lO)[~*{P;=`<%l)[~+TO!l~~+YQh~!O!P+`!Q![+T~+cP!Q![+f~+kPi~!Q![+f~+sO!S~~+xOt~~+}Pn~!_!`,Q~,VOo~~,[QWP!_!`,b!`!a,g~,gO!g~Q,lO!UQ~,qPp~!_!`,t~,yOq~~-OSU~!Q![,y!c!},y#R#S,y#T#o,y~-aOw~~-fOy~~-mS!Z~U~!Q![,y!c!},y#R#S,y#T#o,y~.OUU~!Q![,y!c!},y#R#S,y#T#X,y#X#Y.b#Y#o,y~.gUU~!Q![,y!c!},y#R#S,y#T#Y,y#Y#Z.y#Z#o,y~/OTU~!Q![,y!c!},y#R#S,y#T#U/_#U#o,y~/dUU~!Q![,y!c!},y#R#S,y#T#i,y#i#j/v#j#o,y~/{UU~!Q![,y!c!},y#R#S,y#T#`,y#`#a0_#a#o,y~0dUU~!Q![,y!c!},y#R#S,y#T#h,y#h#i0v#i#o,y~0}S!m~U~!Q![,y!c!},y#R#S,y#T#o,y~1`TU~!Q![,y!c!},y#R#S,y#T#U1o#U#o,y~1tUU~!Q![,y!c!},y#R#S,y#T#`,y#`#a2W#a#o,y~2]UU~!Q![,y!c!},y#R#S,y#T#g,y#g#h2o#h#o,y~2tUU~!Q![,y!c!},y#R#S,y#T#X,y#X#Y3W#Y#o,y~3_Sk~U~!Q![,y!c!},y#R#S,y#T#o,y~3pVU~!Q![,y!c!},y#R#S,y#T#a,y#a#b4V#b#c6j#c#o,y~4[UU~!Q![,y!c!},y#R#S,y#T#d,y#d#e4n#e#o,y~4sUU~!Q![,y!c!},y#R#S,y#T#c,y#c#d5V#d#o,y~5[UU~!Q![,y!c!},y#R#S,y#T#f,y#f#g5n#g#o,y~5sUU~!Q![,y!c!},y#R#S,y#T#h,y#h#i6V#i#o,y~6^S!]~U~!Q![,y!c!},y#R#S,y#T#o,y~6qS}~U~!Q![,y!c!},y#R#S,y#T#o,y~7SUU~!Q![,y!c!},y#R#S,y#T#X,y#X#Y7f#Y#o,y~7kUU~!Q![,y!c!},y#R#S,y#T#h,y#h#i7}#i#o,y~8US{~U~!Q![,y!c!},y#R#S,y#T#o,y~8gTU~!Q![,y!c!},y#R#S,y#T#U8v#U#o,y~8{UU~!Q![,y!c!},y#R#S,y#T#h,y#h#i9_#i#o,y~9dUU~!Q![,y!c!},y#R#S,y#T#V,y#V#W9v#W#o,y~9{UU~!Q![,y!c!},y#R#S,y#T#[,y#[#]:_#]#o,y~:fS!W~U~!Q![,y!c!},y#R#S,y#T#o,y~:wUU~!Q![,y!c!},y#R#S,y#T#f,y#f#g;Z#g#o,y~;`UU~!Q![,y!c!},y#R#S,y#T#i,y#i#j;r#j#o,y~;wUU~!Q![,y!c!},y#R#S,y#T#X,y#X#Y<Z#Y#o,y~<bSj~U~!Q![,y!c!},y#R#S,y#T#o,y~<sOs~~<vP#p#q<y~=OO!j~~=TOu~",
|
||||
tokenData: "=c~R!PX^$Upq$Uqr$yrs%Wst&zvw'cxy'pyz'uz{'z{|(P|}(^}!O(c!O!P(h!P!Q({!Q![+c![!]+|!]!^,R!^!_,W!_!`,e!`!a,z!c!}-X!}#O-j#P#Q-o#R#S-t#T#W-X#W#X.X#X#Y-X#Y#Z1i#Z#]-X#]#^3y#^#`-X#`#a7]#a#b8p#b#h-X#h#i;Q#i#o-X#o#p<|#p#q=R#q#r=^#y#z$U$f$g$U#BY#BZ$U$IS$I_$U$I|$JO$U$JT$JU$U$KV$KW$U&FU&FV$U~$ZY!y~X^$Upq$U#y#z$U$f$g$U#BY#BZ$U$IS$I_$U$I|$JO$U$JT$JU$U$KV$KW$U&FU&FV$U~%OP!b~!_!`%R~%WO!j~~%ZWOY%WZr%Wrs%ss#O%W#O#P%x#P;'S%W;'S;=`&t<%lO%W~%xOg~~%{RO;'S%W;'S;=`&U;=`O%W~&XXOY%WZr%Wrs%ss#O%W#O#P%x#P;'S%W;'S;=`&t;=`<%l%W<%lO%W~&wP;=`<%l%W~'PSP~OY&zZ;'S&z;'S;=`']<%lO&z~'`P;=`<%l&z~'hP!m~vw'k~'pO!k~~'uO!R~~'zO!V~~(PO!d~~(UP!f~{|(X~(^O!g~~(cOy~~(hO!c~R(mPVP!O!P(pQ(sP!O!P(vQ({OxQ~)QW!e~OY)jZ!P)j!P!Q+^!Q#O)j#O#P*[#P;'S)j;'S;=`+W<%lO)j~)mWOY)jZ!P)j!P!Q*V!Q#O)j#O#P*[#P;'S)j;'S;=`+W<%lO)j~*[Ol~~*_RO;'S)j;'S;=`*h;=`O)j~*kXOY)jZ!P)j!P!Q*V!Q#O)j#O#P*[#P;'S)j;'S;=`+W;=`<%l)j<%lO)j~+ZP;=`<%l)j~+cO!n~~+hQh~!O!P+n!Q![+c~+qP!Q![+t~+yPi~!Q![+t~,RO!U~~,WOt~~,]Pn~!_!`,`~,eOo~~,jQWP!_!`,p!`!a,u~,uO!i~Q,zO!WQ~-PPp~!_!`-S~-XOq~~-^SU~!Q![-X!c!}-X#R#S-X#T#o-X~-oOw~~-tOz~~-{S!]~U~!Q![-X!c!}-X#R#S-X#T#o-X~.^UU~!Q![-X!c!}-X#R#S-X#T#X-X#X#Y.p#Y#o-X~.uUU~!Q![-X!c!}-X#R#S-X#T#Y-X#Y#Z/X#Z#o-X~/^TU~!Q![-X!c!}-X#R#S-X#T#U/m#U#o-X~/rUU~!Q![-X!c!}-X#R#S-X#T#i-X#i#j0U#j#o-X~0ZUU~!Q![-X!c!}-X#R#S-X#T#`-X#`#a0m#a#o-X~0rUU~!Q![-X!c!}-X#R#S-X#T#h-X#h#i1U#i#o-X~1]S!o~U~!Q![-X!c!}-X#R#S-X#T#o-X~1nTU~!Q![-X!c!}-X#R#S-X#T#U1}#U#o-X~2SUU~!Q![-X!c!}-X#R#S-X#T#`-X#`#a2f#a#o-X~2kUU~!Q![-X!c!}-X#R#S-X#T#g-X#g#h2}#h#o-X~3SUU~!Q![-X!c!}-X#R#S-X#T#X-X#X#Y3f#Y#o-X~3mSk~U~!Q![-X!c!}-X#R#S-X#T#o-X~4OVU~!Q![-X!c!}-X#R#S-X#T#a-X#a#b4e#b#c6x#c#o-X~4jUU~!Q![-X!c!}-X#R#S-X#T#d-X#d#e4|#e#o-X~5RUU~!Q![-X!c!}-X#R#S-X#T#c-X#c#d5e#d#o-X~5jUU~!Q![-X!c!}-X#R#S-X#T#f-X#f#g5|#g#o-X~6RUU~!Q![-X!c!}-X#R#S-X#T#h-X#h#i6e#i#o-X~6lS!_~U~!Q![-X!c!}-X#R#S-X#T#o-X~7PS!P~U~!Q![-X!c!}-X#R#S-X#T#o-X~7bUU~!Q![-X!c!}-X#R#S-X#T#X-X#X#Y7t#Y#o-X~7yUU~!Q![-X!c!}-X#R#S-X#T#h-X#h#i8]#i#o-X~8dS}~U~!Q![-X!c!}-X#R#S-X#T#o-X~8uTU~!Q![-X!c!}-X#R#S-X#T#U9U#U#o-X~9ZUU~!Q![-X!c!}-X#R#S-X#T#h-X#h#i9m#i#o-X~9rUU~!Q![-X!c!}-X#R#S-X#T#V-X#V#W:U#W#o-X~:ZUU~!Q![-X!c!}-X#R#S-X#T#[-X#[#]:m#]#o-X~:tS!Y~U~!Q![-X!c!}-X#R#S-X#T#o-X~;VUU~!Q![-X!c!}-X#R#S-X#T#f-X#f#g;i#g#o-X~;nUU~!Q![-X!c!}-X#R#S-X#T#i-X#i#j<Q#j#o-X~<VUU~!Q![-X!c!}-X#R#S-X#T#X-X#X#Y<i#Y#o-X~<pSj~U~!Q![-X!c!}-X#R#S-X#T#o-X~=ROs~~=UP#p#q=X~=^O!l~~=cOu~",
|
||||
tokenizers: [0, 1],
|
||||
topRules: {"Source":[0,2]},
|
||||
tokenPrec: 2481
|
||||
tokenPrec: 2497
|
||||
})
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
import {LRParser} from "@lezer/lr"
|
||||
export const parser = LRParser.deserialize({
|
||||
version: 14,
|
||||
states: "7vQYQPOOO#nQPO'#CaOOQO'#Cr'#CrO$wQPO'#CyO&UQPO'#DOO&^QPO'#DSO&eQPO'#DWO&mQPO'#CqO$wQPO'#DcO&wQPO'#DhOOQO'#Cq'#CqOOQO'#Cp'#CpO&|QPO'#CoO$wQPO'#CoOOQO'#Cn'#CnO)fQPO'#CmO.XQPO'#ClO0]QPO'#CkOOQO'#Cj'#CjO2TQPO'#CiO3xQPO'#ChO5jQPO'#CgO7UQPO'#CfOOQO'#Ce'#CeO7`QPO'#C`O7eQPO'#C_OOQO'#Dz'#DzQYQPOOO8xQPO'#D{O8}QPO,58{OOQO,59e,59eO9VQPO'#CaOOQO,59j,59jO9_QPO,59jOOQO,59n,59nO9gQPO,59nO9oQPO'#EOO9tQPO'#DYO9|QPO,59rO:RQPO'#CqO:`QPO'#D^O:hQQO,59vO:mQPO,59vO:rQPO,59]O:wQPO,59}OOQO,5:S,5:SO:|QPO'#DjOOQO,59[,59[O;TQPO,59[OOQO,59Z,59ZO$wQPO,59YO$wQPO,59XO$wQPO,59WOOQO'#Dr'#DrO$wQPO,59VO$wQPO,59UO$wQPO,59TO$wQPO,59SO$wQPO,59RO$wQPO,59QO$wQPO,58zOOQO,58y,58yOOQO-E7x-E7xOOQO,5:g,5:gOOQO-E7y-E7yO;YQPO1G/UO;bQPO1G/UOOQO1G/U1G/UO;jQPO1G/YO;qQPO1G/YOOQO1G/Y1G/YOOQO,5:j,5:jOOQO-E7|-E7|O$wQPO1G/^O$wQPO,59yO;yQPO,59xO<RQPO,59xO$wQPO1G/bO<ZQQO1G/bOOQO1G.w1G.wO<`QPO1G/iO<jQPO'#DkOOQO,5:U,5:UO<rQPO,5:UOOQO1G.v1G.vOOQO1G.t1G.tO<wQPO1G.sO@_QPO1G.rOAyQPO1G.qOOQO1G.p1G.pOCqQPO1G.oOEfQPO1G.nOFxQPO1G.mOOQO1G.l1G.lOOQO1G.f1G.fOOQO1G.s1G.sOOQO1G.r1G.rOOQO1G.q1G.qOOQO1G.o1G.oOOQO1G.n1G.nOOQO1G.m1G.mOOQO,5:h,5:hOOQO7+$p7+$pOHfQPO7+$pOOQO-E7z-E7zOOQO,5:i,5:iOOQO7+$t7+$tOHnQPO7+$tOOQO-E7{-E7{OOQO7+$x7+$xOOQO1G/e1G/eOHuQPO'#D_OOQO,5:k,5:kOHzQPO1G/dOOQO-E7}-E7}OOQO7+$|7+$|O$wQPO7+$|OOQO'#Df'#DfOISQPO'#DeOOQO7+%T7+%TOIXQPO7+%TOIaQPO,5:VOIhQPO,5:VOOQO1G/p1G/pOOQO<<H[<<H[PIpQPO'#D|OOQO<<H`<<H`P$wQPO'#D}PIuQPO'#EPOOQO<<Hh<<HhO$wQPO,5:POIzQPO<<HoOJUQPO<<HoOOQO<<Ho<<HoOJ^QPO1G/qOOQO1G/k1G/kOOQO,5:l,5:lOOQOAN>ZAN>ZOJeQPOAN>ZOOQO-E8O-E8OOOQOG23uG23uP<cQPO'#EQO$wQPO,59XO$wQPO,59WO$wQPO,59VO$wQPO,59TO$wQPO,59SO$wQPO,59ROJoQPO1G.rOKVQPO'#ClOKjQPO'#CkOL^QPO'#ChOM^QPO'#CgONaQPO'#CfO$wQPO,59WO$wQPO,59UO$wQPO,59QO! jQPO1G.qO!!QQPO'#CkO!#_QPO'#CiO$wQPO,59VO$wQPO,59UO$wQPO,59TO$wQPO,59SO$wQPO,59RO$wQPO,59QO$wQPO,58zO!$[QPO1G.oO!$rQPO1G.nO!%YQPO1G.mO!%zQPO'#CiO!&rQPO'#ChO!'gQPO'#CgO!(XQPO'#CfO!(vQPO'#C`",
|
||||
stateData: "!)Y~O!wOSPOS~OUPOgQOhQOiQOjQOkQOlQOnROoROpROqROsSOwTO{UO!PVO!WWO!]XO!`]O!a]O~OneXoeXpeXqeX!PeX!aeX!beX!ceX!deX!eeX!geX!heX!ieX!jeX!keX!leX!meX~OVlOUeXWTXgeXheXieXjeXkeXleXseXteXweX{eX!WeX!]eX!`eX!ueX~P!gOUYOgQOhQOiQOjQOkQOlQOnROoROpROqROsSOwTO{UO!PVO!WWO!]XO!`]O!a]O~OUoOupO~OyrO~P$wOUoO}|P~OUwO!TyO~P]Og}O~OV!QO!P!OOUcXgcXhcXicXjcXkcXlcXncXocXpcXqcXscXtcXwcX{cX!WcX!]cX!`cX!acX!bcX!ccX!dcX!ecX!gcX!hcX!icX!jcX!kcX!lcX!mcX!ucXxcXycX!TcXucX!ScX~O!b!SO!c!SOUaXgaXhaXiaXjaXkaXlaXnaXoaXpaXqaXsaXtaXwaX{aX!PaX!WaX!]aX!`aX!aaX!daX!eaX!gaX!haX!iaX!jaX!kaX!laX!maX!uaXVaXxaXyaX!TaXuaX!SaX~OU`Xg`Xh`Xi`Xj`Xk`Xl`Xn`Xo`Xp`Xq`Xs`Xt`Xw`X{`X!P`X!W`X!]`X!``X!e`X!g`X!h`X!i`X!j`X!k`X!l`X!m`X!u`Xx`Xy`X!T`Xu`X!S`X~O!a!TO!d!TO~P,OOn!VOo!VOp!VOq!VO!g!VO!h!VOU_Xg_Xh_Xi_Xj_Xk_Xl_Xs_Xt_Xw_X{_X!P_X!W_X!]_X!`_X!a_X!i_X!j_X!k_X!l_X!m_X!u_X~O!e!UO~P.cOU]Xg]Xh]Xi]Xj]Xk]Xl]Xn]Xo]Xp]Xq]Xs]Xt]Xw]X{]X!P]X!W]X!]]X!`]X!a]X!j]X!k]X!l]X!m]X!u]X~O!i!XO~P0dOU[Xg[Xh[Xi[Xj[Xk[Xl[Xn[Xo[Xp[Xq[Xs[Xt[Xw[X{[X!P[X!W[X!][X!`[X!a[X!k[X!l[X!m[X!u[X~O!j!YO~P2[OUZXgZXhZXiZXjZXkZXlZXnZXoZXpZXqZXsZXtZXwZX{ZX!PZX!WZX!]ZX!`ZX!aZX!lZX!mZX!uZX~O!k!ZO~P4POUYXgYXhYXiYXjYXkYXlYXnYXoYXpYXqYXsYXtYXwYX{YX!PYX!WYX!]YX!`YX!aYX!uYX~O!l![O!m!]O~P5qOW!^O~Ot!_OURXgRXhRXiRXjRXkRXlRXnRXoRXpRXqRXsRXwRX{RX!PRX!WRX!]RX!`RX!aRX!uRX~OU!aO~OVlOWTa~OVlOWTX~Ot!cOu!eO~Ox!fOy!hO~Ot!iO~OUoO}|X~O}!kO~O!S!lOVeX!TeX~P!gOx!mO!T!QX~O!U!oO~O!T!pO~O!T!qO~Os!rO~O!T!tO~P$wOU!vO~OUoOu#YO~Ot#ZOu#YO~Oy#^O~P$wOx#_Oy#^O~OU#cO!T!Qa~Ox#eO!T!Qa~O!U#hO~Ou#kO!Z#iO~P$wOx#mO!T!_X~O!T#oO~O!b!SO!c!SOUaigaihaiiaijaikailainaioaipaiqaisaitaiwai{ai!Pai!Wai!]ai!`ai!aai!dai!eai!gai!hai!iai!jai!kai!lai!mai!uaixaiyai!Taiuai!Sai~O!d!TOn`io`ip`iq`is`it`i!e`i!g`i!h`i!i`i!j`i!k`i!l`i!m`i~OU`ig`ih`ii`ij`ik`il`iw`i{`i!P`i!W`i!]`i!``i!a`i!u`i~P?^Os_it_i!i_i!j_i!k_i!l_i!m_i~O!e!UOU_ig_ih_ii_ij_ik_il_in_io_ip_iq_iw_i{_i!P_i!W_i!]_i!`_i!a_i!u_i~PAbOs]it]i!j]i!k]i!l]i!m]i~O!i!XOU]ig]ih]ii]ij]ik]il]in]io]ip]iq]iw]i{]i!P]i!W]i!]]i!`]i!a]i!u]i~PC]Os[it[i!k[i!l[i!m[i~O!j!YOU[ig[ih[ii[ij[ik[il[in[io[ip[iq[iw[i{[i!P[i!W[i!][i!`[i!a[i!u[i~PETO!k!ZOUZigZihZiiZijZikZilZinZioZipZiqZisZitZiwZi{Zi!PZi!WZi!]Zi!`Zi!aZi!lZi!mZi!uZi~OUoOu#pO~Oy#rO~P$wO!S!lO~OU#cO!T!Qi~O!S#vO~Ot#wOu#yO~O!T!_a~P$wOx#zO!T!_a~OUoO~OU#cO~Ou#}O!Z#iO~P$wOt$OOu#}O~O!T!_i~P$wOu$QO!Z#iO~P$wO!a!TOx`iy`i!T`iu`i!S`i~P?^O!a$SO!d$SOV`X!b`X!c`X~P,OO!e$TOV_X!b_X!c_X!d_Xx_Xy_X!T_Xu_X!S_X~P.cO!j$VOV[X!b[X!c[X!d[X!e[X!g[X!h[X!i[Xx[Xy[X!T[Xu[X!S[X~P2[O!k$WOVZX!bZX!cZX!dZX!eZX!gZX!hZX!iZX!jZXxZXyZX!TZXuZX!SZX~P4PO!l$XO!m$bOVYX!bYX!cYX!dYX!eYX!gYX!hYX!iYX!jYX!kYXxYXyYX!TYXuYX!SYX~P5qO!e$`Ox_iy_i!T_iu_i!S_i~PAbOn!VOo!VOp!VOq!VO!e$`O!g!VO!h!VOx_Xy_X!i_X!j_X!k_X!l_X!m_X!T_Xs_Xt_Xu_X!S_X~O!i$aOV]X!b]X!c]X!d]X!e]X!g]X!h]Xx]Xy]X!T]Xu]X!S]X~P0dO!i$gOx]iy]i!T]iu]i!S]i~PC]O!j$hOx[iy[i!T[iu[i!S[i~PETO!k$iOxZiyZi!lZi!mZi!TZisZitZiuZi!SZi~O!i$gOx]Xy]X!j]X!k]X!l]X!m]X!T]Xs]Xt]Xu]X!S]X~O!j$hOx[Xy[X!k[X!l[X!m[X!T[Xs[Xt[Xu[X!S[X~O!k$iOxZXyZX!lZX!mZX!TZXsZXtZXuZX!SZX~O!l$jO!m$kOxYXyYX!TYXsYXtYXuYX!SYX~OW$lO~O{}!W!]!mjkih!ZUj~",
|
||||
goto: "3l!uPPP!v!z#[PPP#h$m%]%}&x'v(w)s*z,S-Y.b/f0jPPPPPP0jPPPP0jPPP0jPPP0jP1nP0jP1q1tPPP0jP1|2UP0jP2[2_PPPPPP2bPPPPPPP2k2q2x3O3Y3`3fTjOkSiOkQqSStUuV#X!c#Z#qShOk]$tSUu!c#Z#qSiOkQnRQsTQ{VQ|WQ!s!OS#Q!^$lY#]!f#_#m#s#zQ#a!kQ#b!lQ#g!oW#i!r#w$O$RQ#u#hR#{#v!OgORTVWk!O!^!f!k!l!o!r#_#h#m#s#v#w#z$O$R$lV#P!]$b$kWfOk!]!^Y$_R!k!o#h$bs$sTVW!O!f!l!r#_#m#s#v#w#z$O$R$k$lWeOk!]!^Q#O![Q#W$XY$^R!k!o#h$bQ$o$js$rTVW!O!f!l!r#_#m#s#v#w#z$O$R$k$lYdOk![!]!^Q!}!ZQ#V$W[$]R!k!o#h$X$bQ$n$iu$qTVW!O!f!l!r#_#m#s#v#w#z$O$R$j$k$l[cOk!Z![!]!^Q!|!YQ#U$V^$eR!k!o#h$W$X$bQ$m$hw$pTVW!O!f!l!r#_#m#s#v#w#z$O$R$i$j$k$l!hbORTVWk!O!Y!Z![!]!^!f!k!l!o!r#_#h#m#s#v#w#z$O$R$V$W$X$b$h$i$j$k$lV!{!X$a$g`aOk!X!Y!Z![!]!^Q!z!WQ#T$Ub$[R!k!o#h$V$W$X$a$bQ$c$f{$dTVW!O!f!l!r#_#m#s#v#w#z$O$R$g$h$i$j$k$l!``OTVWk!O!W!X!Y!Z![!]!^!f!l!r#_#m#s#v#w#z$O$R$f$g$h$i$j$k$lQ!y!UQ#S$TQ$Y$`e$ZR!k!o#h$U$V$W$X$a$b!z_ORTVWk!O!U!W!X!Y!Z![!]!^!f!k!l!o!r#_#h#m#s#v#w#z$O$R$T$U$V$W$X$`$a$b$f$g$h$i$j$k$lQ!x!TR#R$S#O^ORTVWk!O!T!U!W!X!Y!Z![!]!^!f!k!l!o!r#_#h#m#s#v#w#z$O$R$S$T$U$V$W$X$`$a$b$f$g$h$i$j$k$lQ!R]R!w!S#T[ORTVW]k!O!S!T!U!W!X!Y!Z![!]!^!f!k!l!o!r#_#h#m#s#v#w#z$O$R$S$T$U$V$W$X$`$a$b$f$g$h$i$j$k$l#TZORTVW]k!O!S!T!U!W!X!Y!Z![!]!^!f!k!l!o!r#_#h#m#s#v#w#z$O$R$S$T$U$V$W$X$`$a$b$f$g$h$i$j$k$l#TYORTVW]k!O!S!T!U!W!X!Y!Z![!]!^!f!k!l!o!r#_#h#m#s#v#w#z$O$R$S$T$U$V$W$X$`$a$b$f$g$h$i$j$k$lRvURzVQxVV#d!m#e#tQ#l!rV#|#w$O$RX#j!r#w$O$RR!P[R!u!OQ!WaQ$U$[R$f$dQkOR!`kSmPoR!bmQ!dqR#[!dQ!gsS#`!g#nR#n!sQuUR!juQ!nxR#f!nQ#x#lR$P#x",
|
||||
nodeNames: "⚠ Comment Source Statement FieldDefinition FieldPath Identifier Dot Equal Expression DefaultExpression PatchExpression ComposeExpression LogicalOrExpression LogicalAndExpression ComparisonExpression ConcatExpression AdditiveExpression MultiplicativeExpression UnaryExpression PostfixExpression PrimaryExpression Literal String Integer Float True False Regex ComparisonConstraint Lt Lte Gt Gte Object LBrace Semicolon RBrace Array LBracket Comma RBracket LetExpression Let FieldDefinitionList In FunctionExpression LParen ParameterList Parameter Colon RParen Arrow MatchExpression Match MatchArm Pattern Underscore ImportExpression Import CallSuffix ArgumentList Bang Minus Star Slash Plus PlusPlus CompareOperator EqualEqual BangEqual AmpAmp PipePipe Amp SlashSlash Default",
|
||||
maxTerm: 85,
|
||||
states: "8fQYQPOOO#nQPO'#CaOOQO'#Cr'#CrO$wQPO'#CyO&UQPO'#DOO&^QQO'#DXO&hQPO'#DYO&pQPO'#CqO$wQPO'#DeO&zQPO'#DjOOQO'#Cq'#CqOOQO'#Cp'#CpO'PQPO'#CoO$wQPO'#CoOOQO'#Cn'#CnO)iQPO'#CmO.[QPO'#ClO0`QPO'#CkOOQO'#Cj'#CjO2WQPO'#CiO3{QPO'#ChO5mQPO'#CgO7XQPO'#CfOOQO'#Ce'#CeO7cQPO'#C`O7hQPO'#C_OOQO'#D|'#D|QYQPOOO8{QPO'#D}O9QQPO,58{OOQO,59e,59eO9YQPO'#CaOOQO,59j,59jO9bQPO,59jO$wQPO,59nOOQO,59s,59sO9jQPO,59sO9rQPO'#EQO9wQPO'#D[O:PQPO,59tO:UQPO'#CqO:cQPO'#D`O:kQQO,59xO:pQPO,59xO:uQPO,59]O:zQPO,5:POOQO,5:U,5:UO;PQPO'#DlOOQO,59[,59[O;WQPO,59[OOQO,59Z,59ZO$wQPO,59YO$wQPO,59XO$wQPO,59WOOQO'#Dt'#DtO$wQPO,59VO$wQPO,59UO$wQPO,59TO$wQPO,59SO$wQPO,59RO$wQPO,59QO$wQPO,58zOOQO,58y,58yOOQO-E7z-E7zOOQO,5:i,5:iOOQO-E7{-E7{O;]QPO1G/UO;eQPO1G/UOOQO1G/U1G/UO;mQPO1G/YO;uQPO1G/_O;|QPO1G/_OOQO1G/_1G/_OOQO,5:l,5:lOOQO-E8O-E8OO$wQPO1G/`O$wQPO,59{O<UQPO,59zO<^QPO,59zO$wQPO1G/dO<fQQO1G/dOOQO1G.w1G.wO<kQPO1G/kO<uQPO'#DmOOQO,5:W,5:WO<}QPO,5:WOOQO1G.v1G.vOOQO1G.t1G.tO=SQPO1G.sO@jQPO1G.rOBUQPO1G.qOOQO1G.p1G.pOC|QPO1G.oOEqQPO1G.nOGTQPO1G.mOOQO1G.l1G.lOOQO1G.f1G.fOOQO1G.s1G.sOOQO1G.r1G.rOOQO1G.q1G.qOOQO1G.o1G.oOOQO1G.n1G.nOOQO1G.m1G.mOOQO,5:j,5:jOOQO7+$p7+$pOHqQPO7+$pOOQO-E7|-E7|OOQO7+$t7+$tOHyQPO7+$tOOQO,5:k,5:kOOQO7+$y7+$yOIOQPO7+$yOOQO-E7}-E7}OOQO7+$z7+$zOOQO1G/g1G/gOIVQPO'#DaOOQO,5:m,5:mOI[QPO1G/fOOQO-E8P-E8POOQO7+%O7+%OO$wQPO7+%OOOQO'#Dh'#DhOIdQPO'#DgOOQO7+%V7+%VOIiQPO7+%VOIqQPO,5:XOIxQPO,5:XOOQO1G/r1G/rOOQO<<H[<<H[PJQQPO'#EOOOQO<<H`<<H`OOQO<<He<<HeP$wQPO'#EPPJVQPO'#EROOQO<<Hj<<HjO$wQPO,5:ROJ[QPO<<HqOJfQPO<<HqOOQO<<Hq<<HqOJnQPO1G/sOOQO1G/m1G/mOOQO,5:n,5:nOOQOAN>]AN>]OJuQPOAN>]OOQO-E8Q-E8QOOQOG23wG23wP<nQPO'#ESO$wQPO,59XO$wQPO,59WO$wQPO,59VO$wQPO,59TO$wQPO,59SO$wQPO,59ROKPQPO1G.rOKgQPO'#ClOKzQPO'#CkOLnQPO'#ChOMnQPO'#CgONqQPO'#CfO$wQPO,59WO$wQPO,59UO$wQPO,59QO! zQPO1G.qO!!bQPO'#CkO!#oQPO'#CiO$wQPO,59VO$wQPO,59UO$wQPO,59TO$wQPO,59SO$wQPO,59RO$wQPO,59QO$wQPO,58zO!$lQPO1G.oO!%SQPO1G.nO!%jQPO1G.mO!&[QPO'#CiO!'SQPO'#ChO!'wQPO'#CgO!(iQPO'#CfO!)WQPO'#C`",
|
||||
stateData: "!)j~O!yOSPOS~OUPOgQOhQOiQOjQOkQOlQOnROoROpROqROsSOwTO}UO!RVO!YWO!_XO!b]O!c]O~OneXoeXpeXqeX!ReX!ceX!deX!eeX!feX!geX!ieX!jeX!keX!leX!meX!neX!oeX~OVlOUeXWTXgeXheXieXjeXkeXleXseXteXweX}eX!YeX!_eX!beX!weX~P!gOUYOgQOhQOiQOjQOkQOlQOnROoROpROqROsSOwTO}UO!RVO!YWO!_XO!b]O!c]O~OUoOupO~OxrOzsO~P$wOUoO!P!OP~OUxO!VzO~P]Og!OO~OV!RO!R!POUcXgcXhcXicXjcXkcXlcXncXocXpcXqcXscXtcXwcX}cX!YcX!_cX!bcX!ccX!dcX!ecX!fcX!gcX!icX!jcX!kcX!lcX!mcX!ncX!ocX!wcXycXzcX!VcXucX!UcX~O!d!TO!e!TOUaXgaXhaXiaXjaXkaXlaXnaXoaXpaXqaXsaXtaXwaX}aX!RaX!YaX!_aX!baX!caX!faX!gaX!iaX!jaX!kaX!laX!maX!naX!oaX!waXVaXyaXzaX!VaXuaX!UaX~OU`Xg`Xh`Xi`Xj`Xk`Xl`Xn`Xo`Xp`Xq`Xs`Xt`Xw`X}`X!R`X!Y`X!_`X!b`X!g`X!i`X!j`X!k`X!l`X!m`X!n`X!o`X!w`Xy`Xz`X!V`Xu`X!U`X~O!c!UO!f!UO~P,ROn!WOo!WOp!WOq!WO!i!WO!j!WOU_Xg_Xh_Xi_Xj_Xk_Xl_Xs_Xt_Xw_X}_X!R_X!Y_X!__X!b_X!c_X!k_X!l_X!m_X!n_X!o_X!w_X~O!g!VO~P.fOU]Xg]Xh]Xi]Xj]Xk]Xl]Xn]Xo]Xp]Xq]Xs]Xt]Xw]X}]X!R]X!Y]X!_]X!b]X!c]X!l]X!m]X!n]X!o]X!w]X~O!k!YO~P0gOU[Xg[Xh[Xi[Xj[Xk[Xl[Xn[Xo[Xp[Xq[Xs[Xt[Xw[X}[X!R[X!Y[X!_[X!b[X!c[X!m[X!n[X!o[X!w[X~O!l!ZO~P2_OUZXgZXhZXiZXjZXkZXlZXnZXoZXpZXqZXsZXtZXwZX}ZX!RZX!YZX!_ZX!bZX!cZX!nZX!oZX!wZX~O!m![O~P4SOUYXgYXhYXiYXjYXkYXlYXnYXoYXpYXqYXsYXtYXwYX}YX!RYX!YYX!_YX!bYX!cYX!wYX~O!n!]O!o!^O~P5tOW!_O~Ot!`OURXgRXhRXiRXjRXkRXlRXnRXoRXpRXqRXsRXwRX}RX!RRX!YRX!_RX!bRX!cRX!wRX~OU!bO~OVlOWTa~OVlOWTX~Ot!dOu!fO~Oy!hOz!jO~Ot!kO~OUoO!P!OX~O!P!mO~O!U!nOVeX!VeX~P!gOy!oO!V!SX~O!W!qO~O!V!rO~O!V!sO~Os!tO~O!V!vO~P$wOU!xO~OUoOu#[O~Ot#]Ou#[O~Oy#`Oz#_O~Oz#bO~P$wOy#cOz#bO~OU#gO!V!Sa~Oy#iO!V!Sa~O!W#lO~Ou#oO!]#mO~P$wOy#qO!V!aX~O!V#sO~O!d!TO!e!TOUaigaihaiiaijaikailainaioaipaiqaisaitaiwai}ai!Rai!Yai!_ai!bai!cai!fai!gai!iai!jai!kai!lai!mai!nai!oai!waiyaizai!Vaiuai!Uai~O!f!UOn`io`ip`iq`is`it`i!g`i!i`i!j`i!k`i!l`i!m`i!n`i!o`i~OU`ig`ih`ii`ij`ik`il`iw`i}`i!R`i!Y`i!_`i!b`i!c`i!w`i~P?iOs_it_i!k_i!l_i!m_i!n_i!o_i~O!g!VOU_ig_ih_ii_ij_ik_il_in_io_ip_iq_iw_i}_i!R_i!Y_i!__i!b_i!c_i!w_i~PAmOs]it]i!l]i!m]i!n]i!o]i~O!k!YOU]ig]ih]ii]ij]ik]il]in]io]ip]iq]iw]i}]i!R]i!Y]i!_]i!b]i!c]i!w]i~PChOs[it[i!m[i!n[i!o[i~O!l!ZOU[ig[ih[ii[ij[ik[il[in[io[ip[iq[iw[i}[i!R[i!Y[i!_[i!b[i!c[i!w[i~PE`O!m![OUZigZihZiiZijZikZilZinZioZipZiqZisZitZiwZi}Zi!RZi!YZi!_Zi!bZi!cZi!nZi!oZi!wZi~OUoOu#tO~Oz#vO~Oz#wO~P$wO!U!nO~OU#gO!V!Si~O!U#{O~Ot#|Ou$OO~O!V!aa~P$wOy$PO!V!aa~OUoO~OU#gO~Ou$SO!]#mO~P$wOt$TOu$SO~O!V!ai~P$wOu$VO!]#mO~P$wO!c!UOy`iz`i!V`iu`i!U`i~P?iO!c$XO!f$XOV`X!d`X!e`X~P,RO!g$YOV_X!d_X!e_X!f_Xy_Xz_X!V_Xu_X!U_X~P.fO!l$[OV[X!d[X!e[X!f[X!g[X!i[X!j[X!k[Xy[Xz[X!V[Xu[X!U[X~P2_O!m$]OVZX!dZX!eZX!fZX!gZX!iZX!jZX!kZX!lZXyZXzZX!VZXuZX!UZX~P4SO!n$^O!o$gOVYX!dYX!eYX!fYX!gYX!iYX!jYX!kYX!lYX!mYXyYXzYX!VYXuYX!UYX~P5tO!g$eOy_iz_i!V_iu_i!U_i~PAmOn!WOo!WOp!WOq!WO!g$eO!i!WO!j!WOy_Xz_X!k_X!l_X!m_X!n_X!o_X!V_Xs_Xt_Xu_X!U_X~O!k$fOV]X!d]X!e]X!f]X!g]X!i]X!j]Xy]Xz]X!V]Xu]X!U]X~P0gO!k$lOy]iz]i!V]iu]i!U]i~PChO!l$mOy[iz[i!V[iu[i!U[i~PE`O!m$nOyZizZi!nZi!oZi!VZisZitZiuZi!UZi~O!k$lOy]Xz]X!l]X!m]X!n]X!o]X!V]Xs]Xt]Xu]X!U]X~O!l$mOy[Xz[X!m[X!n[X!o[X!V[Xs[Xt[Xu[X!U[X~O!m$nOyZXzZX!nZX!oZX!VZXsZXtZXuZX!UZX~O!n$oO!o$pOyYXzYX!VYXsYXtYXuYX!UYX~OW$qO~O}!P!Y!_!ojkih!]Uj~",
|
||||
goto: "4O!wPPP!x!|#^PPP#j$r%c&U'Q(P)R*O+W,a-h.q/v0{PPPPPP0{PPPP0{PPP0{PPPP0{0{P2QP0{P2T2WPPP0{P2`2hP0{P2n2qPPPPPP2tPPPPPPP2}3T3[3b3l3r3xTjOkSiOkQqSSuUvV#Z!d#]#uShOk]$ySUv!d#]#uSiOkQnRQtTQ|VQ}WQ!grQ!u!PS#S!_$qY#a!h#c#q#x$PQ#e!mQ#f!nQ#k!qW#m!t#|$T$WQ#z#lR$Q#{!QgORTVWkr!P!_!h!m!n!q!t#c#l#q#x#{#|$P$T$W$qV#R!^$g$pWfOk!^!_Y$dR!m!q#l$gu$xTVWr!P!h!n!t#c#q#x#{#|$P$T$W$p$qWeOk!^!_Q#Q!]Q#Y$^Y$cR!m!q#l$gQ$t$ou$wTVWr!P!h!n!t#c#q#x#{#|$P$T$W$p$qYdOk!]!^!_Q#P![Q#X$][$bR!m!q#l$^$gQ$s$nw$vTVWr!P!h!n!t#c#q#x#{#|$P$T$W$o$p$q[cOk![!]!^!_Q#O!ZQ#W$[^$jR!m!q#l$]$^$gQ$r$my$uTVWr!P!h!n!t#c#q#x#{#|$P$T$W$n$o$p$q!jbORTVWkr!P!Z![!]!^!_!h!m!n!q!t#c#l#q#x#{#|$P$T$W$[$]$^$g$m$n$o$p$qV!}!Y$f$l`aOk!Y!Z![!]!^!_Q!|!XQ#V$Zb$aR!m!q#l$[$]$^$f$gQ$h$k}$iTVWr!P!h!n!t#c#q#x#{#|$P$T$W$l$m$n$o$p$q!b`OTVWkr!P!X!Y!Z![!]!^!_!h!n!t#c#q#x#{#|$P$T$W$k$l$m$n$o$p$qQ!{!VQ#U$YQ$_$ee$`R!m!q#l$Z$[$]$^$f$g!|_ORTVWkr!P!V!X!Y!Z![!]!^!_!h!m!n!q!t#c#l#q#x#{#|$P$T$W$Y$Z$[$]$^$e$f$g$k$l$m$n$o$p$qQ!z!UR#T$X#Q^ORTVWkr!P!U!V!X!Y!Z![!]!^!_!h!m!n!q!t#c#l#q#x#{#|$P$T$W$X$Y$Z$[$]$^$e$f$g$k$l$m$n$o$p$qQ!S]R!y!T#V[ORTVW]kr!P!T!U!V!X!Y!Z![!]!^!_!h!m!n!q!t#c#l#q#x#{#|$P$T$W$X$Y$Z$[$]$^$e$f$g$k$l$m$n$o$p$q#VZORTVW]kr!P!T!U!V!X!Y!Z![!]!^!_!h!m!n!q!t#c#l#q#x#{#|$P$T$W$X$Y$Z$[$]$^$e$f$g$k$l$m$n$o$p$q#VYORTVW]kr!P!T!U!V!X!Y!Z![!]!^!_!h!m!n!q!t#c#l#q#x#{#|$P$T$W$X$Y$Z$[$]$^$e$f$g$k$l$m$n$o$p$qRwUR{VQyVV#h!o#i#yQ#p!tV$R#|$T$WX#n!t#|$T$WR!Q[R!w!PQ!XaQ$Z$aR$k$iQkOR!akSmPoR!cmQ!eqR#^!eQ!itS#d!i#rR#r!uQvUR!lvQ!pyR#j!pQ#}#pR$U#}",
|
||||
nodeNames: "⚠ Comment Source Statement FieldDefinition FieldPath Identifier Dot Equal Expression DefaultExpression PatchExpression ComposeExpression LogicalOrExpression LogicalAndExpression ComparisonExpression ConcatExpression AdditiveExpression MultiplicativeExpression UnaryExpression PostfixExpression PrimaryExpression Literal String Integer Float True False Regex ComparisonConstraint Lt Lte Gt Gte Object LBrace Semicolon RBrace ArrayConstraint LBracket Ellipsis Comma RBracket Array LetExpression Let FieldDefinitionList In FunctionExpression LParen ParameterList Parameter Colon RParen Arrow MatchExpression Match MatchArm Pattern Underscore ImportExpression Import CallSuffix ArgumentList Bang Minus Star Slash Plus PlusPlus CompareOperator EqualEqual BangEqual AmpAmp PipePipe Amp SlashSlash Default",
|
||||
maxTerm: 87,
|
||||
skippedNodes: [0,1],
|
||||
repeatNodeCount: 7,
|
||||
tokenData: "=T~R!PX^$Upq$Uqr$yrs%Wst&zvw'cxy'pyz'uz{'z{|(P|}(^}!O(c!O!P(h!P!Q(m!Q![+T![!]+n!]!^+s!^!_+x!_!`,V!`!a,l!c!},y!}#O-[#P#Q-a#R#S-f#T#W,y#W#X-y#X#Y,y#Y#Z1Z#Z#],y#]#^3k#^#`,y#`#a6}#a#b8b#b#h,y#h#i:r#i#o,y#o#p<n#p#q<s#q#r=O#y#z$U$f$g$U#BY#BZ$U$IS$I_$U$I|$JO$U$JT$JU$U$KV$KW$U&FU&FV$U~$ZY!w~X^$Upq$U#y#z$U$f$g$U#BY#BZ$U$IS$I_$U$I|$JO$U$JT$JU$U$KV$KW$U&FU&FV$U~%OP!`~!_!`%R~%WO!h~~%ZWOY%WZr%Wrs%ss#O%W#O#P%x#P;'S%W;'S;=`&t<%lO%W~%xOg~~%{RO;'S%W;'S;=`&U;=`O%W~&XXOY%WZr%Wrs%ss#O%W#O#P%x#P;'S%W;'S;=`&t;=`<%l%W<%lO%W~&wP;=`<%l%W~'PSP~OY&zZ;'S&z;'S;=`']<%lO&z~'`P;=`<%l&z~'hP!k~vw'k~'pO!i~~'uO!P~~'zO!T~~(PO!b~~(UP!d~{|(X~(^O!e~~(cOx~~(hO!a~~(mOV~~(rW!c~OY)[Z!P)[!P!Q+O!Q#O)[#O#P)|#P;'S)[;'S;=`*x<%lO)[~)_WOY)[Z!P)[!P!Q)w!Q#O)[#O#P)|#P;'S)[;'S;=`*x<%lO)[~)|Ol~~*PRO;'S)[;'S;=`*Y;=`O)[~*]XOY)[Z!P)[!P!Q)w!Q#O)[#O#P)|#P;'S)[;'S;=`*x;=`<%l)[<%lO)[~*{P;=`<%l)[~+TO!l~~+YQh~!O!P+`!Q![+T~+cP!Q![+f~+kPi~!Q![+f~+sO!S~~+xOt~~+}Pn~!_!`,Q~,VOo~~,[QWP!_!`,b!`!a,g~,gO!g~Q,lO!UQ~,qPp~!_!`,t~,yOq~~-OSU~!Q![,y!c!},y#R#S,y#T#o,y~-aOw~~-fOy~~-mS!Z~U~!Q![,y!c!},y#R#S,y#T#o,y~.OUU~!Q![,y!c!},y#R#S,y#T#X,y#X#Y.b#Y#o,y~.gUU~!Q![,y!c!},y#R#S,y#T#Y,y#Y#Z.y#Z#o,y~/OTU~!Q![,y!c!},y#R#S,y#T#U/_#U#o,y~/dUU~!Q![,y!c!},y#R#S,y#T#i,y#i#j/v#j#o,y~/{UU~!Q![,y!c!},y#R#S,y#T#`,y#`#a0_#a#o,y~0dUU~!Q![,y!c!},y#R#S,y#T#h,y#h#i0v#i#o,y~0}S!m~U~!Q![,y!c!},y#R#S,y#T#o,y~1`TU~!Q![,y!c!},y#R#S,y#T#U1o#U#o,y~1tUU~!Q![,y!c!},y#R#S,y#T#`,y#`#a2W#a#o,y~2]UU~!Q![,y!c!},y#R#S,y#T#g,y#g#h2o#h#o,y~2tUU~!Q![,y!c!},y#R#S,y#T#X,y#X#Y3W#Y#o,y~3_Sk~U~!Q![,y!c!},y#R#S,y#T#o,y~3pVU~!Q![,y!c!},y#R#S,y#T#a,y#a#b4V#b#c6j#c#o,y~4[UU~!Q![,y!c!},y#R#S,y#T#d,y#d#e4n#e#o,y~4sUU~!Q![,y!c!},y#R#S,y#T#c,y#c#d5V#d#o,y~5[UU~!Q![,y!c!},y#R#S,y#T#f,y#f#g5n#g#o,y~5sUU~!Q![,y!c!},y#R#S,y#T#h,y#h#i6V#i#o,y~6^S!]~U~!Q![,y!c!},y#R#S,y#T#o,y~6qS}~U~!Q![,y!c!},y#R#S,y#T#o,y~7SUU~!Q![,y!c!},y#R#S,y#T#X,y#X#Y7f#Y#o,y~7kUU~!Q![,y!c!},y#R#S,y#T#h,y#h#i7}#i#o,y~8US{~U~!Q![,y!c!},y#R#S,y#T#o,y~8gTU~!Q![,y!c!},y#R#S,y#T#U8v#U#o,y~8{UU~!Q![,y!c!},y#R#S,y#T#h,y#h#i9_#i#o,y~9dUU~!Q![,y!c!},y#R#S,y#T#V,y#V#W9v#W#o,y~9{UU~!Q![,y!c!},y#R#S,y#T#[,y#[#]:_#]#o,y~:fS!W~U~!Q![,y!c!},y#R#S,y#T#o,y~:wUU~!Q![,y!c!},y#R#S,y#T#f,y#f#g;Z#g#o,y~;`UU~!Q![,y!c!},y#R#S,y#T#i,y#i#j;r#j#o,y~;wUU~!Q![,y!c!},y#R#S,y#T#X,y#X#Y<Z#Y#o,y~<bSj~U~!Q![,y!c!},y#R#S,y#T#o,y~<sOs~~<vP#p#q<y~=OO!j~~=TOu~",
|
||||
tokenData: "=c~R!PX^$Upq$Uqr$yrs%Wst&zvw'cxy'pyz'uz{'z{|(P|}(^}!O(c!O!P(h!P!Q({!Q![+c![!]+|!]!^,R!^!_,W!_!`,e!`!a,z!c!}-X!}#O-j#P#Q-o#R#S-t#T#W-X#W#X.X#X#Y-X#Y#Z1i#Z#]-X#]#^3y#^#`-X#`#a7]#a#b8p#b#h-X#h#i;Q#i#o-X#o#p<|#p#q=R#q#r=^#y#z$U$f$g$U#BY#BZ$U$IS$I_$U$I|$JO$U$JT$JU$U$KV$KW$U&FU&FV$U~$ZY!y~X^$Upq$U#y#z$U$f$g$U#BY#BZ$U$IS$I_$U$I|$JO$U$JT$JU$U$KV$KW$U&FU&FV$U~%OP!b~!_!`%R~%WO!j~~%ZWOY%WZr%Wrs%ss#O%W#O#P%x#P;'S%W;'S;=`&t<%lO%W~%xOg~~%{RO;'S%W;'S;=`&U;=`O%W~&XXOY%WZr%Wrs%ss#O%W#O#P%x#P;'S%W;'S;=`&t;=`<%l%W<%lO%W~&wP;=`<%l%W~'PSP~OY&zZ;'S&z;'S;=`']<%lO&z~'`P;=`<%l&z~'hP!m~vw'k~'pO!k~~'uO!R~~'zO!V~~(PO!d~~(UP!f~{|(X~(^O!g~~(cOy~~(hO!c~R(mPVP!O!P(pQ(sP!O!P(vQ({OxQ~)QW!e~OY)jZ!P)j!P!Q+^!Q#O)j#O#P*[#P;'S)j;'S;=`+W<%lO)j~)mWOY)jZ!P)j!P!Q*V!Q#O)j#O#P*[#P;'S)j;'S;=`+W<%lO)j~*[Ol~~*_RO;'S)j;'S;=`*h;=`O)j~*kXOY)jZ!P)j!P!Q*V!Q#O)j#O#P*[#P;'S)j;'S;=`+W;=`<%l)j<%lO)j~+ZP;=`<%l)j~+cO!n~~+hQh~!O!P+n!Q![+c~+qP!Q![+t~+yPi~!Q![+t~,RO!U~~,WOt~~,]Pn~!_!`,`~,eOo~~,jQWP!_!`,p!`!a,u~,uO!i~Q,zO!WQ~-PPp~!_!`-S~-XOq~~-^SU~!Q![-X!c!}-X#R#S-X#T#o-X~-oOw~~-tOz~~-{S!]~U~!Q![-X!c!}-X#R#S-X#T#o-X~.^UU~!Q![-X!c!}-X#R#S-X#T#X-X#X#Y.p#Y#o-X~.uUU~!Q![-X!c!}-X#R#S-X#T#Y-X#Y#Z/X#Z#o-X~/^TU~!Q![-X!c!}-X#R#S-X#T#U/m#U#o-X~/rUU~!Q![-X!c!}-X#R#S-X#T#i-X#i#j0U#j#o-X~0ZUU~!Q![-X!c!}-X#R#S-X#T#`-X#`#a0m#a#o-X~0rUU~!Q![-X!c!}-X#R#S-X#T#h-X#h#i1U#i#o-X~1]S!o~U~!Q![-X!c!}-X#R#S-X#T#o-X~1nTU~!Q![-X!c!}-X#R#S-X#T#U1}#U#o-X~2SUU~!Q![-X!c!}-X#R#S-X#T#`-X#`#a2f#a#o-X~2kUU~!Q![-X!c!}-X#R#S-X#T#g-X#g#h2}#h#o-X~3SUU~!Q![-X!c!}-X#R#S-X#T#X-X#X#Y3f#Y#o-X~3mSk~U~!Q![-X!c!}-X#R#S-X#T#o-X~4OVU~!Q![-X!c!}-X#R#S-X#T#a-X#a#b4e#b#c6x#c#o-X~4jUU~!Q![-X!c!}-X#R#S-X#T#d-X#d#e4|#e#o-X~5RUU~!Q![-X!c!}-X#R#S-X#T#c-X#c#d5e#d#o-X~5jUU~!Q![-X!c!}-X#R#S-X#T#f-X#f#g5|#g#o-X~6RUU~!Q![-X!c!}-X#R#S-X#T#h-X#h#i6e#i#o-X~6lS!_~U~!Q![-X!c!}-X#R#S-X#T#o-X~7PS!P~U~!Q![-X!c!}-X#R#S-X#T#o-X~7bUU~!Q![-X!c!}-X#R#S-X#T#X-X#X#Y7t#Y#o-X~7yUU~!Q![-X!c!}-X#R#S-X#T#h-X#h#i8]#i#o-X~8dS}~U~!Q![-X!c!}-X#R#S-X#T#o-X~8uTU~!Q![-X!c!}-X#R#S-X#T#U9U#U#o-X~9ZUU~!Q![-X!c!}-X#R#S-X#T#h-X#h#i9m#i#o-X~9rUU~!Q![-X!c!}-X#R#S-X#T#V-X#V#W:U#W#o-X~:ZUU~!Q![-X!c!}-X#R#S-X#T#[-X#[#]:m#]#o-X~:tS!Y~U~!Q![-X!c!}-X#R#S-X#T#o-X~;VUU~!Q![-X!c!}-X#R#S-X#T#f-X#f#g;i#g#o-X~;nUU~!Q![-X!c!}-X#R#S-X#T#i-X#i#j<Q#j#o-X~<VUU~!Q![-X!c!}-X#R#S-X#T#X-X#X#Y<i#Y#o-X~<pSj~U~!Q![-X!c!}-X#R#S-X#T#o-X~=ROs~~=UP#p#q=X~=^O!l~~=cOu~",
|
||||
tokenizers: [0, 1],
|
||||
topRules: {"Source":[0,2]},
|
||||
tokenPrec: 2481
|
||||
tokenPrec: 2497
|
||||
})
|
||||
|
||||
@@ -37,41 +37,43 @@ export const
|
||||
LBrace = 35,
|
||||
Semicolon = 36,
|
||||
RBrace = 37,
|
||||
Array = 38,
|
||||
ArrayConstraint = 38,
|
||||
LBracket = 39,
|
||||
Comma = 40,
|
||||
RBracket = 41,
|
||||
LetExpression = 42,
|
||||
Let = 43,
|
||||
FieldDefinitionList = 44,
|
||||
In = 45,
|
||||
FunctionExpression = 46,
|
||||
LParen = 47,
|
||||
ParameterList = 48,
|
||||
Parameter = 49,
|
||||
Colon = 50,
|
||||
RParen = 51,
|
||||
Arrow = 52,
|
||||
MatchExpression = 53,
|
||||
Match = 54,
|
||||
MatchArm = 55,
|
||||
Pattern = 56,
|
||||
Underscore = 57,
|
||||
ImportExpression = 58,
|
||||
Import = 59,
|
||||
CallSuffix = 60,
|
||||
ArgumentList = 61,
|
||||
Bang = 62,
|
||||
Minus = 63,
|
||||
Star = 64,
|
||||
Slash = 65,
|
||||
Plus = 66,
|
||||
PlusPlus = 67,
|
||||
CompareOperator = 68,
|
||||
EqualEqual = 69,
|
||||
BangEqual = 70,
|
||||
AmpAmp = 71,
|
||||
PipePipe = 72,
|
||||
Amp = 73,
|
||||
SlashSlash = 74,
|
||||
Default = 75
|
||||
Ellipsis = 40,
|
||||
Comma = 41,
|
||||
RBracket = 42,
|
||||
Array = 43,
|
||||
LetExpression = 44,
|
||||
Let = 45,
|
||||
FieldDefinitionList = 46,
|
||||
In = 47,
|
||||
FunctionExpression = 48,
|
||||
LParen = 49,
|
||||
ParameterList = 50,
|
||||
Parameter = 51,
|
||||
Colon = 52,
|
||||
RParen = 53,
|
||||
Arrow = 54,
|
||||
MatchExpression = 55,
|
||||
Match = 56,
|
||||
MatchArm = 57,
|
||||
Pattern = 58,
|
||||
Underscore = 59,
|
||||
ImportExpression = 60,
|
||||
Import = 61,
|
||||
CallSuffix = 62,
|
||||
ArgumentList = 63,
|
||||
Bang = 64,
|
||||
Minus = 65,
|
||||
Star = 66,
|
||||
Slash = 67,
|
||||
Plus = 68,
|
||||
PlusPlus = 69,
|
||||
CompareOperator = 70,
|
||||
EqualEqual = 71,
|
||||
BangEqual = 72,
|
||||
AmpAmp = 73,
|
||||
PipePipe = 74,
|
||||
Amp = 75,
|
||||
SlashSlash = 76,
|
||||
Default = 77
|
||||
|
||||
@@ -12,7 +12,7 @@ const parserWithMetadata = parser.configure({
|
||||
Regex: t.regexp,
|
||||
'Integer Float': t.number,
|
||||
Comment: t.lineComment,
|
||||
'Plus Minus Star Slash PlusPlus Equal EqualEqual Bang BangEqual Amp AmpAmp PipePipe SlashSlash Gt Gte Lt Lte Arrow': t.operator,
|
||||
'Plus Minus Star Slash PlusPlus Ellipsis Equal EqualEqual Bang BangEqual Amp AmpAmp PipePipe SlashSlash Gt Gte Lt Lte Arrow': t.operator,
|
||||
'LBrace RBrace': t.brace,
|
||||
'LBracket RBracket': t.squareBracket,
|
||||
'LParen RParen': t.paren,
|
||||
@@ -21,12 +21,14 @@ const parserWithMetadata = parser.configure({
|
||||
indentNodeProp.add({
|
||||
Object: context => context.column(context.node.from) + context.unit,
|
||||
Array: context => context.column(context.node.from) + context.unit,
|
||||
ArrayConstraint: context => context.column(context.node.from) + context.unit,
|
||||
MatchExpression: context => context.column(context.node.from) + context.unit,
|
||||
LetExpression: context => context.column(context.node.from) + context.unit,
|
||||
}),
|
||||
foldNodeProp.add({
|
||||
Object: foldDelimited('{', '}'),
|
||||
Array: foldDelimited('[', ']'),
|
||||
ArrayConstraint: foldDelimited('[', ']'),
|
||||
MatchExpression: foldDelimited('{', '}'),
|
||||
}),
|
||||
],
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import type { EditorView } from '@codemirror/view';
|
||||
|
||||
export interface FormatSuccess {
|
||||
ok: true;
|
||||
source: string;
|
||||
}
|
||||
|
||||
export interface FormatFailure {
|
||||
ok: false;
|
||||
error: string;
|
||||
}
|
||||
|
||||
export type FormatResult = FormatSuccess | FormatFailure;
|
||||
|
||||
export declare function initDecodalFormatter(moduleOrPath?: RequestInfo | URL | Response | BufferSource | WebAssembly.Module): Promise<void>;
|
||||
export declare function formatDecodal(source: string): FormatResult;
|
||||
export declare function formatDecodalCommand(view: EditorView): boolean;
|
||||
@@ -0,0 +1,28 @@
|
||||
import initLanguageTools, { formatSource } from '../wasm/decodal_language_tools.js';
|
||||
|
||||
let initialized = false;
|
||||
|
||||
export async function initDecodalFormatter(moduleOrPath) {
|
||||
await initLanguageTools(moduleOrPath);
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
export function formatDecodal(source) {
|
||||
if (!initialized) {
|
||||
return { ok: false, error: 'Decodal formatter is not initialized' };
|
||||
}
|
||||
return JSON.parse(formatSource(source));
|
||||
}
|
||||
|
||||
export function formatDecodalCommand(view) {
|
||||
const result = formatDecodal(view.state.doc.toString());
|
||||
if (!result.ok) return false;
|
||||
view.dispatch({
|
||||
changes: {
|
||||
from: 0,
|
||||
to: view.state.doc.length,
|
||||
insert: result.source,
|
||||
},
|
||||
});
|
||||
return true;
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import type { EditorView } from '@codemirror/view';
|
||||
|
||||
export interface FormatSuccess {
|
||||
ok: true;
|
||||
source: string;
|
||||
}
|
||||
|
||||
export interface FormatFailure {
|
||||
ok: false;
|
||||
error: string;
|
||||
}
|
||||
|
||||
export type FormatResult = FormatSuccess | FormatFailure;
|
||||
|
||||
export declare function initDecodalFormatter(moduleOrPath?: RequestInfo | URL | Response | BufferSource | WebAssembly.Module): Promise<void>;
|
||||
export declare function formatDecodal(source: string): FormatResult;
|
||||
export declare function formatDecodalCommand(view: EditorView): boolean;
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Decodal source formatting helpers for CodeMirror integrations.
|
||||
*
|
||||
* The formatter is backed by WebAssembly generated from the internal Rust
|
||||
* `decodal-language-tools` crate. Call {@link initDecodalFormatter} once before
|
||||
* using {@link formatDecodal} or {@link formatDecodalCommand}.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
import type { EditorView } from 'npm:@codemirror/view@^6.43.6';
|
||||
import initLanguageTools, { formatSource } from '../wasm/decodal_language_tools.js';
|
||||
|
||||
let initialized = false;
|
||||
|
||||
/** Successful formatter result. */
|
||||
export interface FormatSuccess {
|
||||
/** Indicates that formatting succeeded. */
|
||||
ok: true;
|
||||
/** The formatted Decodal source text. */
|
||||
source: string;
|
||||
}
|
||||
|
||||
/** Failed formatter result. */
|
||||
export interface FormatFailure {
|
||||
/** Indicates that formatting failed. */
|
||||
ok: false;
|
||||
/** Human-readable formatter error. */
|
||||
error: string;
|
||||
}
|
||||
|
||||
/** Result returned by {@link formatDecodal}. */
|
||||
export type FormatResult = FormatSuccess | FormatFailure;
|
||||
|
||||
/**
|
||||
* Initialize the bundled Decodal formatter WebAssembly module.
|
||||
*
|
||||
* Bundlers such as Vite should pass the package wasm asset URL explicitly:
|
||||
*
|
||||
* ```ts
|
||||
* import wasmUrl from 'decodal-codemirror/wasm/decodal_language_tools_bg.wasm?url';
|
||||
* await initDecodalFormatter(wasmUrl);
|
||||
* ```
|
||||
*
|
||||
* @param moduleOrPath - Optional WebAssembly module, bytes, response, URL, or
|
||||
* request accepted by wasm-bindgen's generated initializer.
|
||||
*/
|
||||
export async function initDecodalFormatter(
|
||||
moduleOrPath?: RequestInfo | URL | Response | BufferSource | WebAssembly.Module,
|
||||
): Promise<void> {
|
||||
await initLanguageTools(moduleOrPath);
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a complete Decodal source document.
|
||||
*
|
||||
* @param source - Source text to format.
|
||||
* @returns Either formatted source text or an error message.
|
||||
*/
|
||||
export function formatDecodal(source: string): FormatResult {
|
||||
if (!initialized) {
|
||||
return { ok: false, error: 'Decodal formatter is not initialized' };
|
||||
}
|
||||
return JSON.parse(formatSource(source)) as FormatResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* CodeMirror command that formats the whole document in-place.
|
||||
*
|
||||
* @param view - The editor view whose document should be formatted.
|
||||
* @returns `true` when the document was formatted, otherwise `false`.
|
||||
*/
|
||||
export function formatDecodalCommand(view: EditorView): boolean {
|
||||
const result = formatDecodal(view.state.doc.toString());
|
||||
if (!result.ok) return false;
|
||||
view.dispatch({
|
||||
changes: {
|
||||
from: 0,
|
||||
to: view.state.doc.length,
|
||||
insert: result.source,
|
||||
},
|
||||
});
|
||||
return true;
|
||||
}
|
||||
@@ -1,3 +1,13 @@
|
||||
/**
|
||||
* CodeMirror 6 language support for Decodal.
|
||||
*
|
||||
* This module exports the Decodal language extension, parser metadata, and the
|
||||
* bundled highlight style used by the official Decodal playground. Add
|
||||
* {@link decodal} to a CodeMirror extension set to enable highlighting,
|
||||
* indentation, and folding for Decodal documents.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
import { HighlightStyle, LRLanguage, LanguageSupport, foldNodeProp, indentNodeProp, syntaxHighlighting } from 'npm:@codemirror/language@^6.12.4';
|
||||
import { styleTags, tags as t } from 'npm:@lezer/highlight@^1.2.3';
|
||||
import { parser } from './decodal-parser-jsr.js';
|
||||
@@ -12,7 +22,7 @@ const parserWithMetadata = parser.configure({
|
||||
Regex: t.regexp,
|
||||
'Integer Float': t.number,
|
||||
Comment: t.lineComment,
|
||||
'Plus Minus Star Slash PlusPlus Equal EqualEqual Bang BangEqual Amp AmpAmp PipePipe SlashSlash Gt Gte Lt Lte Arrow': t.operator,
|
||||
'Plus Minus Star Slash PlusPlus Ellipsis Equal EqualEqual Bang BangEqual Amp AmpAmp PipePipe SlashSlash Gt Gte Lt Lte Arrow': t.operator,
|
||||
'LBrace RBrace': t.brace,
|
||||
'LBracket RBracket': t.squareBracket,
|
||||
'LParen RParen': t.paren,
|
||||
@@ -21,12 +31,14 @@ const parserWithMetadata = parser.configure({
|
||||
indentNodeProp.add({
|
||||
Object: context => context.column(context.node.from) + context.unit,
|
||||
Array: context => context.column(context.node.from) + context.unit,
|
||||
ArrayConstraint: context => context.column(context.node.from) + context.unit,
|
||||
MatchExpression: context => context.column(context.node.from) + context.unit,
|
||||
LetExpression: context => context.column(context.node.from) + context.unit,
|
||||
}),
|
||||
foldNodeProp.add({
|
||||
Object: foldDelimited('{', '}'),
|
||||
Array: foldDelimited('[', ']'),
|
||||
ArrayConstraint: foldDelimited('[', ']'),
|
||||
MatchExpression: foldDelimited('{', '}'),
|
||||
}),
|
||||
],
|
||||
@@ -44,6 +56,7 @@ function foldDelimited(open: string, close: string) {
|
||||
};
|
||||
}
|
||||
|
||||
/** Decodal LR language definition for CodeMirror 6. */
|
||||
export const decodalLanguage: LRLanguage = LRLanguage.define({
|
||||
parser: parserWithMetadata,
|
||||
languageData: {
|
||||
@@ -52,6 +65,7 @@ export const decodalLanguage: LRLanguage = LRLanguage.define({
|
||||
},
|
||||
});
|
||||
|
||||
/** Default Decodal highlight style used by the playground. */
|
||||
export const decodalHighlightStyle: HighlightStyle = HighlightStyle.define([
|
||||
{ tag: t.keyword, color: '#93c5fd', fontWeight: '700' },
|
||||
{ tag: t.variableName, color: 'inherit' },
|
||||
@@ -64,10 +78,19 @@ export const decodalHighlightStyle: HighlightStyle = HighlightStyle.define([
|
||||
{ tag: [t.brace, t.squareBracket, t.paren, t.punctuation], color: '#f9a8d4' },
|
||||
]);
|
||||
|
||||
/** Options for {@link decodal}. */
|
||||
export interface DecodalOptions {
|
||||
/** Include the bundled {@link decodalHighlightStyle}. Defaults to `true`. */
|
||||
highlight?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create CodeMirror language support for Decodal.
|
||||
*
|
||||
* @param options - Optional language support options.
|
||||
* @returns A CodeMirror extension bundle containing the Decodal language and,
|
||||
* unless disabled, the default highlight style.
|
||||
*/
|
||||
export function decodal(options: DecodalOptions = {}): LanguageSupport {
|
||||
const { highlight = true } = options;
|
||||
return new LanguageSupport(
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
# wasm-pack output is committed as part of decodal-codemirror.
|
||||
@@ -0,0 +1,38 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
export function formatSource(source: string): string;
|
||||
|
||||
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
||||
|
||||
export interface InitOutput {
|
||||
readonly memory: WebAssembly.Memory;
|
||||
readonly formatSource: (a: number, b: number) => [number, number];
|
||||
readonly __wbindgen_externrefs: WebAssembly.Table;
|
||||
readonly __wbindgen_malloc: (a: number, b: number) => number;
|
||||
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
||||
readonly __wbindgen_free: (a: number, b: number, c: number) => void;
|
||||
readonly __wbindgen_start: () => void;
|
||||
}
|
||||
|
||||
export type SyncInitInput = BufferSource | WebAssembly.Module;
|
||||
|
||||
/**
|
||||
* Instantiates the given `module`, which can either be bytes or
|
||||
* a precompiled `WebAssembly.Module`.
|
||||
*
|
||||
* @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
|
||||
*
|
||||
* @returns {InitOutput}
|
||||
*/
|
||||
export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
|
||||
|
||||
/**
|
||||
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
|
||||
* for everything else, calls `WebAssembly.instantiate` directly.
|
||||
*
|
||||
* @param {{ module_or_path: InitInput | Promise<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
|
||||
*
|
||||
* @returns {Promise<InitOutput>}
|
||||
*/
|
||||
export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;
|
||||
@@ -0,0 +1,209 @@
|
||||
/* @ts-self-types="./decodal_language_tools.d.ts" */
|
||||
|
||||
/**
|
||||
* @param {string} source
|
||||
* @returns {string}
|
||||
*/
|
||||
export function formatSource(source) {
|
||||
let deferred2_0;
|
||||
let deferred2_1;
|
||||
try {
|
||||
const ptr0 = passStringToWasm0(source, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.formatSource(ptr0, len0);
|
||||
deferred2_0 = ret[0];
|
||||
deferred2_1 = ret[1];
|
||||
return getStringFromWasm0(ret[0], ret[1]);
|
||||
} finally {
|
||||
wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
|
||||
}
|
||||
}
|
||||
function __wbg_get_imports() {
|
||||
const import0 = {
|
||||
__proto__: null,
|
||||
__wbindgen_init_externref_table: function() {
|
||||
const table = wasm.__wbindgen_externrefs;
|
||||
const offset = table.grow(4);
|
||||
table.set(0, undefined);
|
||||
table.set(offset + 0, undefined);
|
||||
table.set(offset + 1, null);
|
||||
table.set(offset + 2, true);
|
||||
table.set(offset + 3, false);
|
||||
},
|
||||
};
|
||||
return {
|
||||
__proto__: null,
|
||||
"./decodal_language_tools_bg.js": import0,
|
||||
};
|
||||
}
|
||||
|
||||
function getStringFromWasm0(ptr, len) {
|
||||
return decodeText(ptr >>> 0, len);
|
||||
}
|
||||
|
||||
let cachedUint8ArrayMemory0 = null;
|
||||
function getUint8ArrayMemory0() {
|
||||
if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
|
||||
cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
|
||||
}
|
||||
return cachedUint8ArrayMemory0;
|
||||
}
|
||||
|
||||
function passStringToWasm0(arg, malloc, realloc) {
|
||||
if (realloc === undefined) {
|
||||
const buf = cachedTextEncoder.encode(arg);
|
||||
const ptr = malloc(buf.length, 1) >>> 0;
|
||||
getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
|
||||
WASM_VECTOR_LEN = buf.length;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
let len = arg.length;
|
||||
let ptr = malloc(len, 1) >>> 0;
|
||||
|
||||
const mem = getUint8ArrayMemory0();
|
||||
|
||||
let offset = 0;
|
||||
|
||||
for (; offset < len; offset++) {
|
||||
const code = arg.charCodeAt(offset);
|
||||
if (code > 0x7F) break;
|
||||
mem[ptr + offset] = code;
|
||||
}
|
||||
if (offset !== len) {
|
||||
if (offset !== 0) {
|
||||
arg = arg.slice(offset);
|
||||
}
|
||||
ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
|
||||
const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
|
||||
const ret = cachedTextEncoder.encodeInto(arg, view);
|
||||
|
||||
offset += ret.written;
|
||||
ptr = realloc(ptr, len, offset, 1) >>> 0;
|
||||
}
|
||||
|
||||
WASM_VECTOR_LEN = offset;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
|
||||
cachedTextDecoder.decode();
|
||||
const MAX_SAFARI_DECODE_BYTES = 2146435072;
|
||||
let numBytesDecoded = 0;
|
||||
function decodeText(ptr, len) {
|
||||
numBytesDecoded += len;
|
||||
if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) {
|
||||
cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
|
||||
cachedTextDecoder.decode();
|
||||
numBytesDecoded = len;
|
||||
}
|
||||
return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
|
||||
}
|
||||
|
||||
const cachedTextEncoder = new TextEncoder();
|
||||
|
||||
if (!('encodeInto' in cachedTextEncoder)) {
|
||||
cachedTextEncoder.encodeInto = function (arg, view) {
|
||||
const buf = cachedTextEncoder.encode(arg);
|
||||
view.set(buf);
|
||||
return {
|
||||
read: arg.length,
|
||||
written: buf.length
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
let WASM_VECTOR_LEN = 0;
|
||||
|
||||
let wasmModule, wasmInstance, wasm;
|
||||
function __wbg_finalize_init(instance, module) {
|
||||
wasmInstance = instance;
|
||||
wasm = instance.exports;
|
||||
wasmModule = module;
|
||||
cachedUint8ArrayMemory0 = null;
|
||||
wasm.__wbindgen_start();
|
||||
return wasm;
|
||||
}
|
||||
|
||||
async function __wbg_load(module, imports) {
|
||||
if (typeof Response === 'function' && module instanceof Response) {
|
||||
if (typeof WebAssembly.instantiateStreaming === 'function') {
|
||||
try {
|
||||
return await WebAssembly.instantiateStreaming(module, imports);
|
||||
} catch (e) {
|
||||
const validResponse = module.ok && expectedResponseType(module.type);
|
||||
|
||||
if (validResponse && module.headers.get('Content-Type') !== 'application/wasm') {
|
||||
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
|
||||
|
||||
} else { throw e; }
|
||||
}
|
||||
}
|
||||
|
||||
const bytes = await module.arrayBuffer();
|
||||
return await WebAssembly.instantiate(bytes, imports);
|
||||
} else {
|
||||
const instance = await WebAssembly.instantiate(module, imports);
|
||||
|
||||
if (instance instanceof WebAssembly.Instance) {
|
||||
return { instance, module };
|
||||
} else {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
function expectedResponseType(type) {
|
||||
switch (type) {
|
||||
case 'basic': case 'cors': case 'default': return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function initSync(module) {
|
||||
if (wasm !== undefined) return wasm;
|
||||
|
||||
|
||||
if (module !== undefined) {
|
||||
if (Object.getPrototypeOf(module) === Object.prototype) {
|
||||
({module} = module)
|
||||
} else {
|
||||
console.warn('using deprecated parameters for `initSync()`; pass a single object instead')
|
||||
}
|
||||
}
|
||||
|
||||
const imports = __wbg_get_imports();
|
||||
if (!(module instanceof WebAssembly.Module)) {
|
||||
module = new WebAssembly.Module(module);
|
||||
}
|
||||
const instance = new WebAssembly.Instance(module, imports);
|
||||
return __wbg_finalize_init(instance, module);
|
||||
}
|
||||
|
||||
async function __wbg_init(module_or_path) {
|
||||
if (wasm !== undefined) return wasm;
|
||||
|
||||
|
||||
if (module_or_path !== undefined) {
|
||||
if (Object.getPrototypeOf(module_or_path) === Object.prototype) {
|
||||
({module_or_path} = module_or_path)
|
||||
} else {
|
||||
console.warn('using deprecated parameters for the initialization function; pass a single object instead')
|
||||
}
|
||||
}
|
||||
|
||||
if (module_or_path === undefined) {
|
||||
module_or_path = new URL('decodal_language_tools_bg.wasm', import.meta.url);
|
||||
}
|
||||
const imports = __wbg_get_imports();
|
||||
|
||||
if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) {
|
||||
module_or_path = fetch(module_or_path);
|
||||
}
|
||||
|
||||
const { instance, module } = await __wbg_load(await module_or_path, imports);
|
||||
|
||||
return __wbg_finalize_init(instance, module);
|
||||
}
|
||||
|
||||
export { initSync, __wbg_init as default };
|
||||
Binary file not shown.
@@ -0,0 +1,9 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export const memory: WebAssembly.Memory;
|
||||
export const formatSource: (a: number, b: number) => [number, number];
|
||||
export const __wbindgen_externrefs: WebAssembly.Table;
|
||||
export const __wbindgen_malloc: (a: number, b: number) => number;
|
||||
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
||||
export const __wbindgen_free: (a: number, b: number, c: number) => void;
|
||||
export const __wbindgen_start: () => void;
|
||||
@@ -0,0 +1 @@
|
||||
# wasm-pack output is committed for the browser runtime package.
|
||||
@@ -0,0 +1,36 @@
|
||||
# decodal-wasm
|
||||
|
||||
WebAssembly runtime package for Decodal.
|
||||
|
||||
This package exposes the Decodal evaluator to browsers and other JavaScript runtimes that can load WebAssembly modules.
|
||||
It is generated from the Rust crate in `crates/decodal-wasm` with `wasm-pack` and is used by the official playground.
|
||||
|
||||
Use `decodal-codemirror` separately when you need CodeMirror 6 language support.
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
npm install decodal-wasm
|
||||
```
|
||||
|
||||
JSR:
|
||||
|
||||
```sh
|
||||
deno add jsr:@hare/decodal-wasm
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
import init, { evaluateProject } from 'decodal-wasm';
|
||||
|
||||
await init();
|
||||
|
||||
const result = evaluateProject('main.dcdl', JSON.stringify({
|
||||
'main.dcdl': 'Server = { port = Int default 8080; };',
|
||||
}));
|
||||
|
||||
console.log(JSON.parse(result));
|
||||
```
|
||||
|
||||
The exported functions return JSON strings so callers can handle diagnostics and successful results without depending on Rust data structures.
|
||||
Binary file not shown.
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"name": "@hare/decodal-wasm",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.3",
|
||||
"license": "MIT",
|
||||
"exports": "./decodal_wasm.js",
|
||||
"exports": "./mod.ts",
|
||||
"publish": {
|
||||
"include": [
|
||||
"README.md",
|
||||
"mod.ts",
|
||||
"decodal_wasm.js",
|
||||
"decodal_wasm.d.ts",
|
||||
"decodal_wasm_bg.wasm",
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Browser and JavaScript runtime bindings for the Decodal evaluator.
|
||||
*
|
||||
* This module wraps the wasm-bindgen output generated from the Rust
|
||||
* `decodal-wasm` crate and exposes stable, documented entrypoints for JSR
|
||||
* users. The exported evaluator functions return JSON strings.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
import initWasm, {
|
||||
evaluate as evaluateImpl,
|
||||
evaluateProject as evaluateProjectImpl,
|
||||
initSync as initSyncImpl,
|
||||
} from './decodal_wasm.js';
|
||||
|
||||
/** Input accepted by the wasm-bindgen initializer. */
|
||||
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
||||
|
||||
/** Initialized Decodal WebAssembly exports. */
|
||||
export interface InitOutput {
|
||||
/** Linear memory exported by the WebAssembly module. */
|
||||
readonly memory: WebAssembly.Memory;
|
||||
readonly evaluate: (source: number, len: number) => [number, number, number, number];
|
||||
readonly evaluateProject: (entry: number, entryLen: number, files: number, filesLen: number) => [number, number, number, number];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the Decodal WebAssembly runtime asynchronously.
|
||||
*
|
||||
* @param moduleOrPath - Optional WebAssembly module, bytes, response, URL, or request.
|
||||
* @returns The initialized WebAssembly exports.
|
||||
*/
|
||||
export default async function initDecodalRuntime(moduleOrPath?: InitInput): Promise<InitOutput> {
|
||||
return await initWasm(moduleOrPath) as InitOutput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the Decodal WebAssembly runtime synchronously.
|
||||
*
|
||||
* @param module - Compiled module, bytes, or wasm-bindgen sync initialization input.
|
||||
* @returns The initialized WebAssembly exports.
|
||||
*/
|
||||
export function initSync(module: WebAssembly.Module | BufferSource): InitOutput {
|
||||
return initSyncImpl(module) as InitOutput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate and materialize a single Decodal source string.
|
||||
*
|
||||
* @param source - Decodal source text.
|
||||
* @returns A JSON string containing either `{ ok: true, output }` or `{ ok: false, error }`.
|
||||
*/
|
||||
export function evaluate(source: string): string {
|
||||
return evaluateImpl(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate and materialize a virtual multi-file Decodal project.
|
||||
*
|
||||
* @param entry - Entry point file path to materialize.
|
||||
* @param filesJson - JSON object mapping virtual file paths to source strings.
|
||||
* @returns A JSON string containing either `{ ok: true, output }` or `{ ok: false, error }`.
|
||||
*/
|
||||
export function evaluateProject(entry: string, filesJson: string): string {
|
||||
return evaluateProjectImpl(entry, filesJson);
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "decodal-wasm",
|
||||
"type": "module",
|
||||
"description": "WebAssembly wrapper for evaluating Decodal in browser playgrounds.",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.3",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -2,8 +2,18 @@ import { defineConfig } from 'astro/config';
|
||||
|
||||
export default defineConfig({
|
||||
output: 'static',
|
||||
build: {
|
||||
assets: 'assets',
|
||||
},
|
||||
vite: {
|
||||
resolve: {
|
||||
dedupe: [
|
||||
'@codemirror/language',
|
||||
'@codemirror/state',
|
||||
'@codemirror/view',
|
||||
'@lezer/highlight',
|
||||
'@lezer/lr',
|
||||
],
|
||||
preserveSymlinks: true,
|
||||
},
|
||||
},
|
||||
|
||||
Generated
+18
-2
@@ -16,6 +16,7 @@
|
||||
"astro": "^4.16.18",
|
||||
"codemirror": "^6.0.2",
|
||||
"decodal-codemirror": "file:../../packages/decodal-codemirror",
|
||||
"decodal-wasm": "file:../../packages/decodal-wasm",
|
||||
"marked": "^12.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -24,14 +25,25 @@
|
||||
}
|
||||
},
|
||||
"../../packages/decodal-codemirror": {
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.5",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"devDependencies": {
|
||||
"@codemirror/language": "^6.12.4",
|
||||
"@codemirror/view": "^6.43.6",
|
||||
"@lezer/highlight": "^1.2.3",
|
||||
"@lezer/lr": "^1.4.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@codemirror/language": "^6.12.4",
|
||||
"@codemirror/view": "^6.43.6",
|
||||
"@lezer/highlight": "^1.2.3",
|
||||
"@lezer/lr": "^1.4.10"
|
||||
}
|
||||
},
|
||||
"../../packages/decodal-wasm": {
|
||||
"version": "0.1.3",
|
||||
"license": "MIT OR Apache-2.0"
|
||||
},
|
||||
"node_modules/@astrojs/check": {
|
||||
"version": "0.9.9",
|
||||
"resolved": "https://registry.npmjs.org/@astrojs/check/-/check-0.9.9.tgz",
|
||||
@@ -3250,6 +3262,10 @@
|
||||
"resolved": "../../packages/decodal-codemirror",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/decodal-wasm": {
|
||||
"resolved": "../../packages/decodal-wasm",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/decode-named-character-reference": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz",
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
"dev": "astro dev --host 0.0.0.0",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview --host 0.0.0.0",
|
||||
"build:wasm": "wasm-pack build ../../crates/decodal-wasm --target web --out-dir ../../site/decodal-site/src/wasm --release",
|
||||
"build:runtime": "wasm-pack build ../../crates/decodal-wasm --target web --out-dir ../../packages/decodal-wasm --release && node scripts/prepare-wasm-package.mjs",
|
||||
"build:tools": "wasm-pack build ../../crates/decodal-language-tools --target web --out-dir ../../packages/decodal-codemirror/wasm --release && node scripts/prepare-codemirror-wasm.mjs",
|
||||
"build:wasm": "npm run build:runtime && npm run build:tools",
|
||||
"build:packages": "npm run build:wasm",
|
||||
"deploy": "npm run build && node scripts/deploy-pages.mjs",
|
||||
"deploy:wasm": "npm run build:wasm && npm run deploy"
|
||||
},
|
||||
@@ -20,6 +23,7 @@
|
||||
"astro": "^4.16.18",
|
||||
"codemirror": "^6.0.2",
|
||||
"decodal-codemirror": "file:../../packages/decodal-codemirror",
|
||||
"decodal-wasm": "file:../../packages/decodal-wasm",
|
||||
"marked": "^12.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { rmSync, writeFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
const wasmDir = resolve(import.meta.dirname, '../../../packages/decodal-codemirror/wasm');
|
||||
|
||||
rmSync(resolve(wasmDir, 'README.md'), { force: true });
|
||||
rmSync(resolve(wasmDir, 'package.json'), { force: true });
|
||||
|
||||
writeFileSync(
|
||||
resolve(wasmDir, '.gitignore'),
|
||||
'# wasm-pack output is committed as part of decodal-codemirror.\n',
|
||||
);
|
||||
@@ -0,0 +1,79 @@
|
||||
import { readFileSync, writeFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
const packageDir = resolve(import.meta.dirname, '../../../packages/decodal-wasm');
|
||||
|
||||
writeFileSync(
|
||||
resolve(packageDir, '.gitignore'),
|
||||
'# wasm-pack output is committed for the browser runtime package.\n',
|
||||
);
|
||||
|
||||
const packageJsonPath = resolve(packageDir, 'package.json');
|
||||
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
||||
packageJson.version = '0.1.3';
|
||||
writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
|
||||
|
||||
writeFileSync(
|
||||
resolve(packageDir, 'README.md'),
|
||||
`# decodal-wasm
|
||||
|
||||
WebAssembly runtime package for Decodal.
|
||||
|
||||
This package exposes the Decodal evaluator to browsers and other JavaScript runtimes that can load WebAssembly modules.
|
||||
It is generated from the Rust crate in \`crates/decodal-wasm\` with \`wasm-pack\` and is used by the official playground.
|
||||
|
||||
Use \`decodal-codemirror\` separately when you need CodeMirror 6 language support.
|
||||
|
||||
## Install
|
||||
|
||||
\`\`\`sh
|
||||
npm install decodal-wasm
|
||||
\`\`\`
|
||||
|
||||
JSR:
|
||||
|
||||
\`\`\`sh
|
||||
deno add jsr:@hare/decodal-wasm
|
||||
\`\`\`
|
||||
|
||||
## Usage
|
||||
|
||||
\`\`\`js
|
||||
import init, { evaluateProject } from 'decodal-wasm';
|
||||
|
||||
await init();
|
||||
|
||||
const result = evaluateProject('main.dcdl', JSON.stringify({
|
||||
'main.dcdl': 'Server = { port = Int default 8080; };',
|
||||
}));
|
||||
|
||||
console.log(JSON.parse(result));
|
||||
\`\`\`
|
||||
|
||||
The exported functions return JSON strings so callers can handle diagnostics and successful results without depending on Rust data structures.
|
||||
`,
|
||||
);
|
||||
|
||||
writeFileSync(
|
||||
resolve(packageDir, 'jsr.json'),
|
||||
JSON.stringify(
|
||||
{
|
||||
name: '@hare/decodal-wasm',
|
||||
version: '0.1.3',
|
||||
license: 'MIT',
|
||||
exports: './mod.ts',
|
||||
publish: {
|
||||
include: [
|
||||
'README.md',
|
||||
'mod.ts',
|
||||
'decodal_wasm.js',
|
||||
'decodal_wasm.d.ts',
|
||||
'decodal_wasm_bg.wasm',
|
||||
'package.json',
|
||||
],
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
) + '\n',
|
||||
);
|
||||
@@ -41,7 +41,7 @@ export function highlightDecodalTokens(source, tokens) {
|
||||
|
||||
function tokenClass(kind, text) {
|
||||
if (['let', 'in', 'fn', 'match', 'import', 'default'].includes(kind)) return 'tok-keyword';
|
||||
if (kind === 'ident' && ['String', 'Int', 'Float', 'Bool', 'Array'].includes(text)) return 'tok-type';
|
||||
if (kind === 'ident' && ['String', 'Int', 'Float', 'Bool'].includes(text)) return 'tok-type';
|
||||
if (kind === 'ident') return '';
|
||||
if (['true', 'false'].includes(kind)) return 'tok-literal';
|
||||
if (['int', 'float'].includes(kind)) return 'tok-number';
|
||||
@@ -60,6 +60,7 @@ function tokenClass(kind, text) {
|
||||
'pipe_pipe',
|
||||
'plus',
|
||||
'plus_plus',
|
||||
'ellipsis',
|
||||
'minus',
|
||||
'star',
|
||||
'slash',
|
||||
@@ -255,6 +256,7 @@ function isOperatorStart(char) {
|
||||
|
||||
function readOperator(source, start) {
|
||||
let index = start + 1;
|
||||
if (source.startsWith('...', start)) return start + 3;
|
||||
if ('&|/+'.includes(source[start]) && source[index] === source[start]) return index + 1;
|
||||
if ((source[start] === '<' || source[start] === '>' || source[start] === '=' || source[start] === '!') && source[index] === '=') {
|
||||
index += 1;
|
||||
|
||||
@@ -27,28 +27,14 @@ Production = Server & {
|
||||
|
||||
<section class="home-packages" aria-label="Official packages">
|
||||
<h2>Official packages</h2>
|
||||
<div class="package-links">
|
||||
<a href="https://crates.io/crates/decodal">
|
||||
<span>Rust crate</span>
|
||||
<strong>crates.io/crates/decodal</strong>
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/decodal-wasm">
|
||||
<span>npm package</span>
|
||||
<strong>npmjs.com/package/decodal-wasm</strong>
|
||||
</a>
|
||||
<a href="https://jsr.io/@hare/decodal-wasm@0.1.2">
|
||||
<span>JSR package</span>
|
||||
<strong>jsr.io/@hare/decodal-wasm</strong>
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/decodal-codemirror">
|
||||
<span>CodeMirror package</span>
|
||||
<strong>npmjs.com/package/decodal-codemirror</strong>
|
||||
</a>
|
||||
<a href="https://jsr.io/@hare/decodal-codemirror@0.1.2">
|
||||
<span>CodeMirror on JSR</span>
|
||||
<strong>jsr.io/@hare/decodal-codemirror</strong>
|
||||
</a>
|
||||
</div>
|
||||
<a class="primary-package" href="https://crates.io/crates/decodal">
|
||||
<span>Rust crate</span>
|
||||
<strong>crates.io/crates/decodal</strong>
|
||||
</a>
|
||||
<ul class="package-list">
|
||||
<li>decodal-wasm on <a href="https://jsr.io/@hare/decodal-wasm@0.1.3">jsr</a> / <a href="https://www.npmjs.com/package/decodal-wasm">npm</a></li>
|
||||
<li>decodal-codemirror on <a href="https://jsr.io/@hare/decodal-codemirror@0.1.5">jsr</a> / <a href="https://www.npmjs.com/package/decodal-codemirror">npm</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="home-grid" aria-label="Decodal overview">
|
||||
@@ -62,8 +48,8 @@ Production = Server & {
|
||||
<article class="home-card">
|
||||
<h2>Where it runs</h2>
|
||||
<p>
|
||||
Use the Rust crate for runtime embedding, the WASM package in browsers, CodeMirror/Lezer for the Web editor,
|
||||
and Tree-sitter for general editor integration.
|
||||
Use the Rust crate for runtime embedding, the WASM package in browsers, CodeMirror/Lezer for Web editing
|
||||
and formatting, and Tree-sitter for general editor integration.
|
||||
</p>
|
||||
</article>
|
||||
<article class="home-card">
|
||||
|
||||
@@ -6,16 +6,25 @@ import ManualLayout from '../layouts/ManualLayout.astro';
|
||||
<div class="playground-header">
|
||||
<div>
|
||||
<h1>Playground</h1>
|
||||
<p>Virtual files are evaluated in the browser through WebAssembly. Use import paths such as <code>./schemas/service.dcdl</code>.</p>
|
||||
</div>
|
||||
<div class="playground-actions">
|
||||
<label class="example-picker">
|
||||
<span>Example</span>
|
||||
<select id="example-select" aria-label="Playground example"></select>
|
||||
</label>
|
||||
<button id="load-example" type="button">Load</button>
|
||||
<div class="action-group example-actions">
|
||||
<label class="example-picker">
|
||||
<span>Example</span>
|
||||
<select id="example-select" aria-label="Playground example"></select>
|
||||
</label>
|
||||
<button id="load-example" type="button">Load</button>
|
||||
</div>
|
||||
<span class="action-separator" aria-hidden="true">|</span>
|
||||
<div class="action-group run-actions">
|
||||
<button id="format" disabled>Format</button>
|
||||
<label class="entry-picker">
|
||||
<span>Entry</span>
|
||||
<select id="entry-select" aria-label="Entry point file"></select>
|
||||
</label>
|
||||
<button id="run" disabled>Run</button>
|
||||
</div>
|
||||
<p id="status" class="status">Loading WASM...</p>
|
||||
<button id="run" disabled>Run</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,173 +1,392 @@
|
||||
export const playgroundExamples = [
|
||||
{
|
||||
id: 'service-deployment',
|
||||
title: 'Service deployment',
|
||||
id: 'product-api-production',
|
||||
title: 'Production API deployment',
|
||||
activePath: 'main.dcdl',
|
||||
entryPath: 'main.dcdl',
|
||||
files: {
|
||||
'main.dcdl': `let
|
||||
schema = import "./schemas/service.dcdl";
|
||||
prod = import "./env/prod.dcdl";
|
||||
shared = import "./shared/tags.dcdl";
|
||||
schema = import "./schemas/service.dcdl";
|
||||
env = import "./env/production.dcdl";
|
||||
org = import "./org/platform.dcdl";
|
||||
release = import "./release.dcdl";
|
||||
in
|
||||
schema.Service & prod.ServicePatch & {
|
||||
name = "api";
|
||||
image = "registry.example.com/api:2026-06-24";
|
||||
port = 9000 + 443;
|
||||
public = prod.is_public && 9000 + 443 > 9000;
|
||||
tags = shared.common ++ ["api", "edge"];
|
||||
}
|
||||
schema.Service & env.Production & {
|
||||
name = "checkout-api";
|
||||
image = release.image;
|
||||
owner = org.teams.checkout;
|
||||
port = 8443;
|
||||
replicas = env.capacity.base_replicas + 2;
|
||||
resources = {
|
||||
cpu_milli = env.capacity.cpu_milli;
|
||||
memory_mb = env.capacity.memory_mb;
|
||||
};
|
||||
autoscaling = {
|
||||
enabled = true;
|
||||
min = env.capacity.base_replicas;
|
||||
max = env.capacity.base_replicas * 4;
|
||||
};
|
||||
rollout = {
|
||||
strategy = match release.risk {
|
||||
"high": "canary";
|
||||
"medium": "rolling";
|
||||
_: "all-at-once";
|
||||
};
|
||||
canary_percent = match release.risk {
|
||||
"high": 10;
|
||||
_: 100;
|
||||
};
|
||||
};
|
||||
ingress.hosts = env.domains.public ++ ["checkout.internal.example.com"];
|
||||
labels = org.common_labels ++ ["service:checkout", "tier:edge"];
|
||||
alerts.pager = org.pagers.checkout;
|
||||
}
|
||||
`,
|
||||
'schemas/service.dcdl': `Service = {
|
||||
name = String;
|
||||
image = String;
|
||||
port = Int & > 443 default 8443;
|
||||
replicas = Int & > 0 default 2;
|
||||
public = Bool default false;
|
||||
feature.metrics = Bool default true;
|
||||
feature.tracing = Bool default false;
|
||||
};
|
||||
`,
|
||||
'env/prod.dcdl': `is_public = true;
|
||||
|
||||
ServicePatch = {
|
||||
replicas = 3;
|
||||
feature.tracing = true;
|
||||
};
|
||||
`,
|
||||
'shared/tags.dcdl': `common = ["decodal", "prod"];
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'access-policy',
|
||||
title: 'Access policy',
|
||||
activePath: 'main.dcdl',
|
||||
files: {
|
||||
'main.dcdl': `let
|
||||
policy = import "./policy/base.dcdl";
|
||||
teams = import "./policy/teams.dcdl";
|
||||
env = "prod";
|
||||
in
|
||||
{
|
||||
service = "payments";
|
||||
readers = policy.defaultReaders ++ teams.observability;
|
||||
writers = policy.defaultWriters ++ teams.payments;
|
||||
admins = match env {
|
||||
"prod": policy.breakglassAdmins;
|
||||
_: teams.platform;
|
||||
};
|
||||
audit.required = env == "prod";
|
||||
audit.retention_days = match env {
|
||||
"prod": 365;
|
||||
_: 30;
|
||||
};
|
||||
}
|
||||
`,
|
||||
'policy/base.dcdl': `defaultReaders = ["group:engineering", "group:support"];
|
||||
defaultWriters = ["group:platform"];
|
||||
breakglassAdmins = ["user:oncall-primary", "user:oncall-secondary"];
|
||||
`,
|
||||
'policy/teams.dcdl': `platform = ["group:platform-admins"];
|
||||
payments = ["group:payments-api", "group:payments-sre"];
|
||||
observability = ["group:observability"];
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'edge-worker',
|
||||
title: 'Edge worker config',
|
||||
activePath: 'main.dcdl',
|
||||
files: {
|
||||
'main.dcdl': `let
|
||||
schema = import "./schemas/worker.dcdl";
|
||||
routes = import "./routes.dcdl";
|
||||
production = true;
|
||||
in
|
||||
schema.Worker & {
|
||||
name = "decodal-docs";
|
||||
compatibility_date = "2026-06-15";
|
||||
workers_dev = !production;
|
||||
route.host = routes.primary_host;
|
||||
route.paths = routes.docs_paths ++ routes.playground_paths;
|
||||
cache.ttl_seconds = 60 * 60;
|
||||
cache.bypass = !production;
|
||||
}
|
||||
`,
|
||||
'schemas/worker.dcdl': `Worker = {
|
||||
name = String;
|
||||
compatibility_date = String;
|
||||
workers_dev = Bool default false;
|
||||
route.host = String;
|
||||
cache.ttl_seconds = Int & >= 0 default 300;
|
||||
cache.bypass = Bool default false;
|
||||
};
|
||||
`,
|
||||
'routes.dcdl': `primary_host = "decodal.example.com";
|
||||
docs_paths = ["/docs/*", "/assets/*"];
|
||||
playground_paths = ["/playground/*"];
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'data-pipeline',
|
||||
title: 'Data pipeline',
|
||||
activePath: 'main.dcdl',
|
||||
files: {
|
||||
'main.dcdl': `let
|
||||
schema = import "./schemas/pipeline.dcdl";
|
||||
presets = import "./presets/batch.dcdl";
|
||||
env = "staging";
|
||||
in
|
||||
schema.Pipeline & presets.Batch & {
|
||||
name = "events-rollup";
|
||||
source.topic = "events.raw";
|
||||
sink.table = "analytics.events_daily";
|
||||
workers = match env {
|
||||
"prod": 8;
|
||||
"staging": 3;
|
||||
_: 1;
|
||||
name = String;
|
||||
image = String;
|
||||
owner = String;
|
||||
port = Int & >= 1024 & <= 65535 default 8080;
|
||||
replicas = Int & > 0 default 2;
|
||||
resources = {
|
||||
cpu_milli = Int & >= 100 default 500;
|
||||
memory_mb = Int & >= 128 default 512;
|
||||
};
|
||||
alerts.enabled = env != "dev";
|
||||
transforms = presets.standardTransforms ++ ["dedupe", "aggregate_daily"];
|
||||
}
|
||||
autoscaling = {
|
||||
enabled = Bool default false;
|
||||
min = Int & > 0 default 2;
|
||||
max = Int & > 0 default 4;
|
||||
};
|
||||
rollout = {
|
||||
strategy = String default "rolling";
|
||||
canary_percent = Int & >= 0 & <= 100 default 100;
|
||||
};
|
||||
observability = {
|
||||
metrics = Bool default true;
|
||||
tracing = Bool default true;
|
||||
logs = Bool default true;
|
||||
};
|
||||
network.public = Bool default false;
|
||||
};
|
||||
`,
|
||||
'env/production.dcdl': `capacity = {
|
||||
base_replicas = 6;
|
||||
cpu_milli = 2000;
|
||||
memory_mb = 4096;
|
||||
};
|
||||
|
||||
domains.public = ["checkout.example.com", "api.example.com"];
|
||||
|
||||
Production = {
|
||||
network.public = true;
|
||||
observability = {
|
||||
tracing = true;
|
||||
logs = true;
|
||||
};
|
||||
};
|
||||
`,
|
||||
'org/platform.dcdl': `teams = {
|
||||
checkout = "team:checkout-platform";
|
||||
platform = "team:core-platform";
|
||||
};
|
||||
|
||||
pagers = {
|
||||
checkout = "pagerduty:checkout-primary";
|
||||
};
|
||||
|
||||
common_labels = ["managed-by:decodal", "environment:production"];
|
||||
`,
|
||||
'release.dcdl': `version = "2026.06.15";
|
||||
risk = "high";
|
||||
image = "registry.example.com/checkout-api:2026.06.15";
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'tenant-enterprise-plan',
|
||||
title: 'Enterprise tenant contract',
|
||||
activePath: 'main.dcdl',
|
||||
entryPath: 'main.dcdl',
|
||||
files: {
|
||||
'main.dcdl': `let
|
||||
schema = import "./schemas/tenant.dcdl";
|
||||
plans = import "./plans.dcdl";
|
||||
region = import "./regions/eu.dcdl";
|
||||
customer = import "./customers/acme.dcdl";
|
||||
in
|
||||
schema.Tenant & (plans.Enterprise // region.Europe // customer.Acme // {
|
||||
slug = "acme";
|
||||
display_name = "Acme Manufacturing";
|
||||
limits = {
|
||||
users = customer.seats + 25;
|
||||
projects = 200;
|
||||
storage_gb = 2048;
|
||||
};
|
||||
features = {
|
||||
audit_log = true;
|
||||
sso = true;
|
||||
data_residency = true;
|
||||
};
|
||||
compliance.retention_days = match customer.contract_tier {
|
||||
"regulated": 2555;
|
||||
_: 365;
|
||||
};
|
||||
notifications.channels = customer.notification_channels ++ ["security"];
|
||||
})
|
||||
`,
|
||||
'schemas/tenant.dcdl': `Tenant = {
|
||||
slug = String;
|
||||
display_name = String;
|
||||
plan = String default "standard";
|
||||
limits = {
|
||||
users = Int & > 0 default 25;
|
||||
projects = Int & > 0 default 20;
|
||||
storage_gb = Int & > 0 default 100;
|
||||
};
|
||||
features = {
|
||||
sso = Bool default false;
|
||||
audit_log = Bool default true;
|
||||
data_residency = Bool default false;
|
||||
};
|
||||
compliance = {
|
||||
region = String default "us";
|
||||
retention_days = Int & >= 30 default 90;
|
||||
};
|
||||
billing = {
|
||||
currency = String default "USD";
|
||||
invoice_day = Int & >= 1 & <= 28 default 1;
|
||||
};
|
||||
};
|
||||
`,
|
||||
'plans.dcdl': `Enterprise = {
|
||||
plan = "enterprise";
|
||||
limits = {
|
||||
users = 500;
|
||||
projects = 100;
|
||||
storage_gb = 1024;
|
||||
};
|
||||
features = {
|
||||
sso = true;
|
||||
audit_log = true;
|
||||
};
|
||||
billing.invoice_day = 15;
|
||||
};
|
||||
`,
|
||||
'regions/eu.dcdl': `Europe = {
|
||||
compliance = {
|
||||
region = "eu-west";
|
||||
retention_days = 365;
|
||||
};
|
||||
billing.currency = "EUR";
|
||||
};
|
||||
`,
|
||||
'customers/acme.dcdl': `seats = 475;
|
||||
contract_tier = "regulated";
|
||||
notification_channels = ["email", "slack", "pagerduty"];
|
||||
|
||||
Acme = {
|
||||
billing.invoice_day = 10;
|
||||
};
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'event-pipeline-prod',
|
||||
title: 'Event ingestion pipeline',
|
||||
activePath: 'main.dcdl',
|
||||
entryPath: 'main.dcdl',
|
||||
files: {
|
||||
'main.dcdl': `let
|
||||
schema = import "./schemas/pipeline.dcdl";
|
||||
env = import "./env/prod.dcdl";
|
||||
sources = import "./sources/events.dcdl";
|
||||
sinks = import "./sinks/warehouse.dcdl";
|
||||
transforms = import "./transforms.dcdl";
|
||||
in
|
||||
schema.Pipeline & env.Production & {
|
||||
name = "events-rollup";
|
||||
source = {
|
||||
kafka_cluster = sources.cluster;
|
||||
topic = sources.raw_topic;
|
||||
consumer_group = "analytics-events-rollup";
|
||||
};
|
||||
sink = {
|
||||
table = sinks.daily_events_table;
|
||||
mode = "append";
|
||||
};
|
||||
workers = env.scale.worker_count;
|
||||
batch = {
|
||||
size = env.scale.batch_size;
|
||||
timeout_seconds = 60 + 30;
|
||||
};
|
||||
schedule.cron = "*/15 * * * *";
|
||||
transforms = transforms.standard ++ transforms.privacy ++ ["aggregate_daily"];
|
||||
quality.max_lag_seconds = match env.tier {
|
||||
"gold": 120;
|
||||
"silver": 300;
|
||||
_: 900;
|
||||
};
|
||||
alerts.channels = env.alert_channels;
|
||||
}
|
||||
`,
|
||||
'schemas/pipeline.dcdl': `Pipeline = {
|
||||
name = String;
|
||||
source.topic = String;
|
||||
sink.table = String;
|
||||
workers = Int & > 0 default 1;
|
||||
batch.size = Int & >= 100 default 1000;
|
||||
batch.timeout_seconds = Int & > 0 default 60;
|
||||
alerts.enabled = Bool default false;
|
||||
name = String;
|
||||
source = {
|
||||
kafka_cluster = String;
|
||||
topic = String;
|
||||
consumer_group = String;
|
||||
};
|
||||
sink = {
|
||||
table = String;
|
||||
mode = String default "append";
|
||||
};
|
||||
workers = Int & > 0 default 2;
|
||||
batch = {
|
||||
size = Int & >= 100 default 1000;
|
||||
timeout_seconds = Int & > 0 default 60;
|
||||
};
|
||||
schedule.cron = String;
|
||||
quality.max_lag_seconds = Int & > 0 default 300;
|
||||
dead_letter = {
|
||||
enabled = Bool default true;
|
||||
topic = String default "events.dead_letter";
|
||||
};
|
||||
};
|
||||
`,
|
||||
'presets/batch.dcdl': `standardTransforms = ["parse_json", "validate_schema", "enrich_metadata"];
|
||||
'env/prod.dcdl': `tier = "gold";
|
||||
alert_channels = ["slack:data-platform", "pagerduty:data-primary"];
|
||||
|
||||
Batch = {
|
||||
batch.size = 5000;
|
||||
batch.timeout_seconds = 30 + 30;
|
||||
scale = {
|
||||
worker_count = 12;
|
||||
batch_size = 5000;
|
||||
};
|
||||
|
||||
Production = {
|
||||
dead_letter.enabled = true;
|
||||
};
|
||||
`,
|
||||
'sources/events.dcdl': `cluster = "kafka-prod-use1";
|
||||
raw_topic = "events.raw.v3";
|
||||
`,
|
||||
'sinks/warehouse.dcdl': `daily_events_table = "analytics.events_daily";
|
||||
`,
|
||||
'transforms.dcdl': `standard = ["parse_json", "validate_schema", "dedupe"];
|
||||
privacy = ["redact_ip", "hash_user_id"];
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'edge-cdn-policy',
|
||||
title: 'Edge CDN routing policy',
|
||||
activePath: 'main.dcdl',
|
||||
entryPath: 'main.dcdl',
|
||||
files: {
|
||||
'main.dcdl': `let
|
||||
schema = import "./schemas/edge.dcdl";
|
||||
domains = import "./domains.dcdl";
|
||||
security = import "./security.dcdl";
|
||||
env = "production";
|
||||
in
|
||||
schema.EdgeApplication & security.StrictHeaders & {
|
||||
name = "docs-and-playground";
|
||||
domains = domains.primary ++ domains.aliases;
|
||||
origin = {
|
||||
host = "origin-docs.internal.example.com";
|
||||
port = 443;
|
||||
};
|
||||
tls.mode = "strict";
|
||||
cache = {
|
||||
default_ttl_seconds = 60 * 60;
|
||||
static_ttl_seconds = 60 * 60 * 24;
|
||||
bypass_preview = env != "production";
|
||||
};
|
||||
routing = {
|
||||
docs_paths = ["/docs/*", "/assets/*"];
|
||||
playground_paths = ["/playground/*"];
|
||||
};
|
||||
waf = {
|
||||
enabled = true;
|
||||
rules = security.managed_rules ++ ["block-country:kp", "rate-limit-login"];
|
||||
};
|
||||
}
|
||||
`,
|
||||
'schemas/edge.dcdl': `EdgeApplication = {
|
||||
name = String;
|
||||
origin = {
|
||||
host = String;
|
||||
port = Int & >= 1 & <= 65535 default 443;
|
||||
};
|
||||
tls.mode = String default "strict";
|
||||
cache = {
|
||||
default_ttl_seconds = Int & >= 0 default 300;
|
||||
static_ttl_seconds = Int & >= 0 default 86400;
|
||||
bypass_preview = Bool default false;
|
||||
};
|
||||
waf.enabled = Bool default true;
|
||||
};
|
||||
`,
|
||||
'domains.dcdl': `primary = ["decodal.example.com"];
|
||||
aliases = ["www.decodal.example.com", "docs.decodal.example.com"];
|
||||
`,
|
||||
'security.dcdl': `managed_rules = ["owasp-core", "known-bots", "credential-stuffing"];
|
||||
|
||||
StrictHeaders = {
|
||||
headers = {
|
||||
hsts = "max-age=31536000; includeSubDomains; preload";
|
||||
referrer_policy = "same-origin";
|
||||
frame_options = "DENY";
|
||||
};
|
||||
};
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'diagnostics',
|
||||
title: 'Diagnostics: invalid port',
|
||||
id: 'incident-escalation',
|
||||
title: 'Incident escalation policy',
|
||||
activePath: 'main.dcdl',
|
||||
entryPath: 'main.dcdl',
|
||||
files: {
|
||||
'main.dcdl': `let
|
||||
schema = import "./schemas/service.dcdl";
|
||||
schema = import "./schemas/oncall.dcdl";
|
||||
teams = import "./teams.dcdl";
|
||||
calendar = import "./calendar.dcdl";
|
||||
severity = "sev1";
|
||||
in
|
||||
schema.Service & {
|
||||
name = "broken-api";
|
||||
port = 442;
|
||||
}
|
||||
schema.Policy & {
|
||||
service = "payments";
|
||||
severity = severity;
|
||||
primary = teams.payments_primary;
|
||||
secondary = teams.platform_secondary;
|
||||
notify = teams.payments_primary ++ teams.platform_secondary ++ calendar.executive_watch;
|
||||
response_minutes = match severity {
|
||||
"sev1": 5;
|
||||
"sev2": 15;
|
||||
_: 60;
|
||||
};
|
||||
escalation = {
|
||||
after_minutes = match severity {
|
||||
"sev1": 10;
|
||||
"sev2": 30;
|
||||
_: 120;
|
||||
};
|
||||
targets = teams.platform_secondary ++ calendar.executive_watch;
|
||||
};
|
||||
runbooks = ["runbooks/payments-api", "runbooks/database-failover"];
|
||||
}
|
||||
`,
|
||||
'schemas/service.dcdl': `Service = {
|
||||
name = String;
|
||||
port = Int & > 443 default 8443;
|
||||
replicas = Int & > 0 default 2;
|
||||
'schemas/oncall.dcdl': `Policy = {
|
||||
service = String;
|
||||
severity = String;
|
||||
response_minutes = Int & > 0 default 30;
|
||||
escalation.after_minutes = Int & > 0 default 60;
|
||||
auto_page = Bool default true;
|
||||
create_status_page = Bool default true;
|
||||
};
|
||||
`,
|
||||
'teams.dcdl': `payments_primary = ["user:pay-oncall-a", "user:pay-oncall-b"];
|
||||
platform_secondary = ["user:platform-sre-a", "user:platform-sre-b"];
|
||||
`,
|
||||
'calendar.dcdl': `executive_watch = ["user:vp-engineering", "user:cto"];
|
||||
`,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
import init, { evaluateProject } from '../wasm/decodal_wasm.js';
|
||||
import initRuntime, { evaluateProject } from 'decodal-wasm';
|
||||
import runtimeWasmUrl from 'decodal-wasm/decodal_wasm_bg.wasm?url';
|
||||
import { EditorView, basicSetup } from 'codemirror';
|
||||
import { keymap } from '@codemirror/view';
|
||||
import { decodal } from 'decodal-codemirror';
|
||||
import { formatDecodal, initDecodalFormatter } from 'decodal-codemirror/format';
|
||||
import toolsWasmUrl from 'decodal-codemirror/wasm/decodal_language_tools_bg.wasm?url';
|
||||
import { playgroundExamples } from './playground-examples.js';
|
||||
|
||||
const STORAGE_KEY = 'decodal-playground-project-v1';
|
||||
const STORAGE_KEY = 'decodal-playground-project-v2';
|
||||
const starterProject = playgroundExamples[0];
|
||||
|
||||
const editorHost = document.getElementById('editor');
|
||||
const output = document.getElementById('output');
|
||||
const run = document.getElementById('run');
|
||||
const formatButton = document.getElementById('format');
|
||||
const status = document.getElementById('status');
|
||||
const fileTree = document.getElementById('file-tree');
|
||||
const activeFile = document.getElementById('active-file');
|
||||
const newFile = document.getElementById('new-file');
|
||||
const deleteFile = document.getElementById('delete-file');
|
||||
const exampleSelect = document.getElementById('example-select');
|
||||
const entrySelect = document.getElementById('entry-select');
|
||||
const loadExample = document.getElementById('load-example');
|
||||
|
||||
const project = loadProject();
|
||||
@@ -86,6 +91,7 @@ for (const example of playgroundExamples) {
|
||||
exampleSelect.value = starterProject.id;
|
||||
|
||||
setActiveFile(project.activePath);
|
||||
updateRunLabel();
|
||||
renderFileTree();
|
||||
|
||||
function loadProject() {
|
||||
@@ -94,12 +100,22 @@ function loadProject() {
|
||||
if (stored && stored.files && typeof stored.activePath === 'string') {
|
||||
const files = normalizeFiles(stored.files);
|
||||
const activePath = files[stored.activePath] === undefined ? Object.keys(files)[0] : stored.activePath;
|
||||
if (activePath) return { files, activePath };
|
||||
if (activePath) {
|
||||
const storedEntryPath = typeof stored.entryPath === 'string' ? normalizePath(stored.entryPath) : '';
|
||||
const entryPath = files[storedEntryPath] === undefined
|
||||
? files['main.dcdl'] === undefined ? activePath : 'main.dcdl'
|
||||
: storedEntryPath;
|
||||
return { files, activePath, entryPath };
|
||||
}
|
||||
}
|
||||
} catch (_error) {
|
||||
// Fall back to the starter project.
|
||||
}
|
||||
return { files: cloneFiles(starterProject.files), activePath: starterProject.activePath };
|
||||
return {
|
||||
files: cloneFiles(starterProject.files),
|
||||
activePath: starterProject.activePath,
|
||||
entryPath: starterProject.entryPath ?? starterProject.activePath,
|
||||
};
|
||||
}
|
||||
|
||||
function cloneFiles(files) {
|
||||
@@ -121,10 +137,13 @@ function saveProject() {
|
||||
|
||||
function loadExampleProject(exampleId) {
|
||||
const example = playgroundExamples.find((item) => item.id === exampleId) ?? starterProject;
|
||||
project.files = cloneFiles(example.files);
|
||||
project.activePath = example.activePath;
|
||||
const files = cloneFiles(example.files);
|
||||
project.files = {};
|
||||
project.activePath = '';
|
||||
project.entryPath = example.entryPath ?? example.activePath;
|
||||
project.files = files;
|
||||
exampleSelect.value = example.id;
|
||||
setActiveFile(project.activePath);
|
||||
setActiveFile(example.activePath);
|
||||
output.textContent = '';
|
||||
output.classList.remove('error');
|
||||
execute();
|
||||
@@ -165,17 +184,56 @@ function setActiveFile(path) {
|
||||
activeFile.textContent = normalized;
|
||||
deleteFile.disabled = Object.keys(project.files).length <= 1;
|
||||
renderFileTree();
|
||||
updateRunLabel();
|
||||
saveProject();
|
||||
}
|
||||
|
||||
function updateEntrySelect() {
|
||||
const paths = Object.keys(project.files).sort();
|
||||
const entryPath = project.files[project.entryPath] === undefined ? project.activePath : project.entryPath;
|
||||
project.entryPath = entryPath;
|
||||
entrySelect.replaceChildren(
|
||||
...paths.map((path) => {
|
||||
const option = document.createElement('option');
|
||||
option.value = path;
|
||||
option.textContent = path;
|
||||
return option;
|
||||
}),
|
||||
);
|
||||
entrySelect.value = entryPath;
|
||||
}
|
||||
|
||||
function updateRunLabel() {
|
||||
updateEntrySelect();
|
||||
run.textContent = 'Run';
|
||||
run.title = `Materialize ${project.entryPath}`;
|
||||
}
|
||||
|
||||
function execute() {
|
||||
project.files[project.activePath] = getEditorText();
|
||||
const entryPath = project.files[project.entryPath] === undefined ? project.activePath : project.entryPath;
|
||||
project.entryPath = entryPath;
|
||||
updateRunLabel();
|
||||
saveProject();
|
||||
const result = JSON.parse(evaluateProject(project.activePath, JSON.stringify(project.files)));
|
||||
const result = JSON.parse(evaluateProject(entryPath, JSON.stringify(project.files)));
|
||||
output.textContent = result.ok ? result.output : result.error;
|
||||
output.classList.toggle('error', !result.ok);
|
||||
}
|
||||
|
||||
function formatActiveFile() {
|
||||
const result = formatDecodal(getEditorText());
|
||||
if (!result.ok) {
|
||||
output.textContent = result.error;
|
||||
output.classList.add('error');
|
||||
return;
|
||||
}
|
||||
setEditorText(result.source);
|
||||
project.files[project.activePath] = result.source;
|
||||
saveProject();
|
||||
output.textContent = 'Formatted.';
|
||||
output.classList.remove('error');
|
||||
}
|
||||
|
||||
function renderFileTree() {
|
||||
const tree = buildTree(Object.keys(project.files).sort());
|
||||
fileTree.replaceChildren(renderTreeList(tree.children));
|
||||
@@ -227,19 +285,27 @@ function compareNodes(a, b) {
|
||||
}
|
||||
|
||||
try {
|
||||
await init();
|
||||
await Promise.all([initRuntime(runtimeWasmUrl), initDecodalFormatter(toolsWasmUrl)]);
|
||||
run.disabled = false;
|
||||
formatButton.disabled = false;
|
||||
status.textContent = '';
|
||||
execute();
|
||||
} catch (error) {
|
||||
status.textContent = `Failed to load WASM: ${error?.message ?? error}`;
|
||||
}
|
||||
|
||||
entrySelect.addEventListener('change', () => {
|
||||
const path = normalizePath(entrySelect.value);
|
||||
if (project.files[path] === undefined) return;
|
||||
project.entryPath = path;
|
||||
updateRunLabel();
|
||||
saveProject();
|
||||
});
|
||||
run.addEventListener('click', execute);
|
||||
formatButton.addEventListener('click', formatActiveFile);
|
||||
loadExample.addEventListener('click', () => {
|
||||
const example = playgroundExamples.find((item) => item.id === exampleSelect.value);
|
||||
if (!example) return;
|
||||
if (!confirm(`Load example "${example.title}"? This replaces the current virtual files.`)) return;
|
||||
loadExampleProject(example.id);
|
||||
});
|
||||
newFile.addEventListener('click', () => {
|
||||
@@ -256,5 +322,8 @@ deleteFile.addEventListener('click', () => {
|
||||
if (Object.keys(project.files).length <= 1) return;
|
||||
if (!confirm(`Delete ${project.activePath}?`)) return;
|
||||
delete project.files[project.activePath];
|
||||
if (project.files[project.entryPath] === undefined) {
|
||||
project.entryPath = Object.keys(project.files).sort()[0];
|
||||
}
|
||||
setActiveFile(Object.keys(project.files).sort()[0]);
|
||||
});
|
||||
|
||||
@@ -293,11 +293,23 @@ main.playground {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
gap: 10px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.example-picker {
|
||||
.action-group {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.action-separator {
|
||||
color: var(--border);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.example-picker,
|
||||
.entry-picker {
|
||||
align-items: center;
|
||||
color: var(--muted);
|
||||
display: flex;
|
||||
@@ -307,17 +319,23 @@ main.playground {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.example-picker select {
|
||||
.example-picker select,
|
||||
.entry-picker select {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
color: var(--text);
|
||||
font: inherit;
|
||||
min-height: 28px;
|
||||
min-width: 180px;
|
||||
padding: 7px 10px;
|
||||
padding: 4px 10px;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.entry-picker select {
|
||||
max-width: 220px;
|
||||
}
|
||||
|
||||
button {
|
||||
background: var(--link);
|
||||
border: 0;
|
||||
@@ -325,7 +343,8 @@ button {
|
||||
color: var(--topbar-text);
|
||||
cursor: pointer;
|
||||
font-weight: 700;
|
||||
padding: 8px 14px;
|
||||
min-height: 28px;
|
||||
padding: 4px 14px;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
@@ -519,45 +538,57 @@ button:disabled {
|
||||
}
|
||||
|
||||
.home-packages {
|
||||
align-items: flex-start;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 18px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.9rem 1.4rem;
|
||||
margin-bottom: 1.5rem;
|
||||
padding: 1.4rem;
|
||||
padding: 1.2rem 1.4rem;
|
||||
}
|
||||
|
||||
.home-packages h2 {
|
||||
margin-top: 0;
|
||||
flex-basis: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.package-links {
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.package-links a {
|
||||
.primary-package {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 14px;
|
||||
display: block;
|
||||
padding: 1rem;
|
||||
flex: 0 1 320px;
|
||||
padding: 0.9rem 1rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.package-links span {
|
||||
.primary-package span {
|
||||
color: var(--muted);
|
||||
display: block;
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 0.3rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.package-links strong {
|
||||
.primary-package strong {
|
||||
color: var(--text);
|
||||
display: block;
|
||||
font-size: 0.95rem;
|
||||
font-size: 1.15rem;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.package-list {
|
||||
columns: 1;
|
||||
flex: 1 1 320px;
|
||||
margin: 0;
|
||||
padding-left: 1.2rem;
|
||||
}
|
||||
|
||||
.package-list li {
|
||||
break-inside: avoid;
|
||||
margin: 0.25rem 0;
|
||||
}
|
||||
|
||||
.home-grid {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
@@ -604,10 +635,12 @@ button:disabled {
|
||||
}
|
||||
.playground-shell,
|
||||
.home-grid,
|
||||
.home-example,
|
||||
.package-links {
|
||||
.home-example {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.package-list {
|
||||
columns: 1;
|
||||
}
|
||||
.hero {
|
||||
padding-top: 3rem;
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
# wasm-pack output is committed for the playground build.
|
||||
@@ -1,82 +0,0 @@
|
||||
# Decodal
|
||||
|
||||
Decodal is a small deterministic DSL for describing, composing, validating, and materializing structured data.
|
||||
|
||||
It is designed around a lightweight Rust library:
|
||||
|
||||
- host-supplied imports through `SourceLoader`
|
||||
- no filesystem access in the library core
|
||||
- concrete and abstract values with constraints and defaults
|
||||
- deterministic expression evaluation
|
||||
- optional regex support behind a Cargo feature
|
||||
- browser playground support through WebAssembly
|
||||
|
||||
## Library crate
|
||||
|
||||
Embedded hosts should depend on `decodal` and provide imports with a `SourceLoader`.
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
decodal = "0.1"
|
||||
```
|
||||
|
||||
## Derive support
|
||||
|
||||
For embedded Rust applications, Decodal can generate a schema and typed decoder from a Rust struct with the `derive` feature.
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
decodal = { version = "0.1", features = ["derive"] }
|
||||
```
|
||||
|
||||
```rust
|
||||
use decodal::{Decodal, DecodalDecode, DecodalSchema, Engine};
|
||||
|
||||
#[derive(Decodal)]
|
||||
struct Service {
|
||||
name: String,
|
||||
#[decodal(gt = 443, default = 8443)]
|
||||
port: i64,
|
||||
#[decodal(rename = "feature.enable", default = true)]
|
||||
feature_enabled: bool,
|
||||
}
|
||||
```
|
||||
|
||||
The derive implements:
|
||||
|
||||
- `DecodalSchema`, which produces a host schema for `Engine::bind_global`
|
||||
- `DecodalDecode`, which converts materialized `Data` into the Rust struct
|
||||
|
||||
## CLI
|
||||
|
||||
A standalone CLI is kept in this repository as the `decodal-cli` workspace package.
|
||||
It builds a `decodal` binary, but it is not the primary crates.io package.
|
||||
|
||||
Run a Decodal file from the repository:
|
||||
|
||||
```sh
|
||||
cargo run -q -p decodal-cli -- examples/advanced/main.dcdl
|
||||
```
|
||||
|
||||
Enable optional regex support when needed:
|
||||
|
||||
```sh
|
||||
cargo run -q -p decodal-cli --features regex -- examples/regex/main.dcdl
|
||||
```
|
||||
|
||||
## Web playground
|
||||
|
||||
The static documentation site and browser playground live under:
|
||||
|
||||
```text
|
||||
site/decodal-site/
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Licensed under either of:
|
||||
|
||||
- Apache License, Version 2.0
|
||||
- MIT license
|
||||
|
||||
at your option.
|
||||
Binary file not shown.
Reference in New Issue
Block a user