Compare commits

25 Commits
Author SHA1 Message Date
Hare 46b2b3b1b6 Add required array element constraints 2026-08-11 17:24:04 +09:00
Hare 5a05da6fe4 Use non-underscore Astro asset directory 2026-07-10 04:36:22 +09:00
Hare aa0f464320 Bump CodeMirror package to 0.1.5 2026-07-10 04:30:38 +09:00
Hare fc1f3e0b25 Improve JSR package metadata 2026-07-10 04:19:00 +09:00
Hare 8eac5c5cb4 Bundle language tools with CodeMirror package 2026-07-09 21:25:04 +09:00
Hare 59cb8f31e4 Simplify package link labels 2026-07-09 20:50:09 +09:00
Hare 4eee2cd2c9 Make package links flex layout 2026-07-09 20:45:16 +09:00
Hare 44b6041e60 Compact package links on landing page 2026-07-09 20:36:57 +09:00
Hare a95afe0463 Align playground control heights 2026-07-09 19:36:15 +09:00
Hare 43ab6244ef Reduce button vertical padding 2026-07-09 19:34:33 +09:00
Hare 1d4503b015 Simplify playground action separator 2026-07-09 19:33:48 +09:00
Hare 250e29e00c Remove playground helper copy 2026-07-09 19:29:22 +09:00
Hare 1080081cec Reset state before loading playground examples 2026-07-09 19:27:19 +09:00
Hare 43f692e75c Add playground entry selector 2026-07-09 19:21:12 +09:00
Hare 5b26d071ca Run playground examples from entrypoint 2026-07-09 19:12:18 +09:00
Hare a653f0b7c6 Nest repeated playground fields 2026-07-09 19:06:28 +09:00
Hare 384d17b539 Replace playground examples 2026-07-09 19:02:11 +09:00
Hare 09a84264ab Format playground examples 2026-07-09 18:54:29 +09:00
Hare 0a8c261545 Load playground wasm assets through Vite 2026-07-09 18:50:26 +09:00
Hare a8a46998a8 Make build:wasm build all wasm packages 2026-07-09 18:46:24 +09:00
Hare e4a9f80617 Dedupe CodeMirror dependencies 2026-07-09 18:43:09 +09:00
Hare 0e2a7b35d2 Add language tools package 2026-07-09 18:34:41 +09:00
Hare ac99c4cb4e Revert "Add Decodal formatter"
This reverts commit 22f6bc1ab9.
2026-07-09 17:46:24 +09:00
Hare 22f6bc1ab9 Add Decodal formatter 2026-07-09 17:39:32 +09:00
Hare 16006d000a Move wasm package under packages 2026-07-09 04:13:08 +09:00
73 changed files with 8548 additions and 5362 deletions
Generated
+51
View File
@@ -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"
+1
View File
@@ -4,6 +4,7 @@ members = [
"crates/decodal-cli",
"crates/decodal-wasm",
"crates/decodal-derive",
"crates/decodal-language-tools",
]
resolver = "2"
+3
View File
@@ -50,6 +50,9 @@ pub enum Expr {
Ident(String),
Object(Vec<Field>),
Array(Vec<ExprId>),
ArrayConstraint {
item: ExprId,
},
Let {
bindings: Vec<Field>,
body: ExprId,
+37 -4
View File
@@ -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(
+28 -2
View File
@@ -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
View File
@@ -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]");
+22 -1
View File
@@ -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);
}
}
+25
View File
@@ -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());
}
}
+1 -1
View File
@@ -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)]
+2 -2
View File
@@ -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())
}
}
+71
View File
@@ -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,
}],
}
);
}
+25
View File
@@ -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
+870
View File
@@ -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"));
}
}
+25 -4
View File
@@ -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.
+5 -1
View File
@@ -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.
+4
View File
@@ -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 は必要になった時点で追加する。
+15 -11
View File
@@ -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` は制約ではない。
+17
View File
@@ -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
+2
View File
@@ -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 ;
+1
View File
@@ -121,6 +121,7 @@ false
default fallback 指定
=> 関数
. フィールド参照 / ドットパス定義
... 配列の要素制約
```
演算子の優先順位は [合成演算子](./operators.md) で定義する。
+3 -2
View File
@@ -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) も参照する。
+3
View File
@@ -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))))))))
+9
View File
@@ -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
View File
@@ -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
View File
@@ -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
+5487 -4941
View File
File diff suppressed because it is too large Load Diff
+11
View File
@@ -0,0 +1,11 @@
let
Service = {
name = String;
enabled = Bool default true;
};
Services = [...Service];
in
Services & [
{ name = "api"; },
{ name = "worker"; enabled = false; },
]
+22 -2
View File
@@ -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);
```
+14 -2
View File
@@ -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
View File
@@ -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"
}
}
+20 -4
View File
@@ -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
+3 -1
View File
@@ -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
View File
@@ -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;
+28
View File
@@ -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
View File
@@ -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;
+84
View File
@@ -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;
}
+24 -1
View File
@@ -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 };
@@ -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;
+1
View File
@@ -0,0 +1 @@
# wasm-pack output is committed for the browser runtime package.
+36
View File
@@ -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",
+66
View File
@@ -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",
+10
View File
@@ -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,
},
},
+18 -2
View File
@@ -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",
+5 -1
View File
@@ -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',
);
+3 -1
View File
@@ -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;
+10 -24
View File
@@ -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">
+16 -7
View File
@@ -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"];
`,
},
},
+79 -10
View File
@@ -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]);
});
+54 -21
View File
@@ -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
View File
@@ -1 +0,0 @@
# wasm-pack output is committed for the playground build.
-82
View File
@@ -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.