Compare commits
No commits in common. "01ad6dca5211255a5ae996556f7a3cd961c3db5c" and "58d2c9b42330018b2cc4022ba9c825542a694492" have entirely different histories.
01ad6dca52
...
58d2c9b423
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -3,9 +3,3 @@
|
||||||
/.env
|
/.env
|
||||||
/.yoi
|
/.yoi
|
||||||
/result
|
/result
|
||||||
node_modules
|
|
||||||
/dist
|
|
||||||
site/decodal-site/dist
|
|
||||||
|
|
||||||
# Astro
|
|
||||||
site/decodal-site/.astro
|
|
||||||
|
|
|
||||||
167
Cargo.lock
generated
167
Cargo.lock
generated
|
|
@ -11,18 +11,6 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bumpalo"
|
|
||||||
version = "3.20.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cfg-if"
|
|
||||||
version = "1.0.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "decodal"
|
name = "decodal"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
@ -37,51 +25,12 @@ dependencies = [
|
||||||
"regex",
|
"regex",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "decodal-wasm"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"decodal-core",
|
|
||||||
"serde_json",
|
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "itoa"
|
|
||||||
version = "1.0.18"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.8.2"
|
version = "2.8.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
|
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "once_cell"
|
|
||||||
version = "1.21.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro2"
|
|
||||||
version = "1.0.106"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "quote"
|
|
||||||
version = "1.0.45"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.12.4"
|
version = "1.12.4"
|
||||||
|
|
@ -110,119 +59,3 @@ name = "regex-syntax"
|
||||||
version = "0.8.11"
|
version = "0.8.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustversion"
|
|
||||||
version = "1.0.22"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde"
|
|
||||||
version = "1.0.228"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
|
||||||
dependencies = [
|
|
||||||
"serde_core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_core"
|
|
||||||
version = "1.0.228"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
|
||||||
dependencies = [
|
|
||||||
"serde_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_derive"
|
|
||||||
version = "1.0.228"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_json"
|
|
||||||
version = "1.0.150"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
|
|
||||||
dependencies = [
|
|
||||||
"itoa",
|
|
||||||
"memchr",
|
|
||||||
"serde",
|
|
||||||
"serde_core",
|
|
||||||
"zmij",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "syn"
|
|
||||||
version = "2.0.117"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-ident"
|
|
||||||
version = "1.0.24"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen"
|
|
||||||
version = "0.2.125"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"once_cell",
|
|
||||||
"rustversion",
|
|
||||||
"wasm-bindgen-macro",
|
|
||||||
"wasm-bindgen-shared",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-macro"
|
|
||||||
version = "0.2.125"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d"
|
|
||||||
dependencies = [
|
|
||||||
"quote",
|
|
||||||
"wasm-bindgen-macro-support",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-macro-support"
|
|
||||||
version = "0.2.125"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd"
|
|
||||||
dependencies = [
|
|
||||||
"bumpalo",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
"wasm-bindgen-shared",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-shared"
|
|
||||||
version = "0.2.125"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zmij"
|
|
||||||
version = "1.0.21"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
members = [
|
members = [
|
||||||
"crates/decodal-core",
|
"crates/decodal-core",
|
||||||
"crates/decodal-cli",
|
"crates/decodal-cli",
|
||||||
"crates/decodal-wasm",
|
|
||||||
]
|
]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,10 +71,6 @@ pub enum Expr {
|
||||||
scrutinee: ExprId,
|
scrutinee: ExprId,
|
||||||
arms: Vec<MatchArm>,
|
arms: Vec<MatchArm>,
|
||||||
},
|
},
|
||||||
Unary {
|
|
||||||
op: UnaryOp,
|
|
||||||
expr: ExprId,
|
|
||||||
},
|
|
||||||
Binary {
|
Binary {
|
||||||
op: BinaryOp,
|
op: BinaryOp,
|
||||||
lhs: ExprId,
|
lhs: ExprId,
|
||||||
|
|
@ -121,27 +117,8 @@ pub enum Literal {
|
||||||
Bool(bool),
|
Bool(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum UnaryOp {
|
|
||||||
Neg,
|
|
||||||
Not,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum BinaryOp {
|
pub enum BinaryOp {
|
||||||
Add,
|
|
||||||
Sub,
|
|
||||||
Mul,
|
|
||||||
Div,
|
|
||||||
Concat,
|
|
||||||
Equal,
|
|
||||||
NotEqual,
|
|
||||||
Greater,
|
|
||||||
GreaterEqual,
|
|
||||||
Less,
|
|
||||||
LessEqual,
|
|
||||||
LogicalAnd,
|
|
||||||
LogicalOr,
|
|
||||||
And,
|
And,
|
||||||
Patch,
|
Patch,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use alloc::{format, string::String, vec, vec::Vec};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ExprId, SourceForm, SourceId, Span,
|
ExprId, SourceForm, SourceId, Span,
|
||||||
ast::{Ast, BinaryOp, CompareOp, Expr, Field, Literal, UnaryOp},
|
ast::{Ast, BinaryOp, CompareOp, Expr, Field, Literal},
|
||||||
constraints::normalize_constraints,
|
constraints::normalize_constraints,
|
||||||
diagnostic::{Diagnostic, DiagnosticKind, Result},
|
diagnostic::{Diagnostic, DiagnosticKind, Result},
|
||||||
embedding::HostValue,
|
embedding::HostValue,
|
||||||
|
|
@ -337,106 +337,24 @@ impl<L: SourceLoader> Engine<L> {
|
||||||
"no match arm matched",
|
"no match arm matched",
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
Expr::Unary { op, expr } => {
|
|
||||||
let value = self.eval_expr(
|
|
||||||
ExprRef {
|
|
||||||
module: reference.module,
|
|
||||||
expr,
|
|
||||||
},
|
|
||||||
env,
|
|
||||||
)?;
|
|
||||||
match op {
|
|
||||||
UnaryOp::Neg => negate_number(value, span),
|
|
||||||
UnaryOp::Not => negate_bool(value, span),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Expr::Binary { op, lhs, rhs } => {
|
Expr::Binary { op, lhs, rhs } => {
|
||||||
let lhs_value = self.eval_expr(
|
let lhs = self.eval_expr(
|
||||||
ExprRef {
|
ExprRef {
|
||||||
module: reference.module,
|
module: reference.module,
|
||||||
expr: lhs,
|
expr: lhs,
|
||||||
},
|
},
|
||||||
env,
|
env,
|
||||||
)?;
|
)?;
|
||||||
|
let rhs = self.eval_expr(
|
||||||
|
ExprRef {
|
||||||
|
module: reference.module,
|
||||||
|
expr: rhs,
|
||||||
|
},
|
||||||
|
env,
|
||||||
|
)?;
|
||||||
match op {
|
match op {
|
||||||
BinaryOp::LogicalAnd => {
|
BinaryOp::And => self.compose_and(lhs, rhs, span),
|
||||||
if !bool_from_runtime(lhs_value, span)? {
|
BinaryOp::Patch => self.patch(lhs, rhs),
|
||||||
return Ok(RuntimeValue::Concrete(ConcreteValue::Bool(false)));
|
|
||||||
}
|
|
||||||
let rhs_value = self.eval_expr(
|
|
||||||
ExprRef {
|
|
||||||
module: reference.module,
|
|
||||||
expr: rhs,
|
|
||||||
},
|
|
||||||
env,
|
|
||||||
)?;
|
|
||||||
Ok(RuntimeValue::Concrete(ConcreteValue::Bool(
|
|
||||||
bool_from_runtime(rhs_value, span)?,
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
BinaryOp::LogicalOr => {
|
|
||||||
if bool_from_runtime(lhs_value, span)? {
|
|
||||||
return Ok(RuntimeValue::Concrete(ConcreteValue::Bool(true)));
|
|
||||||
}
|
|
||||||
let rhs_value = self.eval_expr(
|
|
||||||
ExprRef {
|
|
||||||
module: reference.module,
|
|
||||||
expr: rhs,
|
|
||||||
},
|
|
||||||
env,
|
|
||||||
)?;
|
|
||||||
Ok(RuntimeValue::Concrete(ConcreteValue::Bool(
|
|
||||||
bool_from_runtime(rhs_value, span)?,
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
let rhs_value = self.eval_expr(
|
|
||||||
ExprRef {
|
|
||||||
module: reference.module,
|
|
||||||
expr: rhs,
|
|
||||||
},
|
|
||||||
env,
|
|
||||||
)?;
|
|
||||||
match op {
|
|
||||||
BinaryOp::Add => {
|
|
||||||
arithmetic(lhs_value, rhs_value, ArithmeticOp::Add, span)
|
|
||||||
}
|
|
||||||
BinaryOp::Sub => {
|
|
||||||
arithmetic(lhs_value, rhs_value, ArithmeticOp::Sub, span)
|
|
||||||
}
|
|
||||||
BinaryOp::Mul => {
|
|
||||||
arithmetic(lhs_value, rhs_value, ArithmeticOp::Mul, span)
|
|
||||||
}
|
|
||||||
BinaryOp::Div => {
|
|
||||||
arithmetic(lhs_value, rhs_value, ArithmeticOp::Div, span)
|
|
||||||
}
|
|
||||||
BinaryOp::Concat => concat_arrays(lhs_value, rhs_value, span),
|
|
||||||
BinaryOp::Equal => {
|
|
||||||
compare_expr(lhs_value, rhs_value, CompareExprOp::Equal, span)
|
|
||||||
}
|
|
||||||
BinaryOp::NotEqual => {
|
|
||||||
compare_expr(lhs_value, rhs_value, CompareExprOp::NotEqual, span)
|
|
||||||
}
|
|
||||||
BinaryOp::Greater => {
|
|
||||||
compare_expr(lhs_value, rhs_value, CompareExprOp::Greater, span)
|
|
||||||
}
|
|
||||||
BinaryOp::GreaterEqual => compare_expr(
|
|
||||||
lhs_value,
|
|
||||||
rhs_value,
|
|
||||||
CompareExprOp::GreaterEqual,
|
|
||||||
span,
|
|
||||||
),
|
|
||||||
BinaryOp::Less => {
|
|
||||||
compare_expr(lhs_value, rhs_value, CompareExprOp::Less, span)
|
|
||||||
}
|
|
||||||
BinaryOp::LessEqual => {
|
|
||||||
compare_expr(lhs_value, rhs_value, CompareExprOp::LessEqual, span)
|
|
||||||
}
|
|
||||||
BinaryOp::And => self.compose_and(lhs_value, rhs_value, span),
|
|
||||||
BinaryOp::Patch => self.patch(lhs_value, rhs_value),
|
|
||||||
BinaryOp::LogicalAnd | BinaryOp::LogicalOr => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expr::Default { base, fallback } => {
|
Expr::Default { base, fallback } => {
|
||||||
|
|
@ -1081,230 +999,6 @@ fn satisfies_regex(_value: &RuntimeValue, _pattern: &str, span: Span) -> Result<
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
enum ArithmeticOp {
|
|
||||||
Add,
|
|
||||||
Sub,
|
|
||||||
Mul,
|
|
||||||
Div,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
enum CompareExprOp {
|
|
||||||
Equal,
|
|
||||||
NotEqual,
|
|
||||||
Greater,
|
|
||||||
GreaterEqual,
|
|
||||||
Less,
|
|
||||||
LessEqual,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
enum Number {
|
|
||||||
Int(i64),
|
|
||||||
Float(f64),
|
|
||||||
}
|
|
||||||
|
|
||||||
fn negate_number(value: RuntimeValue, span: Span) -> Result<RuntimeValue> {
|
|
||||||
match value {
|
|
||||||
RuntimeValue::Concrete(ConcreteValue::Int(value)) => value
|
|
||||||
.checked_neg()
|
|
||||||
.map(|value| RuntimeValue::Concrete(ConcreteValue::Int(value)))
|
|
||||||
.ok_or_else(|| arithmetic_error(span, "integer negation overflow")),
|
|
||||||
RuntimeValue::Concrete(ConcreteValue::Float(value)) => {
|
|
||||||
Ok(RuntimeValue::Concrete(ConcreteValue::Float(-value)))
|
|
||||||
}
|
|
||||||
_ => Err(Diagnostic::new(
|
|
||||||
DiagnosticKind::TypeMismatch,
|
|
||||||
span,
|
|
||||||
"unary '-' expects a numeric value",
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn negate_bool(value: RuntimeValue, span: Span) -> Result<RuntimeValue> {
|
|
||||||
Ok(RuntimeValue::Concrete(ConcreteValue::Bool(
|
|
||||||
!bool_from_runtime(value, span)?,
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bool_from_runtime(value: RuntimeValue, span: Span) -> Result<bool> {
|
|
||||||
match value {
|
|
||||||
RuntimeValue::Concrete(ConcreteValue::Bool(value)) => Ok(value),
|
|
||||||
_ => Err(Diagnostic::new(
|
|
||||||
DiagnosticKind::TypeMismatch,
|
|
||||||
span,
|
|
||||||
"logical operators expect boolean values",
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compare_expr(
|
|
||||||
lhs: RuntimeValue,
|
|
||||||
rhs: RuntimeValue,
|
|
||||||
op: CompareExprOp,
|
|
||||||
span: Span,
|
|
||||||
) -> Result<RuntimeValue> {
|
|
||||||
let result = match op {
|
|
||||||
CompareExprOp::Equal => scalar_equal(&lhs, &rhs, span)?,
|
|
||||||
CompareExprOp::NotEqual => !scalar_equal(&lhs, &rhs, span)?,
|
|
||||||
CompareExprOp::Greater => compare_numbers(lhs, rhs, |lhs, rhs| lhs > rhs, span)?,
|
|
||||||
CompareExprOp::GreaterEqual => compare_numbers(lhs, rhs, |lhs, rhs| lhs >= rhs, span)?,
|
|
||||||
CompareExprOp::Less => compare_numbers(lhs, rhs, |lhs, rhs| lhs < rhs, span)?,
|
|
||||||
CompareExprOp::LessEqual => compare_numbers(lhs, rhs, |lhs, rhs| lhs <= rhs, span)?,
|
|
||||||
};
|
|
||||||
Ok(RuntimeValue::Concrete(ConcreteValue::Bool(result)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scalar_equal(lhs: &RuntimeValue, rhs: &RuntimeValue, span: Span) -> Result<bool> {
|
|
||||||
match (lhs, rhs) {
|
|
||||||
(
|
|
||||||
RuntimeValue::Concrete(ConcreteValue::String(lhs)),
|
|
||||||
RuntimeValue::Concrete(ConcreteValue::String(rhs)),
|
|
||||||
) => Ok(lhs == rhs),
|
|
||||||
(
|
|
||||||
RuntimeValue::Concrete(ConcreteValue::Bool(lhs)),
|
|
||||||
RuntimeValue::Concrete(ConcreteValue::Bool(rhs)),
|
|
||||||
) => Ok(lhs == rhs),
|
|
||||||
(
|
|
||||||
RuntimeValue::Concrete(ConcreteValue::Int(lhs)),
|
|
||||||
RuntimeValue::Concrete(ConcreteValue::Int(rhs)),
|
|
||||||
) => Ok(lhs == rhs),
|
|
||||||
(
|
|
||||||
RuntimeValue::Concrete(ConcreteValue::Float(lhs)),
|
|
||||||
RuntimeValue::Concrete(ConcreteValue::Float(rhs)),
|
|
||||||
) => Ok(lhs == rhs),
|
|
||||||
(
|
|
||||||
RuntimeValue::Concrete(ConcreteValue::Int(lhs)),
|
|
||||||
RuntimeValue::Concrete(ConcreteValue::Float(rhs)),
|
|
||||||
) => Ok(*lhs as f64 == *rhs),
|
|
||||||
(
|
|
||||||
RuntimeValue::Concrete(ConcreteValue::Float(lhs)),
|
|
||||||
RuntimeValue::Concrete(ConcreteValue::Int(rhs)),
|
|
||||||
) => Ok(*lhs == *rhs as f64),
|
|
||||||
_ => Err(Diagnostic::new(
|
|
||||||
DiagnosticKind::TypeMismatch,
|
|
||||||
span,
|
|
||||||
"equality operators expect comparable scalar values",
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compare_numbers(
|
|
||||||
lhs: RuntimeValue,
|
|
||||||
rhs: RuntimeValue,
|
|
||||||
compare: impl FnOnce(f64, f64) -> bool,
|
|
||||||
span: Span,
|
|
||||||
) -> Result<bool> {
|
|
||||||
let lhs = number_from_runtime(lhs).ok_or_else(|| comparison_type_error(span))?;
|
|
||||||
let rhs = number_from_runtime(rhs).ok_or_else(|| comparison_type_error(span))?;
|
|
||||||
Ok(compare(number_to_f64(lhs), number_to_f64(rhs)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn comparison_type_error(span: Span) -> Diagnostic {
|
|
||||||
Diagnostic::new(
|
|
||||||
DiagnosticKind::TypeMismatch,
|
|
||||||
span,
|
|
||||||
"ordering operators expect numeric values",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn concat_arrays(lhs: RuntimeValue, rhs: RuntimeValue, span: Span) -> Result<RuntimeValue> {
|
|
||||||
match (lhs, rhs) {
|
|
||||||
(
|
|
||||||
RuntimeValue::Concrete(ConcreteValue::Array(mut lhs)),
|
|
||||||
RuntimeValue::Concrete(ConcreteValue::Array(rhs)),
|
|
||||||
) => {
|
|
||||||
lhs.extend(rhs);
|
|
||||||
Ok(RuntimeValue::Concrete(ConcreteValue::Array(lhs)))
|
|
||||||
}
|
|
||||||
_ => Err(Diagnostic::new(
|
|
||||||
DiagnosticKind::TypeMismatch,
|
|
||||||
span,
|
|
||||||
"'++' expects array values",
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn arithmetic(
|
|
||||||
lhs: RuntimeValue,
|
|
||||||
rhs: RuntimeValue,
|
|
||||||
op: ArithmeticOp,
|
|
||||||
span: Span,
|
|
||||||
) -> Result<RuntimeValue> {
|
|
||||||
let lhs = number_from_runtime(lhs).ok_or_else(|| arithmetic_type_error(span))?;
|
|
||||||
let rhs = number_from_runtime(rhs).ok_or_else(|| arithmetic_type_error(span))?;
|
|
||||||
match (lhs, rhs) {
|
|
||||||
(Number::Int(lhs), Number::Int(rhs)) => arithmetic_int(lhs, rhs, op, span),
|
|
||||||
(lhs, rhs) => arithmetic_float(number_to_f64(lhs), number_to_f64(rhs), op, span),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn arithmetic_int(lhs: i64, rhs: i64, op: ArithmeticOp, span: Span) -> Result<RuntimeValue> {
|
|
||||||
match op {
|
|
||||||
ArithmeticOp::Add => lhs
|
|
||||||
.checked_add(rhs)
|
|
||||||
.map(|value| RuntimeValue::Concrete(ConcreteValue::Int(value)))
|
|
||||||
.ok_or_else(|| arithmetic_error(span, "integer addition overflow")),
|
|
||||||
ArithmeticOp::Sub => lhs
|
|
||||||
.checked_sub(rhs)
|
|
||||||
.map(|value| RuntimeValue::Concrete(ConcreteValue::Int(value)))
|
|
||||||
.ok_or_else(|| arithmetic_error(span, "integer subtraction overflow")),
|
|
||||||
ArithmeticOp::Mul => lhs
|
|
||||||
.checked_mul(rhs)
|
|
||||||
.map(|value| RuntimeValue::Concrete(ConcreteValue::Int(value)))
|
|
||||||
.ok_or_else(|| arithmetic_error(span, "integer multiplication overflow")),
|
|
||||||
ArithmeticOp::Div => {
|
|
||||||
if rhs == 0 {
|
|
||||||
return Err(arithmetic_error(span, "division by zero"));
|
|
||||||
}
|
|
||||||
Ok(RuntimeValue::Concrete(ConcreteValue::Float(
|
|
||||||
lhs as f64 / rhs as f64,
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn arithmetic_float(lhs: f64, rhs: f64, op: ArithmeticOp, span: Span) -> Result<RuntimeValue> {
|
|
||||||
if matches!(op, ArithmeticOp::Div) && rhs == 0.0 {
|
|
||||||
return Err(arithmetic_error(span, "division by zero"));
|
|
||||||
}
|
|
||||||
let value = match op {
|
|
||||||
ArithmeticOp::Add => lhs + rhs,
|
|
||||||
ArithmeticOp::Sub => lhs - rhs,
|
|
||||||
ArithmeticOp::Mul => lhs * rhs,
|
|
||||||
ArithmeticOp::Div => lhs / rhs,
|
|
||||||
};
|
|
||||||
Ok(RuntimeValue::Concrete(ConcreteValue::Float(value)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn number_from_runtime(value: RuntimeValue) -> Option<Number> {
|
|
||||||
match value {
|
|
||||||
RuntimeValue::Concrete(ConcreteValue::Int(value)) => Some(Number::Int(value)),
|
|
||||||
RuntimeValue::Concrete(ConcreteValue::Float(value)) => Some(Number::Float(value)),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn number_to_f64(value: Number) -> f64 {
|
|
||||||
match value {
|
|
||||||
Number::Int(value) => value as f64,
|
|
||||||
Number::Float(value) => value,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn arithmetic_type_error(span: Span) -> Diagnostic {
|
|
||||||
Diagnostic::new(
|
|
||||||
DiagnosticKind::TypeMismatch,
|
|
||||||
span,
|
|
||||||
"arithmetic operators expect numeric values",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn arithmetic_error(span: Span, message: &'static str) -> Diagnostic {
|
|
||||||
Diagnostic::new(DiagnosticKind::Conflict, span, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compare_value(value: &RuntimeValue, op: CompareOp, expected: &LiteralValue) -> bool {
|
fn compare_value(value: &RuntimeValue, op: CompareOp, expected: &LiteralValue) -> bool {
|
||||||
match (value, expected) {
|
match (value, expected) {
|
||||||
(RuntimeValue::Concrete(ConcreteValue::Int(actual)), LiteralValue::Int(expected)) => {
|
(RuntimeValue::Concrete(ConcreteValue::Int(actual)), LiteralValue::Int(expected)) => {
|
||||||
|
|
@ -1378,111 +1072,6 @@ mod tests {
|
||||||
assert!(matches!(data, Data::Object(_)));
|
assert!(matches!(data, Data::Object(_)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn evaluates_arithmetic_expressions() {
|
|
||||||
let data = eval_data(
|
|
||||||
r#"
|
|
||||||
{
|
|
||||||
sum = 1 + 2 * 3;
|
|
||||||
diff = 10 - 4;
|
|
||||||
product = (2 + 3) * 4;
|
|
||||||
quotient = 5 / 2;
|
|
||||||
negative = -3 + 1;
|
|
||||||
}
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
let Data::Object(fields) = data else { panic!() };
|
|
||||||
assert_eq!(fields[0].value, Data::Int(7));
|
|
||||||
assert_eq!(fields[1].value, Data::Int(6));
|
|
||||||
assert_eq!(fields[2].value, Data::Int(20));
|
|
||||||
assert_eq!(fields[3].value, Data::Float(2.5));
|
|
||||||
assert_eq!(fields[4].value, Data::Int(-2));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn arithmetic_can_feed_constraints() {
|
|
||||||
let data = eval_data("port = Int & > 4000 + 42 default 8080;");
|
|
||||||
let Data::Object(fields) = data else { panic!() };
|
|
||||||
assert_eq!(fields[0].value, Data::Int(8080));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn rejects_division_by_zero() {
|
|
||||||
let parsed = parse_source("1 / 0").unwrap();
|
|
||||||
let mut engine = Engine::from_parse(parsed.ast, parsed.root);
|
|
||||||
assert!(engine.eval_root().is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn evaluates_array_concat() {
|
|
||||||
let data = eval_data(
|
|
||||||
r#"
|
|
||||||
[1, 2] ++ [3, 4]
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
data,
|
|
||||||
Data::Array(vec![Data::Int(1), Data::Int(2), Data::Int(3), Data::Int(4)])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn array_concat_has_lower_precedence_than_arithmetic() {
|
|
||||||
let data = eval_data("[1 + 1] ++ [2 * 2]");
|
|
||||||
assert_eq!(data, Data::Array(vec![Data::Int(2), Data::Int(4)]));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn rejects_invalid_array_concat_operands() {
|
|
||||||
let parsed = parse_source("[1] ++ 2").unwrap();
|
|
||||||
let mut engine = Engine::from_parse(parsed.ast, parsed.root);
|
|
||||||
assert!(engine.eval_root().is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn evaluates_logical_and_comparison_expressions() {
|
|
||||||
let data = eval_data(
|
|
||||||
r#"
|
|
||||||
{
|
|
||||||
enabled = true && !false;
|
|
||||||
fallback = false || true;
|
|
||||||
compare = 9000 + 443 > 9442;
|
|
||||||
equality = 1 == 1.0;
|
|
||||||
inequality = "prod" != "dev";
|
|
||||||
}
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
let Data::Object(fields) = data else { panic!() };
|
|
||||||
assert_eq!(fields[0].value, Data::Bool(true));
|
|
||||||
assert_eq!(fields[1].value, Data::Bool(true));
|
|
||||||
assert_eq!(fields[2].value, Data::Bool(true));
|
|
||||||
assert_eq!(fields[3].value, Data::Bool(true));
|
|
||||||
assert_eq!(fields[4].value, Data::Bool(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn logical_operators_short_circuit() {
|
|
||||||
let false_and_missing = eval_data("false && missing_identifier");
|
|
||||||
assert_eq!(false_and_missing, Data::Bool(false));
|
|
||||||
|
|
||||||
let true_or_missing = eval_data("true || missing_identifier");
|
|
||||||
assert_eq!(true_or_missing, Data::Bool(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn rejects_invalid_logical_operands() {
|
|
||||||
let parsed = parse_source("true && 1").unwrap();
|
|
||||||
let mut engine = Engine::from_parse(parsed.ast, parsed.root);
|
|
||||||
assert!(engine.eval_root().is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn rejects_invalid_comparison_operands() {
|
|
||||||
let parsed = parse_source("\"a\" < \"b\"").unwrap();
|
|
||||||
let mut engine = Engine::from_parse(parsed.ast, parsed.root);
|
|
||||||
assert!(engine.eval_root().is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn composes_schema_and_value() {
|
fn composes_schema_and_value() {
|
||||||
let data = eval_data(
|
let data = eval_data(
|
||||||
|
|
|
||||||
|
|
@ -34,18 +34,8 @@ pub enum TokenKind {
|
||||||
Dot,
|
Dot,
|
||||||
Colon,
|
Colon,
|
||||||
Equal,
|
Equal,
|
||||||
EqualEqual,
|
|
||||||
Bang,
|
|
||||||
BangEqual,
|
|
||||||
Arrow,
|
Arrow,
|
||||||
Amp,
|
Amp,
|
||||||
AmpAmp,
|
|
||||||
PipePipe,
|
|
||||||
Plus,
|
|
||||||
PlusPlus,
|
|
||||||
Minus,
|
|
||||||
Star,
|
|
||||||
Slash,
|
|
||||||
SlashSlash,
|
SlashSlash,
|
||||||
Gt,
|
Gt,
|
||||||
Gte,
|
Gte,
|
||||||
|
|
@ -77,13 +67,9 @@ impl<'a> Lexer<'a> {
|
||||||
|
|
||||||
pub fn tokenize(mut self) -> Result<Vec<Token>> {
|
pub fn tokenize(mut self) -> Result<Vec<Token>> {
|
||||||
let mut tokens = Vec::new();
|
let mut tokens = Vec::new();
|
||||||
let mut previous = None;
|
|
||||||
loop {
|
loop {
|
||||||
let token = self.next_token(previous.as_ref())?;
|
let token = self.next_token()?;
|
||||||
let is_eof = token.kind == TokenKind::Eof;
|
let is_eof = token.kind == TokenKind::Eof;
|
||||||
if !is_eof {
|
|
||||||
previous = Some(token.kind.clone());
|
|
||||||
}
|
|
||||||
tokens.push(token);
|
tokens.push(token);
|
||||||
if is_eof {
|
if is_eof {
|
||||||
return Ok(tokens);
|
return Ok(tokens);
|
||||||
|
|
@ -91,7 +77,7 @@ impl<'a> Lexer<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_token(&mut self, previous: Option<&TokenKind>) -> Result<Token> {
|
fn next_token(&mut self) -> Result<Token> {
|
||||||
self.skip_ws_and_comments();
|
self.skip_ws_and_comments();
|
||||||
let start = self.pos;
|
let start = self.pos;
|
||||||
let Some(ch) = self.peek() else {
|
let Some(ch) = self.peek() else {
|
||||||
|
|
@ -148,57 +134,16 @@ impl<'a> Lexer<'a> {
|
||||||
}
|
}
|
||||||
b'&' => {
|
b'&' => {
|
||||||
self.pos += 1;
|
self.pos += 1;
|
||||||
if self.consume(b'&') {
|
TokenKind::Amp
|
||||||
TokenKind::AmpAmp
|
|
||||||
} else {
|
|
||||||
TokenKind::Amp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b'|' => {
|
|
||||||
self.pos += 1;
|
|
||||||
if self.consume(b'|') {
|
|
||||||
TokenKind::PipePipe
|
|
||||||
} else {
|
|
||||||
return Err(Diagnostic::syntax(
|
|
||||||
self.span(start, self.pos),
|
|
||||||
"expected '|' after '|'",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b'+' => {
|
|
||||||
self.pos += 1;
|
|
||||||
if self.consume(b'+') {
|
|
||||||
TokenKind::PlusPlus
|
|
||||||
} else {
|
|
||||||
TokenKind::Plus
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b'-' => {
|
|
||||||
self.pos += 1;
|
|
||||||
TokenKind::Minus
|
|
||||||
}
|
|
||||||
b'*' => {
|
|
||||||
self.pos += 1;
|
|
||||||
TokenKind::Star
|
|
||||||
}
|
}
|
||||||
b'=' => {
|
b'=' => {
|
||||||
self.pos += 1;
|
self.pos += 1;
|
||||||
if self.consume(b'>') {
|
if self.consume(b'>') {
|
||||||
TokenKind::Arrow
|
TokenKind::Arrow
|
||||||
} else if self.consume(b'=') {
|
|
||||||
TokenKind::EqualEqual
|
|
||||||
} else {
|
} else {
|
||||||
TokenKind::Equal
|
TokenKind::Equal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
b'!' => {
|
|
||||||
self.pos += 1;
|
|
||||||
if self.consume(b'=') {
|
|
||||||
TokenKind::BangEqual
|
|
||||||
} else {
|
|
||||||
TokenKind::Bang
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b'>' => {
|
b'>' => {
|
||||||
self.pos += 1;
|
self.pos += 1;
|
||||||
if self.consume(b'=') {
|
if self.consume(b'=') {
|
||||||
|
|
@ -219,8 +164,6 @@ impl<'a> Lexer<'a> {
|
||||||
self.pos += 1;
|
self.pos += 1;
|
||||||
if self.consume(b'/') {
|
if self.consume(b'/') {
|
||||||
TokenKind::SlashSlash
|
TokenKind::SlashSlash
|
||||||
} else if previous.is_some_and(token_can_end_expr) {
|
|
||||||
TokenKind::Slash
|
|
||||||
} else {
|
} else {
|
||||||
self.lex_regex(start)?
|
self.lex_regex(start)?
|
||||||
}
|
}
|
||||||
|
|
@ -398,23 +341,6 @@ fn is_ident_continue(c: u8) -> bool {
|
||||||
c.is_ascii_alphanumeric() || c == b'_'
|
c.is_ascii_alphanumeric() || c == b'_'
|
||||||
}
|
}
|
||||||
|
|
||||||
fn token_can_end_expr(kind: &TokenKind) -> bool {
|
|
||||||
matches!(
|
|
||||||
kind,
|
|
||||||
TokenKind::Ident(_)
|
|
||||||
| TokenKind::Int(_)
|
|
||||||
| TokenKind::Float(_)
|
|
||||||
| TokenKind::String(_)
|
|
||||||
| TokenKind::Regex(_)
|
|
||||||
| TokenKind::True
|
|
||||||
| TokenKind::False
|
|
||||||
| TokenKind::Underscore
|
|
||||||
| TokenKind::RBrace
|
|
||||||
| TokenKind::RBracket
|
|
||||||
| TokenKind::RParen
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use alloc::{string::String, vec::Vec};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
SourceId, Span,
|
SourceId, Span,
|
||||||
ast::{Ast, BinaryOp, CompareOp, Expr, ExprId, Field, Literal, MatchArm, Param, UnaryOp},
|
ast::{Ast, BinaryOp, CompareOp, Expr, ExprId, Field, Literal, MatchArm, Param},
|
||||||
diagnostic::{Diagnostic, Result},
|
diagnostic::{Diagnostic, Result},
|
||||||
lexer::{Lexer, Token, TokenKind},
|
lexer::{Lexer, Token, TokenKind},
|
||||||
};
|
};
|
||||||
|
|
@ -115,110 +115,6 @@ impl Parser {
|
||||||
let rhs = self.parse_expr(r_bp)?;
|
let rhs = self.parse_expr(r_bp)?;
|
||||||
let span = self.ast.span(lhs).join(self.ast.span(rhs)).join(op_span);
|
let span = self.ast.span(lhs).join(self.ast.span(rhs)).join(op_span);
|
||||||
lhs = match kind {
|
lhs = match kind {
|
||||||
InfixKind::Add => self.ast.push(
|
|
||||||
Expr::Binary {
|
|
||||||
op: BinaryOp::Add,
|
|
||||||
lhs,
|
|
||||||
rhs,
|
|
||||||
},
|
|
||||||
span,
|
|
||||||
),
|
|
||||||
InfixKind::Sub => self.ast.push(
|
|
||||||
Expr::Binary {
|
|
||||||
op: BinaryOp::Sub,
|
|
||||||
lhs,
|
|
||||||
rhs,
|
|
||||||
},
|
|
||||||
span,
|
|
||||||
),
|
|
||||||
InfixKind::Mul => self.ast.push(
|
|
||||||
Expr::Binary {
|
|
||||||
op: BinaryOp::Mul,
|
|
||||||
lhs,
|
|
||||||
rhs,
|
|
||||||
},
|
|
||||||
span,
|
|
||||||
),
|
|
||||||
InfixKind::Div => self.ast.push(
|
|
||||||
Expr::Binary {
|
|
||||||
op: BinaryOp::Div,
|
|
||||||
lhs,
|
|
||||||
rhs,
|
|
||||||
},
|
|
||||||
span,
|
|
||||||
),
|
|
||||||
InfixKind::Concat => self.ast.push(
|
|
||||||
Expr::Binary {
|
|
||||||
op: BinaryOp::Concat,
|
|
||||||
lhs,
|
|
||||||
rhs,
|
|
||||||
},
|
|
||||||
span,
|
|
||||||
),
|
|
||||||
InfixKind::Equal => self.ast.push(
|
|
||||||
Expr::Binary {
|
|
||||||
op: BinaryOp::Equal,
|
|
||||||
lhs,
|
|
||||||
rhs,
|
|
||||||
},
|
|
||||||
span,
|
|
||||||
),
|
|
||||||
InfixKind::NotEqual => self.ast.push(
|
|
||||||
Expr::Binary {
|
|
||||||
op: BinaryOp::NotEqual,
|
|
||||||
lhs,
|
|
||||||
rhs,
|
|
||||||
},
|
|
||||||
span,
|
|
||||||
),
|
|
||||||
InfixKind::Greater => self.ast.push(
|
|
||||||
Expr::Binary {
|
|
||||||
op: BinaryOp::Greater,
|
|
||||||
lhs,
|
|
||||||
rhs,
|
|
||||||
},
|
|
||||||
span,
|
|
||||||
),
|
|
||||||
InfixKind::GreaterEqual => self.ast.push(
|
|
||||||
Expr::Binary {
|
|
||||||
op: BinaryOp::GreaterEqual,
|
|
||||||
lhs,
|
|
||||||
rhs,
|
|
||||||
},
|
|
||||||
span,
|
|
||||||
),
|
|
||||||
InfixKind::Less => self.ast.push(
|
|
||||||
Expr::Binary {
|
|
||||||
op: BinaryOp::Less,
|
|
||||||
lhs,
|
|
||||||
rhs,
|
|
||||||
},
|
|
||||||
span,
|
|
||||||
),
|
|
||||||
InfixKind::LessEqual => self.ast.push(
|
|
||||||
Expr::Binary {
|
|
||||||
op: BinaryOp::LessEqual,
|
|
||||||
lhs,
|
|
||||||
rhs,
|
|
||||||
},
|
|
||||||
span,
|
|
||||||
),
|
|
||||||
InfixKind::LogicalAnd => self.ast.push(
|
|
||||||
Expr::Binary {
|
|
||||||
op: BinaryOp::LogicalAnd,
|
|
||||||
lhs,
|
|
||||||
rhs,
|
|
||||||
},
|
|
||||||
span,
|
|
||||||
),
|
|
||||||
InfixKind::LogicalOr => self.ast.push(
|
|
||||||
Expr::Binary {
|
|
||||||
op: BinaryOp::LogicalOr,
|
|
||||||
lhs,
|
|
||||||
rhs,
|
|
||||||
},
|
|
||||||
span,
|
|
||||||
),
|
|
||||||
InfixKind::And => self.ast.push(
|
InfixKind::And => self.ast.push(
|
||||||
Expr::Binary {
|
Expr::Binary {
|
||||||
op: BinaryOp::And,
|
op: BinaryOp::And,
|
||||||
|
|
@ -277,28 +173,6 @@ impl Parser {
|
||||||
TokenKind::Let => self.parse_let(token.span),
|
TokenKind::Let => self.parse_let(token.span),
|
||||||
TokenKind::Match => self.parse_match(token.span),
|
TokenKind::Match => self.parse_match(token.span),
|
||||||
TokenKind::Import => self.parse_import(token.span),
|
TokenKind::Import => self.parse_import(token.span),
|
||||||
TokenKind::Minus => {
|
|
||||||
let expr = self.parse_expr(17)?;
|
|
||||||
let span = token.span.join(self.ast.span(expr));
|
|
||||||
Ok(self.ast.push(
|
|
||||||
Expr::Unary {
|
|
||||||
op: UnaryOp::Neg,
|
|
||||||
expr,
|
|
||||||
},
|
|
||||||
span,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
TokenKind::Bang => {
|
|
||||||
let expr = self.parse_expr(17)?;
|
|
||||||
let span = token.span.join(self.ast.span(expr));
|
|
||||||
Ok(self.ast.push(
|
|
||||||
Expr::Unary {
|
|
||||||
op: UnaryOp::Not,
|
|
||||||
expr,
|
|
||||||
},
|
|
||||||
span,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
TokenKind::Gt | TokenKind::Gte | TokenKind::Lt | TokenKind::Lte => {
|
TokenKind::Gt | TokenKind::Gte | TokenKind::Lt | TokenKind::Lte => {
|
||||||
let op = match token.kind {
|
let op = match token.kind {
|
||||||
TokenKind::Gt => CompareOp::Gt,
|
TokenKind::Gt => CompareOp::Gt,
|
||||||
|
|
@ -307,7 +181,7 @@ impl Parser {
|
||||||
TokenKind::Lte => CompareOp::Lte,
|
TokenKind::Lte => CompareOp::Lte,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
let value = self.parse_expr(12)?;
|
let value = self.parse_expr(8)?;
|
||||||
let span = token.span.join(self.ast.span(value));
|
let span = token.span.join(self.ast.span(value));
|
||||||
Ok(self.ast.push(Expr::CompareConstraint { op, value }, span))
|
Ok(self.ast.push(Expr::CompareConstraint { op, value }, span))
|
||||||
}
|
}
|
||||||
|
|
@ -532,19 +406,6 @@ impl Parser {
|
||||||
TokenKind::Default => Some((InfixKind::Default, 1, 2)),
|
TokenKind::Default => Some((InfixKind::Default, 1, 2)),
|
||||||
TokenKind::SlashSlash => Some((InfixKind::Patch, 3, 4)),
|
TokenKind::SlashSlash => Some((InfixKind::Patch, 3, 4)),
|
||||||
TokenKind::Amp => Some((InfixKind::And, 5, 6)),
|
TokenKind::Amp => Some((InfixKind::And, 5, 6)),
|
||||||
TokenKind::PipePipe => Some((InfixKind::LogicalOr, 7, 8)),
|
|
||||||
TokenKind::AmpAmp => Some((InfixKind::LogicalAnd, 9, 10)),
|
|
||||||
TokenKind::EqualEqual => Some((InfixKind::Equal, 11, 12)),
|
|
||||||
TokenKind::BangEqual => Some((InfixKind::NotEqual, 11, 12)),
|
|
||||||
TokenKind::Gt => Some((InfixKind::Greater, 11, 12)),
|
|
||||||
TokenKind::Gte => Some((InfixKind::GreaterEqual, 11, 12)),
|
|
||||||
TokenKind::Lt => Some((InfixKind::Less, 11, 12)),
|
|
||||||
TokenKind::Lte => Some((InfixKind::LessEqual, 11, 12)),
|
|
||||||
TokenKind::PlusPlus => Some((InfixKind::Concat, 12, 13)),
|
|
||||||
TokenKind::Plus => Some((InfixKind::Add, 13, 14)),
|
|
||||||
TokenKind::Minus => Some((InfixKind::Sub, 13, 14)),
|
|
||||||
TokenKind::Star => Some((InfixKind::Mul, 15, 16)),
|
|
||||||
TokenKind::Slash => Some((InfixKind::Div, 15, 16)),
|
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -637,19 +498,6 @@ impl Parser {
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
enum InfixKind {
|
enum InfixKind {
|
||||||
Add,
|
|
||||||
Sub,
|
|
||||||
Mul,
|
|
||||||
Div,
|
|
||||||
Concat,
|
|
||||||
Equal,
|
|
||||||
NotEqual,
|
|
||||||
Greater,
|
|
||||||
GreaterEqual,
|
|
||||||
Less,
|
|
||||||
LessEqual,
|
|
||||||
LogicalAnd,
|
|
||||||
LogicalOr,
|
|
||||||
And,
|
And,
|
||||||
Patch,
|
Patch,
|
||||||
Default,
|
Default,
|
||||||
|
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "decodal-wasm"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2024"
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
crate-type = ["cdylib", "rlib"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
decodal-core = { path = "../decodal-core" }
|
|
||||||
serde_json = "1"
|
|
||||||
wasm-bindgen = "0.2"
|
|
||||||
|
|
||||||
[package.metadata.wasm-pack.profile.release]
|
|
||||||
wasm-opt = false
|
|
||||||
|
|
@ -1,233 +0,0 @@
|
||||||
use std::collections::BTreeMap;
|
|
||||||
|
|
||||||
use decodal_core::{
|
|
||||||
Data, Diagnostic, DiagnosticKind, EmptyLoader, Engine, LoadedSource, SourceLoader, Span,
|
|
||||||
};
|
|
||||||
use wasm_bindgen::prelude::*;
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
pub fn evaluate(source: &str) -> String {
|
|
||||||
encode_result(evaluate_inner(source))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = evaluateProject)]
|
|
||||||
pub fn evaluate_project(entry: &str, files_json: &str) -> String {
|
|
||||||
encode_result(evaluate_project_inner(entry, files_json))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn encode_result(result: Result<String, String>) -> String {
|
|
||||||
match result {
|
|
||||||
Ok(output) => format!("{{\"ok\":true,\"output\":{}}}", json_string(&output)),
|
|
||||||
Err(error) => format!("{{\"ok\":false,\"error\":{}}}", json_string(&error)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn evaluate_inner(source: &str) -> Result<String, String> {
|
|
||||||
let mut engine = Engine::new(EmptyLoader);
|
|
||||||
let module = engine
|
|
||||||
.add_root_source("playground", "playground", source)
|
|
||||||
.map_err(format_diagnostic)?;
|
|
||||||
let value = engine.eval_module(module).map_err(format_diagnostic)?;
|
|
||||||
let data = engine.materialize(&value).map_err(format_diagnostic)?;
|
|
||||||
Ok(format_data(&data, 0))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn evaluate_project_inner(entry: &str, files_json: &str) -> Result<String, String> {
|
|
||||||
let raw_files: BTreeMap<String, String> = serde_json::from_str(files_json)
|
|
||||||
.map_err(|error| format!("failed to read playground files: {error}"))?;
|
|
||||||
let mut files = BTreeMap::new();
|
|
||||||
for (path, source) in raw_files {
|
|
||||||
let path = normalize_path(&path).ok_or_else(|| format!("invalid file path `{path}`"))?;
|
|
||||||
files.insert(path, source);
|
|
||||||
}
|
|
||||||
|
|
||||||
let entry = normalize_path(entry).ok_or_else(|| format!("invalid entry path `{entry}`"))?;
|
|
||||||
let source = files
|
|
||||||
.get(&entry)
|
|
||||||
.cloned()
|
|
||||||
.ok_or_else(|| format!("entry file `{entry}` was not found"))?;
|
|
||||||
|
|
||||||
let mut engine = Engine::new(VirtualLoader { files });
|
|
||||||
let module = engine
|
|
||||||
.add_root_source(entry.clone(), entry.clone(), &source)
|
|
||||||
.map_err(format_diagnostic)?;
|
|
||||||
let value = engine.eval_module(module).map_err(format_diagnostic)?;
|
|
||||||
let data = engine.materialize(&value).map_err(format_diagnostic)?;
|
|
||||||
Ok(format_data(&data, 0))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
struct VirtualLoader {
|
|
||||||
files: BTreeMap<String, String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SourceLoader for VirtualLoader {
|
|
||||||
fn load(
|
|
||||||
&mut self,
|
|
||||||
current_key: Option<&str>,
|
|
||||||
specifier: &str,
|
|
||||||
) -> decodal_core::Result<LoadedSource> {
|
|
||||||
let key = resolve_import(current_key, specifier).ok_or_else(|| {
|
|
||||||
Diagnostic::new(
|
|
||||||
DiagnosticKind::Import,
|
|
||||||
Span::default(),
|
|
||||||
format!("invalid import path `{specifier}`"),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
let source = self.files.get(&key).cloned().ok_or_else(|| {
|
|
||||||
Diagnostic::new(
|
|
||||||
DiagnosticKind::Import,
|
|
||||||
Span::default(),
|
|
||||||
format!("import `{specifier}` resolved to `{key}`, but that file does not exist"),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
Ok(LoadedSource {
|
|
||||||
key: key.clone(),
|
|
||||||
name: key,
|
|
||||||
source,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resolve_import(current_key: Option<&str>, specifier: &str) -> Option<String> {
|
|
||||||
if specifier.starts_with('/') {
|
|
||||||
return normalize_path(specifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut base = String::new();
|
|
||||||
if let Some(current_key) = current_key {
|
|
||||||
if let Some((parent, _file)) = current_key.rsplit_once('/') {
|
|
||||||
base.push_str(parent);
|
|
||||||
base.push('/');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
base.push_str(specifier);
|
|
||||||
normalize_path(&base)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn normalize_path(path: &str) -> Option<String> {
|
|
||||||
let mut parts = Vec::new();
|
|
||||||
let normalized = path.replace('\\', "/");
|
|
||||||
for part in normalized.split('/') {
|
|
||||||
match part {
|
|
||||||
"" | "." => {}
|
|
||||||
".." => {
|
|
||||||
parts.pop()?;
|
|
||||||
}
|
|
||||||
part => parts.push(part),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if parts.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(parts.join("/"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn format_diagnostic(diagnostic: decodal_core::Diagnostic) -> String {
|
|
||||||
format!(
|
|
||||||
"{:?} at {}:{}..{}: {}",
|
|
||||||
diagnostic.kind,
|
|
||||||
diagnostic.span.source.0,
|
|
||||||
diagnostic.span.start,
|
|
||||||
diagnostic.span.end,
|
|
||||||
diagnostic.message,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn format_data(data: &Data, indent: usize) -> String {
|
|
||||||
match data {
|
|
||||||
Data::String(value) => json_string(value),
|
|
||||||
Data::Int(value) => value.to_string(),
|
|
||||||
Data::Float(value) => value.to_string(),
|
|
||||||
Data::Bool(value) => value.to_string(),
|
|
||||||
Data::Array(items) => {
|
|
||||||
if items.is_empty() {
|
|
||||||
return String::from("[]");
|
|
||||||
}
|
|
||||||
let mut out = String::from("[\n");
|
|
||||||
for (index, item) in items.iter().enumerate() {
|
|
||||||
out.push_str(&" ".repeat(indent + 2));
|
|
||||||
out.push_str(&format_data(item, indent + 2));
|
|
||||||
if index + 1 != items.len() {
|
|
||||||
out.push(',');
|
|
||||||
}
|
|
||||||
out.push('\n');
|
|
||||||
}
|
|
||||||
out.push_str(&" ".repeat(indent));
|
|
||||||
out.push(']');
|
|
||||||
out
|
|
||||||
}
|
|
||||||
Data::Object(fields) => {
|
|
||||||
if fields.is_empty() {
|
|
||||||
return String::from("{}");
|
|
||||||
}
|
|
||||||
let mut out = String::from("{\n");
|
|
||||||
for (index, field) in fields.iter().enumerate() {
|
|
||||||
out.push_str(&" ".repeat(indent + 2));
|
|
||||||
out.push_str(&json_string(&field.name));
|
|
||||||
out.push_str(": ");
|
|
||||||
out.push_str(&format_data(&field.value, indent + 2));
|
|
||||||
if index + 1 != fields.len() {
|
|
||||||
out.push(',');
|
|
||||||
}
|
|
||||||
out.push('\n');
|
|
||||||
}
|
|
||||||
out.push_str(&" ".repeat(indent));
|
|
||||||
out.push('}');
|
|
||||||
out
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn json_string(value: &str) -> String {
|
|
||||||
let mut out = String::from("\"");
|
|
||||||
for ch in value.chars() {
|
|
||||||
match ch {
|
|
||||||
'"' => out.push_str("\\\""),
|
|
||||||
'\\' => out.push_str("\\\\"),
|
|
||||||
'\n' => out.push_str("\\n"),
|
|
||||||
'\r' => out.push_str("\\r"),
|
|
||||||
'\t' => out.push_str("\\t"),
|
|
||||||
ch if ch.is_control() => {
|
|
||||||
use core::fmt::Write;
|
|
||||||
let _ = write!(out, "\\u{:04x}", ch as u32);
|
|
||||||
}
|
|
||||||
ch => out.push(ch),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out.push('"');
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::{evaluate_project_inner, normalize_path, resolve_import};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn normalizes_virtual_paths() {
|
|
||||||
assert_eq!(
|
|
||||||
normalize_path("/schemas/../main.dcdl"),
|
|
||||||
Some("main.dcdl".into())
|
|
||||||
);
|
|
||||||
assert_eq!(normalize_path("../main.dcdl"), None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn resolves_imports_relative_to_current_file() {
|
|
||||||
assert_eq!(
|
|
||||||
resolve_import(Some("schemas/service.dcdl"), "./types.dcdl"),
|
|
||||||
Some("schemas/types.dcdl".into())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn evaluates_project_imports() {
|
|
||||||
let files = r#"{
|
|
||||||
"main.dcdl":"let dep = import \"./schemas/service.dcdl\"; in dep.Service & { port = 9443; }",
|
|
||||||
"schemas/service.dcdl":"Service = { name = String default \"api\"; port = Int & > 443 default 8443; }"
|
|
||||||
}"#;
|
|
||||||
let output = evaluate_project_inner("main.dcdl", files).unwrap();
|
|
||||||
assert!(output.contains("\"port\": 9443"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -4,13 +4,11 @@ pkgs.mkShell {
|
||||||
cargo
|
cargo
|
||||||
clippy
|
clippy
|
||||||
git
|
git
|
||||||
lld
|
|
||||||
nodejs
|
nodejs
|
||||||
nixfmt
|
nixfmt
|
||||||
rustc
|
rustc
|
||||||
rustfmt
|
rustfmt
|
||||||
tree-sitter
|
tree-sitter
|
||||||
wasm-pack
|
|
||||||
];
|
];
|
||||||
|
|
||||||
shellHook = ''
|
shellHook = ''
|
||||||
|
|
|
||||||
|
|
@ -20,51 +20,6 @@ cargo test -p decodal-core --features regex
|
||||||
cargo run -q -p decodal --features regex -- examples/regex/main.dcdl
|
cargo run -q -p decodal --features regex -- examples/regex/main.dcdl
|
||||||
```
|
```
|
||||||
|
|
||||||
## Web site and playground
|
|
||||||
|
|
||||||
The Astro documentation site and browser playground are kept in:
|
|
||||||
|
|
||||||
```text
|
|
||||||
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.
|
|
||||||
|
|
||||||
Important files:
|
|
||||||
|
|
||||||
```text
|
|
||||||
site/decodal-site/src/pages/docs/[...slug].astro
|
|
||||||
site/decodal-site/src/pages/playground.astro
|
|
||||||
site/decodal-site/src/layouts/ManualLayout.astro
|
|
||||||
site/decodal-site/src/lib/docs.js
|
|
||||||
crates/decodal-wasm/src/lib.rs
|
|
||||||
```
|
|
||||||
|
|
||||||
Build the WebAssembly package before building the site:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd site/decodal-site
|
|
||||||
npm install
|
|
||||||
npm run build:wasm
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
`npm run build:wasm` writes generated files into:
|
|
||||||
|
|
||||||
```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.
|
|
||||||
|
|
||||||
To run the site locally:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd site/decodal-site
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
## Tree-sitter grammar
|
## Tree-sitter grammar
|
||||||
|
|
||||||
The Tree-sitter grammar is kept in:
|
The Tree-sitter grammar is kept in:
|
||||||
|
|
@ -118,7 +73,7 @@ When the Decodal syntax changes:
|
||||||
|
|
||||||
## Development shell
|
## Development shell
|
||||||
|
|
||||||
The Nix development shell includes Rust tooling, Node.js, Tree-sitter CLI tooling, and wasm-pack tooling.
|
The Nix development shell includes Rust tooling, Node.js, and Tree-sitter CLI tooling.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
nix develop
|
nix develop
|
||||||
|
|
@ -132,7 +87,5 @@ The shell provides:
|
||||||
- `clippy`
|
- `clippy`
|
||||||
- `node`
|
- `node`
|
||||||
- `npm`
|
- `npm`
|
||||||
- `wasm-pack`
|
|
||||||
- `lld`
|
|
||||||
- `tree-sitter`
|
- `tree-sitter`
|
||||||
- `nixfmt`
|
- `nixfmt`
|
||||||
|
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
# Arithmetic Expression
|
|
||||||
|
|
||||||
Decodal supports arithmetic over concrete numeric values.
|
|
||||||
|
|
||||||
```dcdl
|
|
||||||
{
|
|
||||||
workers = 2 + 2;
|
|
||||||
timeout = 30.0 / 2;
|
|
||||||
port = 8000 + 80;
|
|
||||||
negative = -1;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Operators
|
|
||||||
|
|
||||||
- `+` addition
|
|
||||||
- `-` subtraction
|
|
||||||
- `*` multiplication
|
|
||||||
- `/` division
|
|
||||||
- unary `-` negation
|
|
||||||
|
|
||||||
`*` and `/` bind tighter than `+` and `-`.
|
|
||||||
Parentheses can be used to make grouping explicit.
|
|
||||||
|
|
||||||
```dcdl
|
|
||||||
2 + 3 * 4 # 14
|
|
||||||
(2 + 3) * 4 # 20
|
|
||||||
```
|
|
||||||
|
|
||||||
## Numeric behavior
|
|
||||||
|
|
||||||
Arithmetic requires concrete `Int` or `Float` operands.
|
|
||||||
`Int + Int`, `Int - Int`, and `Int * Int` produce `Int` when no overflow occurs.
|
|
||||||
Mixed `Int` / `Float` arithmetic produces `Float`.
|
|
||||||
Division always produces `Float`.
|
|
||||||
|
|
||||||
Division by zero and integer overflow are evaluation errors.
|
|
||||||
|
|
||||||
Arithmetic expressions can be used anywhere a concrete numeric expression is expected, including defaults and numeric constraints.
|
|
||||||
|
|
||||||
```dcdl
|
|
||||||
port = Int & > 4000 + 42 default 8080;
|
|
||||||
```
|
|
||||||
|
|
@ -7,21 +7,9 @@ array expression は、順序付きの値の列を表す。
|
||||||
["a", "b", "c"]
|
["a", "b", "c"]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Array concat
|
## 未確定事項
|
||||||
|
|
||||||
`++` は concrete array 同士を連結する。
|
- 配列要素の制約表現。
|
||||||
|
- 異種配列を許可するか。
|
||||||
```dcdl
|
- `//` による patch を右辺置換だけにするか。
|
||||||
base = ["read", "write"];
|
- append / prepend / remove などの操作を提供するか。
|
||||||
extra = ["admin"];
|
|
||||||
roles = base ++ extra;
|
|
||||||
```
|
|
||||||
|
|
||||||
`roles` は以下と同じ値になる。
|
|
||||||
|
|
||||||
```dcdl
|
|
||||||
["read", "write", "admin"]
|
|
||||||
```
|
|
||||||
|
|
||||||
`++` は配列要素を変換しない。
|
|
||||||
左辺の要素の後に右辺の要素が並ぶ。
|
|
||||||
|
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
# Logical and Comparison Expressions
|
|
||||||
|
|
||||||
Decodal supports boolean logic over concrete `Bool` values and comparison over concrete scalar values.
|
|
||||||
|
|
||||||
```dcdl
|
|
||||||
{
|
|
||||||
is_prod = env == "prod";
|
|
||||||
high_port = port > 9000;
|
|
||||||
enabled = is_prod && high_port;
|
|
||||||
disabled = !enabled;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Logical operators
|
|
||||||
|
|
||||||
- `!expr` negates a concrete `Bool`.
|
|
||||||
- `lhs && rhs` returns boolean AND.
|
|
||||||
- `lhs || rhs` returns boolean OR.
|
|
||||||
|
|
||||||
`&&` and `||` short-circuit: the right-hand side is evaluated only when needed.
|
|
||||||
Logical operands must evaluate to concrete `Bool` values.
|
|
||||||
|
|
||||||
## Comparison operators
|
|
||||||
|
|
||||||
- `==`
|
|
||||||
- `!=`
|
|
||||||
- `<`
|
|
||||||
- `<=`
|
|
||||||
- `>`
|
|
||||||
- `>=`
|
|
||||||
|
|
||||||
`==` and `!=` compare concrete scalar values: `String`, `Bool`, `Int`, and `Float`.
|
|
||||||
`Int` and `Float` can be compared to each other numerically.
|
|
||||||
|
|
||||||
Ordering operators `<`, `<=`, `>`, and `>=` compare concrete numeric values only.
|
|
||||||
They are separate from prefix comparison constraints such as `> 443`.
|
|
||||||
|
|
||||||
```dcdl
|
|
||||||
port = Int & > 443 default 9443;
|
|
||||||
is_high = port > 9000;
|
|
||||||
```
|
|
||||||
|
|
@ -1,79 +1,6 @@
|
||||||
# 演算子
|
# 合成演算子
|
||||||
|
|
||||||
この章では、Decodal の演算子の意味を定義する。
|
この章では、`&` と `//` の意味を定義する。
|
||||||
|
|
||||||
## 演算子一覧
|
|
||||||
|
|
||||||
| 演算子 | 形 | 種類 | 対象 | 結果 / 意味 |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| `.` | `object.field` | field reference | object / abstract object | field value |
|
|
||||||
| call | `fn(arg)` | function call | function | function result |
|
|
||||||
| `!` | `!expr` | unary logical | concrete `Bool` | concrete `Bool` |
|
|
||||||
| `-` | `-expr` | unary arithmetic | concrete `Int` / `Float` | negated number |
|
|
||||||
| `*` | `lhs * rhs` | arithmetic | concrete `Int` / `Float` | numeric product |
|
|
||||||
| `/` | `lhs / rhs` | arithmetic | concrete `Int` / `Float` | `Float` quotient |
|
|
||||||
| `+` | `lhs + rhs` | arithmetic | concrete `Int` / `Float` | numeric sum |
|
|
||||||
| `-` | `lhs - rhs` | arithmetic | concrete `Int` / `Float` | numeric difference |
|
|
||||||
| `++` | `lhs ++ rhs` | array concat | concrete arrays | concatenated array |
|
|
||||||
| `==` | `lhs == rhs` | equality | concrete scalar | concrete `Bool` |
|
|
||||||
| `!=` | `lhs != rhs` | equality | concrete scalar | concrete `Bool` |
|
|
||||||
| `<` | `lhs < rhs` | ordering | concrete `Int` / `Float` | concrete `Bool` |
|
|
||||||
| `<=` | `lhs <= rhs` | ordering | concrete `Int` / `Float` | concrete `Bool` |
|
|
||||||
| `>` | `lhs > rhs` | ordering | concrete `Int` / `Float` | concrete `Bool` |
|
|
||||||
| `>=` | `lhs >= rhs` | ordering | concrete `Int` / `Float` | concrete `Bool` |
|
|
||||||
| `>` | `> value` | comparison constraint | numeric constraint value | abstract constraint |
|
|
||||||
| `>=` | `>= value` | comparison constraint | numeric constraint value | abstract constraint |
|
|
||||||
| `<` | `< value` | comparison constraint | numeric constraint value | abstract constraint |
|
|
||||||
| `<=` | `<= value` | comparison constraint | numeric constraint value | abstract constraint |
|
|
||||||
| `&&` | `lhs && rhs` | logical | concrete `Bool` | short-circuit AND |
|
|
||||||
| `||` | `lhs || rhs` | logical | concrete `Bool` | short-circuit OR |
|
|
||||||
| `&` | `lhs & rhs` | composition | value / constraint / object | constraint-preserving composition |
|
|
||||||
| `//` | `lhs // rhs` | patch | object / value | right-biased structural patch |
|
|
||||||
| `default` | `base default fallback` | default | abstract value | materialization fallback |
|
|
||||||
|
|
||||||
`concrete scalar` は `String`、`Bool`、`Int`、`Float` を指す。
|
|
||||||
|
|
||||||
## 優先順位
|
|
||||||
|
|
||||||
優先順位は高い順に以下である。
|
|
||||||
|
|
||||||
1. 関数呼び出しとフィールド参照
|
|
||||||
2. unary `!` `-`
|
|
||||||
3. `*` `/`
|
|
||||||
4. `+` `-`
|
|
||||||
5. `++`
|
|
||||||
6. `==` `!=` `<` `<=` `>` `>=`
|
|
||||||
7. `&&`
|
|
||||||
8. `||`
|
|
||||||
9. `&`
|
|
||||||
10. `//`
|
|
||||||
11. `default`
|
|
||||||
|
|
||||||
同じ優先順位の二項演算子は左結合である。
|
|
||||||
`default` は右結合である。
|
|
||||||
|
|
||||||
## Arithmetic operators
|
|
||||||
|
|
||||||
`+` `-` `*` `/` は具体的な `Int` / `Float` に対する四則演算である。
|
|
||||||
詳しくは [Arithmetic Expression](./expression/arithmetic.md) を参照する。
|
|
||||||
|
|
||||||
## Array concat operator
|
|
||||||
|
|
||||||
`++` は concrete array 同士を連結する演算子である。
|
|
||||||
要素は変換されず、左辺の要素の後に右辺の要素が並ぶ。
|
|
||||||
|
|
||||||
```dcdl
|
|
||||||
["read", "write"] ++ ["admin"]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Logical and comparison operators
|
|
||||||
|
|
||||||
`!` `&&` `||` は concrete `Bool` に対する論理演算である。
|
|
||||||
`&&` と `||` は短絡評価される。
|
|
||||||
|
|
||||||
`==` `!=` は concrete scalar value を比較する。
|
|
||||||
`<` `<=` `>` `>=` は concrete numeric value を比較する。
|
|
||||||
詳しくは [Logical and Comparison Expressions](./expression/logical-and-comparison.md) を参照する。
|
|
||||||
|
|
||||||
## `&`: 制約合成
|
## `&`: 制約合成
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -115,15 +115,12 @@ rec
|
||||||
主要な演算子は以下である。
|
主要な演算子は以下である。
|
||||||
|
|
||||||
```text
|
```text
|
||||||
+ - * / 四則演算
|
& 制約合成
|
||||||
++ 配列結合
|
// patch 合成
|
||||||
! && || 論理演算
|
default fallback 指定
|
||||||
== != < <= > >= 比較式
|
=> 関数
|
||||||
& 制約合成
|
. フィールド参照 / ドットパス定義
|
||||||
// patch 合成
|
|
||||||
default fallback 指定
|
|
||||||
=> 関数
|
|
||||||
. フィールド参照 / ドットパス定義
|
|
||||||
```
|
```
|
||||||
|
|
||||||
演算子の優先順位は [合成演算子](./operators.md) で定義する。
|
演算子の優先順位は未確定である。
|
||||||
|
詳細は [合成演算子](./operators.md) で定義する。
|
||||||
|
|
|
||||||
|
|
@ -74,87 +74,3 @@ base // {
|
||||||
body: (literal (string)))
|
body: (literal (string)))
|
||||||
(match_arm
|
(match_arm
|
||||||
body: (literal (string))))))))
|
body: (literal (string))))))))
|
||||||
|
|
||||||
==================
|
|
||||||
Arithmetic
|
|
||||||
==================
|
|
||||||
{
|
|
||||||
value = 1 + 2 * 3;
|
|
||||||
grouped = (1 + 2) / -3;
|
|
||||||
}
|
|
||||||
---
|
|
||||||
|
|
||||||
(source_file
|
|
||||||
(object
|
|
||||||
(field_definition
|
|
||||||
path: (field_path (identifier))
|
|
||||||
value: (binary_expression
|
|
||||||
left: (literal (integer))
|
|
||||||
right: (binary_expression
|
|
||||||
left: (literal (integer))
|
|
||||||
right: (literal (integer)))))
|
|
||||||
(field_definition
|
|
||||||
path: (field_path (identifier))
|
|
||||||
value: (binary_expression
|
|
||||||
left: (parenthesized_expression
|
|
||||||
(binary_expression
|
|
||||||
left: (literal (integer))
|
|
||||||
right: (literal (integer))))
|
|
||||||
right: (unary_expression
|
|
||||||
operand: (literal (integer)))))))
|
|
||||||
|
|
||||||
==================
|
|
||||||
Logical and comparison
|
|
||||||
==================
|
|
||||||
{
|
|
||||||
enabled = env == "prod" && replicas > 1;
|
|
||||||
disabled = !enabled || false;
|
|
||||||
}
|
|
||||||
---
|
|
||||||
|
|
||||||
(source_file
|
|
||||||
(object
|
|
||||||
(field_definition
|
|
||||||
path: (field_path (identifier))
|
|
||||||
value: (binary_expression
|
|
||||||
left: (binary_expression
|
|
||||||
left: (identifier)
|
|
||||||
right: (literal (string)))
|
|
||||||
right: (binary_expression
|
|
||||||
left: (identifier)
|
|
||||||
right: (literal (integer)))))
|
|
||||||
(field_definition
|
|
||||||
path: (field_path (identifier))
|
|
||||||
value: (binary_expression
|
|
||||||
left: (unary_expression
|
|
||||||
operand: (identifier))
|
|
||||||
right: (literal (boolean))))))
|
|
||||||
|
|
||||||
==================
|
|
||||||
Array concat
|
|
||||||
==================
|
|
||||||
{
|
|
||||||
roles = ["read"] ++ ["write", "admin"];
|
|
||||||
ports = [8000 + 80] ++ [9443];
|
|
||||||
}
|
|
||||||
---
|
|
||||||
|
|
||||||
(source_file
|
|
||||||
(object
|
|
||||||
(field_definition
|
|
||||||
path: (field_path (identifier))
|
|
||||||
value: (binary_expression
|
|
||||||
left: (array
|
|
||||||
(literal (string)))
|
|
||||||
right: (array
|
|
||||||
(literal (string))
|
|
||||||
(literal (string)))))
|
|
||||||
(field_definition
|
|
||||||
path: (field_path (identifier))
|
|
||||||
value: (binary_expression
|
|
||||||
left: (array
|
|
||||||
(binary_expression
|
|
||||||
left: (literal (integer))
|
|
||||||
right: (literal (integer))))
|
|
||||||
right: (array
|
|
||||||
(literal (integer)))))))
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,9 @@
|
||||||
const PREC = {
|
const PREC = {
|
||||||
DEFAULT: 1,
|
DEFAULT: 1,
|
||||||
PATCH: 2,
|
PATCH: 2,
|
||||||
COMPOSE: 3,
|
AND: 3,
|
||||||
OR: 4,
|
CALL: 7,
|
||||||
LOGICAL_AND: 5,
|
PATH: 8,
|
||||||
COMPARE: 6,
|
|
||||||
CONCAT: 7,
|
|
||||||
ADD: 8,
|
|
||||||
MUL: 9,
|
|
||||||
UNARY: 10,
|
|
||||||
CALL: 11,
|
|
||||||
PATH: 12,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function commaSep(rule) {
|
function commaSep(rule) {
|
||||||
|
|
@ -59,7 +52,6 @@ module.exports = grammar({
|
||||||
$.parenthesized_expression,
|
$.parenthesized_expression,
|
||||||
$.call_expression,
|
$.call_expression,
|
||||||
$.path_expression,
|
$.path_expression,
|
||||||
$.unary_expression,
|
|
||||||
$.binary_expression,
|
$.binary_expression,
|
||||||
$.default_expression,
|
$.default_expression,
|
||||||
),
|
),
|
||||||
|
|
@ -167,48 +159,13 @@ module.exports = grammar({
|
||||||
field('field', $.identifier),
|
field('field', $.identifier),
|
||||||
)),
|
)),
|
||||||
|
|
||||||
comparison_constraint: $ => prec.right(PREC.UNARY + 1, seq(
|
comparison_constraint: $ => prec(6, seq(
|
||||||
field('operator', choice('>', '>=', '<', '<=')),
|
field('operator', choice('>', '>=', '<', '<=')),
|
||||||
field('value', $._expression),
|
field('value', choice($.integer, $.float)),
|
||||||
)),
|
|
||||||
|
|
||||||
unary_expression: $ => prec(PREC.UNARY, seq(
|
|
||||||
field('operator', choice('-', '!')),
|
|
||||||
field('operand', $._expression),
|
|
||||||
)),
|
)),
|
||||||
|
|
||||||
binary_expression: $ => choice(
|
binary_expression: $ => choice(
|
||||||
prec.left(PREC.OR, seq(
|
prec.left(PREC.AND, seq(
|
||||||
field('left', $._expression),
|
|
||||||
field('operator', token(prec(2, '||'))),
|
|
||||||
field('right', $._expression),
|
|
||||||
)),
|
|
||||||
prec.left(PREC.LOGICAL_AND, seq(
|
|
||||||
field('left', $._expression),
|
|
||||||
field('operator', token(prec(2, '&&'))),
|
|
||||||
field('right', $._expression),
|
|
||||||
)),
|
|
||||||
prec.left(PREC.COMPARE, seq(
|
|
||||||
field('left', $._expression),
|
|
||||||
field('operator', choice('==', '!=', '>', '>=', '<', '<=')),
|
|
||||||
field('right', $._expression),
|
|
||||||
)),
|
|
||||||
prec.left(PREC.CONCAT, seq(
|
|
||||||
field('left', $._expression),
|
|
||||||
field('operator', token(prec(2, '++'))),
|
|
||||||
field('right', $._expression),
|
|
||||||
)),
|
|
||||||
prec.left(PREC.ADD, seq(
|
|
||||||
field('left', $._expression),
|
|
||||||
field('operator', choice('+', '-')),
|
|
||||||
field('right', $._expression),
|
|
||||||
)),
|
|
||||||
prec.left(PREC.MUL, seq(
|
|
||||||
field('left', $._expression),
|
|
||||||
field('operator', choice('*', '/')),
|
|
||||||
field('right', $._expression),
|
|
||||||
)),
|
|
||||||
prec.left(PREC.COMPOSE, seq(
|
|
||||||
field('left', $._expression),
|
field('left', $._expression),
|
||||||
field('operator', '&'),
|
field('operator', '&'),
|
||||||
field('right', $._expression),
|
field('right', $._expression),
|
||||||
|
|
|
||||||
|
|
@ -17,16 +17,6 @@
|
||||||
[
|
[
|
||||||
"&"
|
"&"
|
||||||
"//"
|
"//"
|
||||||
"+"
|
|
||||||
"-"
|
|
||||||
"*"
|
|
||||||
"/"
|
|
||||||
"++"
|
|
||||||
"&&"
|
|
||||||
"||"
|
|
||||||
"!"
|
|
||||||
"=="
|
|
||||||
"!="
|
|
||||||
"=>"
|
"=>"
|
||||||
"="
|
"="
|
||||||
">"
|
">"
|
||||||
|
|
|
||||||
307
editors/tree-sitter-decodal/src/grammar.json
generated
307
editors/tree-sitter-decodal/src/grammar.json
generated
|
|
@ -127,10 +127,6 @@
|
||||||
"type": "SYMBOL",
|
"type": "SYMBOL",
|
||||||
"name": "path_expression"
|
"name": "path_expression"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "SYMBOL",
|
|
||||||
"name": "unary_expression"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "SYMBOL",
|
"type": "SYMBOL",
|
||||||
"name": "binary_expression"
|
"name": "binary_expression"
|
||||||
|
|
@ -716,7 +712,7 @@
|
||||||
},
|
},
|
||||||
"call_expression": {
|
"call_expression": {
|
||||||
"type": "PREC_LEFT",
|
"type": "PREC_LEFT",
|
||||||
"value": 11,
|
"value": 7,
|
||||||
"content": {
|
"content": {
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
|
|
@ -786,7 +782,7 @@
|
||||||
},
|
},
|
||||||
"path_expression": {
|
"path_expression": {
|
||||||
"type": "PREC_LEFT",
|
"type": "PREC_LEFT",
|
||||||
"value": 12,
|
"value": 8,
|
||||||
"content": {
|
"content": {
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
|
|
@ -814,8 +810,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"comparison_constraint": {
|
"comparison_constraint": {
|
||||||
"type": "PREC_RIGHT",
|
"type": "PREC",
|
||||||
"value": 11,
|
"value": 6,
|
||||||
"content": {
|
"content": {
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
|
|
@ -847,44 +843,19 @@
|
||||||
{
|
{
|
||||||
"type": "FIELD",
|
"type": "FIELD",
|
||||||
"name": "value",
|
"name": "value",
|
||||||
"content": {
|
|
||||||
"type": "SYMBOL",
|
|
||||||
"name": "_expression"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"unary_expression": {
|
|
||||||
"type": "PREC",
|
|
||||||
"value": 10,
|
|
||||||
"content": {
|
|
||||||
"type": "SEQ",
|
|
||||||
"members": [
|
|
||||||
{
|
|
||||||
"type": "FIELD",
|
|
||||||
"name": "operator",
|
|
||||||
"content": {
|
"content": {
|
||||||
"type": "CHOICE",
|
"type": "CHOICE",
|
||||||
"members": [
|
"members": [
|
||||||
{
|
{
|
||||||
"type": "STRING",
|
"type": "SYMBOL",
|
||||||
"value": "-"
|
"name": "integer"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "STRING",
|
"type": "SYMBOL",
|
||||||
"value": "!"
|
"name": "float"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "FIELD",
|
|
||||||
"name": "operand",
|
|
||||||
"content": {
|
|
||||||
"type": "SYMBOL",
|
|
||||||
"name": "_expression"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -892,268 +863,6 @@
|
||||||
"binary_expression": {
|
"binary_expression": {
|
||||||
"type": "CHOICE",
|
"type": "CHOICE",
|
||||||
"members": [
|
"members": [
|
||||||
{
|
|
||||||
"type": "PREC_LEFT",
|
|
||||||
"value": 4,
|
|
||||||
"content": {
|
|
||||||
"type": "SEQ",
|
|
||||||
"members": [
|
|
||||||
{
|
|
||||||
"type": "FIELD",
|
|
||||||
"name": "left",
|
|
||||||
"content": {
|
|
||||||
"type": "SYMBOL",
|
|
||||||
"name": "_expression"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "FIELD",
|
|
||||||
"name": "operator",
|
|
||||||
"content": {
|
|
||||||
"type": "TOKEN",
|
|
||||||
"content": {
|
|
||||||
"type": "PREC",
|
|
||||||
"value": 2,
|
|
||||||
"content": {
|
|
||||||
"type": "STRING",
|
|
||||||
"value": "||"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "FIELD",
|
|
||||||
"name": "right",
|
|
||||||
"content": {
|
|
||||||
"type": "SYMBOL",
|
|
||||||
"name": "_expression"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "PREC_LEFT",
|
|
||||||
"value": 5,
|
|
||||||
"content": {
|
|
||||||
"type": "SEQ",
|
|
||||||
"members": [
|
|
||||||
{
|
|
||||||
"type": "FIELD",
|
|
||||||
"name": "left",
|
|
||||||
"content": {
|
|
||||||
"type": "SYMBOL",
|
|
||||||
"name": "_expression"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "FIELD",
|
|
||||||
"name": "operator",
|
|
||||||
"content": {
|
|
||||||
"type": "TOKEN",
|
|
||||||
"content": {
|
|
||||||
"type": "PREC",
|
|
||||||
"value": 2,
|
|
||||||
"content": {
|
|
||||||
"type": "STRING",
|
|
||||||
"value": "&&"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "FIELD",
|
|
||||||
"name": "right",
|
|
||||||
"content": {
|
|
||||||
"type": "SYMBOL",
|
|
||||||
"name": "_expression"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "PREC_LEFT",
|
|
||||||
"value": 6,
|
|
||||||
"content": {
|
|
||||||
"type": "SEQ",
|
|
||||||
"members": [
|
|
||||||
{
|
|
||||||
"type": "FIELD",
|
|
||||||
"name": "left",
|
|
||||||
"content": {
|
|
||||||
"type": "SYMBOL",
|
|
||||||
"name": "_expression"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "FIELD",
|
|
||||||
"name": "operator",
|
|
||||||
"content": {
|
|
||||||
"type": "CHOICE",
|
|
||||||
"members": [
|
|
||||||
{
|
|
||||||
"type": "STRING",
|
|
||||||
"value": "=="
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "STRING",
|
|
||||||
"value": "!="
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "STRING",
|
|
||||||
"value": ">"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "STRING",
|
|
||||||
"value": ">="
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "STRING",
|
|
||||||
"value": "<"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "STRING",
|
|
||||||
"value": "<="
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "FIELD",
|
|
||||||
"name": "right",
|
|
||||||
"content": {
|
|
||||||
"type": "SYMBOL",
|
|
||||||
"name": "_expression"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "PREC_LEFT",
|
|
||||||
"value": 7,
|
|
||||||
"content": {
|
|
||||||
"type": "SEQ",
|
|
||||||
"members": [
|
|
||||||
{
|
|
||||||
"type": "FIELD",
|
|
||||||
"name": "left",
|
|
||||||
"content": {
|
|
||||||
"type": "SYMBOL",
|
|
||||||
"name": "_expression"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "FIELD",
|
|
||||||
"name": "operator",
|
|
||||||
"content": {
|
|
||||||
"type": "TOKEN",
|
|
||||||
"content": {
|
|
||||||
"type": "PREC",
|
|
||||||
"value": 2,
|
|
||||||
"content": {
|
|
||||||
"type": "STRING",
|
|
||||||
"value": "++"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "FIELD",
|
|
||||||
"name": "right",
|
|
||||||
"content": {
|
|
||||||
"type": "SYMBOL",
|
|
||||||
"name": "_expression"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "PREC_LEFT",
|
|
||||||
"value": 8,
|
|
||||||
"content": {
|
|
||||||
"type": "SEQ",
|
|
||||||
"members": [
|
|
||||||
{
|
|
||||||
"type": "FIELD",
|
|
||||||
"name": "left",
|
|
||||||
"content": {
|
|
||||||
"type": "SYMBOL",
|
|
||||||
"name": "_expression"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "FIELD",
|
|
||||||
"name": "operator",
|
|
||||||
"content": {
|
|
||||||
"type": "CHOICE",
|
|
||||||
"members": [
|
|
||||||
{
|
|
||||||
"type": "STRING",
|
|
||||||
"value": "+"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "STRING",
|
|
||||||
"value": "-"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "FIELD",
|
|
||||||
"name": "right",
|
|
||||||
"content": {
|
|
||||||
"type": "SYMBOL",
|
|
||||||
"name": "_expression"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "PREC_LEFT",
|
|
||||||
"value": 9,
|
|
||||||
"content": {
|
|
||||||
"type": "SEQ",
|
|
||||||
"members": [
|
|
||||||
{
|
|
||||||
"type": "FIELD",
|
|
||||||
"name": "left",
|
|
||||||
"content": {
|
|
||||||
"type": "SYMBOL",
|
|
||||||
"name": "_expression"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "FIELD",
|
|
||||||
"name": "operator",
|
|
||||||
"content": {
|
|
||||||
"type": "CHOICE",
|
|
||||||
"members": [
|
|
||||||
{
|
|
||||||
"type": "STRING",
|
|
||||||
"value": "*"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "STRING",
|
|
||||||
"value": "/"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "FIELD",
|
|
||||||
"name": "right",
|
|
||||||
"content": {
|
|
||||||
"type": "SYMBOL",
|
|
||||||
"name": "_expression"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "PREC_LEFT",
|
"type": "PREC_LEFT",
|
||||||
"value": 3,
|
"value": 3,
|
||||||
|
|
|
||||||
310
editors/tree-sitter-decodal/src/node-types.json
generated
310
editors/tree-sitter-decodal/src/node-types.json
generated
|
|
@ -66,10 +66,6 @@
|
||||||
{
|
{
|
||||||
"type": "regex_literal",
|
"type": "regex_literal",
|
||||||
"named": true
|
"named": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "unary_expression",
|
|
||||||
"named": true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -141,10 +137,6 @@
|
||||||
{
|
{
|
||||||
"type": "regex_literal",
|
"type": "regex_literal",
|
||||||
"named": true
|
"named": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "unary_expression",
|
|
||||||
"named": true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
@ -152,65 +144,13 @@
|
||||||
"multiple": false,
|
"multiple": false,
|
||||||
"required": true,
|
"required": true,
|
||||||
"types": [
|
"types": [
|
||||||
{
|
|
||||||
"type": "!=",
|
|
||||||
"named": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "&",
|
"type": "&",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "&&",
|
|
||||||
"named": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "*",
|
|
||||||
"named": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "+",
|
|
||||||
"named": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "++",
|
|
||||||
"named": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "-",
|
|
||||||
"named": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "/",
|
|
||||||
"named": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "//",
|
"type": "//",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "<",
|
|
||||||
"named": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "<=",
|
|
||||||
"named": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "==",
|
|
||||||
"named": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": ">",
|
|
||||||
"named": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": ">=",
|
|
||||||
"named": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "||",
|
|
||||||
"named": false
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
@ -277,10 +217,6 @@
|
||||||
{
|
{
|
||||||
"type": "regex_literal",
|
"type": "regex_literal",
|
||||||
"named": true
|
"named": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "unary_expression",
|
|
||||||
"named": true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -358,10 +294,6 @@
|
||||||
{
|
{
|
||||||
"type": "regex_literal",
|
"type": "regex_literal",
|
||||||
"named": true
|
"named": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "unary_expression",
|
|
||||||
"named": true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -429,10 +361,6 @@
|
||||||
{
|
{
|
||||||
"type": "regex_literal",
|
"type": "regex_literal",
|
||||||
"named": true
|
"named": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "unary_expression",
|
|
||||||
"named": true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -468,67 +396,11 @@
|
||||||
"required": true,
|
"required": true,
|
||||||
"types": [
|
"types": [
|
||||||
{
|
{
|
||||||
"type": "array",
|
"type": "float",
|
||||||
"named": true
|
"named": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "binary_expression",
|
"type": "integer",
|
||||||
"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
|
"named": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -602,10 +474,6 @@
|
||||||
{
|
{
|
||||||
"type": "regex_literal",
|
"type": "regex_literal",
|
||||||
"named": true
|
"named": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "unary_expression",
|
|
||||||
"named": true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
@ -672,10 +540,6 @@
|
||||||
{
|
{
|
||||||
"type": "regex_literal",
|
"type": "regex_literal",
|
||||||
"named": true
|
"named": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "unary_expression",
|
|
||||||
"named": true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -758,10 +622,6 @@
|
||||||
{
|
{
|
||||||
"type": "regex_literal",
|
"type": "regex_literal",
|
||||||
"named": true
|
"named": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "unary_expression",
|
|
||||||
"named": true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -849,10 +709,6 @@
|
||||||
{
|
{
|
||||||
"type": "regex_literal",
|
"type": "regex_literal",
|
||||||
"named": true
|
"named": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "unary_expression",
|
|
||||||
"named": true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -951,10 +807,6 @@
|
||||||
{
|
{
|
||||||
"type": "regex_literal",
|
"type": "regex_literal",
|
||||||
"named": true
|
"named": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "unary_expression",
|
|
||||||
"named": true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -1064,10 +916,6 @@
|
||||||
{
|
{
|
||||||
"type": "regex_literal",
|
"type": "regex_literal",
|
||||||
"named": true
|
"named": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "unary_expression",
|
|
||||||
"named": true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
@ -1138,10 +986,6 @@
|
||||||
{
|
{
|
||||||
"type": "regex_literal",
|
"type": "regex_literal",
|
||||||
"named": true
|
"named": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "unary_expression",
|
|
||||||
"named": true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -1214,10 +1058,6 @@
|
||||||
{
|
{
|
||||||
"type": "regex_literal",
|
"type": "regex_literal",
|
||||||
"named": true
|
"named": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "unary_expression",
|
|
||||||
"named": true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -1315,10 +1155,6 @@
|
||||||
{
|
{
|
||||||
"type": "regex_literal",
|
"type": "regex_literal",
|
||||||
"named": true
|
"named": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "unary_expression",
|
|
||||||
"named": true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
@ -1401,10 +1237,6 @@
|
||||||
{
|
{
|
||||||
"type": "regex_literal",
|
"type": "regex_literal",
|
||||||
"named": true
|
"named": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "unary_expression",
|
|
||||||
"named": true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -1486,10 +1318,6 @@
|
||||||
{
|
{
|
||||||
"type": "regex_literal",
|
"type": "regex_literal",
|
||||||
"named": true
|
"named": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "unary_expression",
|
|
||||||
"named": true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -1566,120 +1394,14 @@
|
||||||
{
|
{
|
||||||
"type": "regex_literal",
|
"type": "regex_literal",
|
||||||
"named": true
|
"named": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "unary_expression",
|
|
||||||
"named": true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "unary_expression",
|
|
||||||
"named": true,
|
|
||||||
"fields": {
|
|
||||||
"operand": {
|
|
||||||
"multiple": false,
|
|
||||||
"required": true,
|
|
||||||
"types": [
|
|
||||||
{
|
|
||||||
"type": "array",
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"operator": {
|
|
||||||
"multiple": false,
|
|
||||||
"required": true,
|
|
||||||
"types": [
|
|
||||||
{
|
|
||||||
"type": "!",
|
|
||||||
"named": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "-",
|
|
||||||
"named": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "!",
|
|
||||||
"named": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "!=",
|
|
||||||
"named": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "&",
|
"type": "&",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "&&",
|
|
||||||
"named": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "(",
|
"type": "(",
|
||||||
"named": false
|
"named": false
|
||||||
|
|
@ -1688,34 +1410,14 @@
|
||||||
"type": ")",
|
"type": ")",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "*",
|
|
||||||
"named": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "+",
|
|
||||||
"named": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "++",
|
|
||||||
"named": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": ",",
|
"type": ",",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "-",
|
|
||||||
"named": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": ".",
|
"type": ".",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "/",
|
|
||||||
"named": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "//",
|
"type": "//",
|
||||||
"named": false
|
"named": false
|
||||||
|
|
@ -1740,10 +1442,6 @@
|
||||||
"type": "=",
|
"type": "=",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "==",
|
|
||||||
"named": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "=>",
|
"type": "=>",
|
||||||
"named": false
|
"named": false
|
||||||
|
|
@ -1824,10 +1522,6 @@
|
||||||
"type": "{",
|
"type": "{",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "||",
|
|
||||||
"named": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "}",
|
"type": "}",
|
||||||
"named": false
|
"named": false
|
||||||
|
|
|
||||||
11004
editors/tree-sitter-decodal/src/parser.c
generated
11004
editors/tree-sitter-decodal/src/parser.c
generated
File diff suppressed because it is too large
Load Diff
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
workers = 2 + 2;
|
|
||||||
memory_gib = 1.5 * 4;
|
|
||||||
port = 9000 + 443;
|
|
||||||
timeout_seconds = 30 / 2;
|
|
||||||
negative_offset = -3;
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
base_roles = ["read", "write"];
|
|
||||||
extra_roles = ["admin"];
|
|
||||||
roles = ["read", "write"] ++ ["admin"];
|
|
||||||
ports = [8000 + 80] ++ [9000 + 443];
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
let
|
|
||||||
env = "prod";
|
|
||||||
replicas = 3;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
is_prod = env == "prod";
|
|
||||||
scaled = replicas > 1;
|
|
||||||
enabled = env == "prod" && replicas > 1;
|
|
||||||
disabled = !(env == "prod" && replicas > 1);
|
|
||||||
safe = env != "dev" || replicas >= 1;
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
import { defineConfig } from 'astro/config';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
output: 'static',
|
|
||||||
});
|
|
||||||
6480
site/decodal-site/package-lock.json
generated
6480
site/decodal-site/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
|
@ -1,17 +0,0 @@
|
||||||
{
|
|
||||||
"name": "decodal-site",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"private": true,
|
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
|
||||||
"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"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@astrojs/check": "^0.9.4",
|
|
||||||
"astro": "^4.16.18",
|
|
||||||
"marked": "^12.0.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1
site/decodal-site/src/env.d.ts
vendored
1
site/decodal-site/src/env.d.ts
vendored
|
|
@ -1 +0,0 @@
|
||||||
/// <reference path="../.astro/types.d.ts" />
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
---
|
|
||||||
import { nav } from '../lib/docs.js';
|
|
||||||
import '../style.css';
|
|
||||||
|
|
||||||
const { title = 'Decodal', active = '', playground = false } = Astro.props;
|
|
||||||
|
|
||||||
function renderNav(items, prefix = []) {
|
|
||||||
return `<ul class="nav-tree">${items
|
|
||||||
.map((item, index) => {
|
|
||||||
const number = [...prefix, index + 1];
|
|
||||||
const href = `/docs/${item.slug}/`;
|
|
||||||
const activeClass = active === item.slug ? ' class="active"' : '';
|
|
||||||
const children = item.children ? renderNav(item.children, number) : '';
|
|
||||||
return `<li><a${activeClass} href="${href}"><span class="nav-number">${number.join('.')}.</span>${item.title}</a>${children}</li>`;
|
|
||||||
})
|
|
||||||
.join('')}</ul>`;
|
|
||||||
}
|
|
||||||
---
|
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>{title}</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header class="topbar">
|
|
||||||
<a class="brand" href="/docs/introduction/">Decodal</a>
|
|
||||||
<nav class="topnav">
|
|
||||||
<a href="/docs/introduction/">Docs</a>
|
|
||||||
<a href="/playground/">Playground</a>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
<div class={playground ? 'layout playground-layout' : 'layout'}>
|
|
||||||
{!playground && (
|
|
||||||
<aside class="sidebar">
|
|
||||||
<a class="sidebar-title" href="/docs/">Manual</a>
|
|
||||||
<nav set:html={renderNav(nav)} />
|
|
||||||
</aside>
|
|
||||||
)}
|
|
||||||
<main class={playground ? 'playground' : ''}>
|
|
||||||
<slot />
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,121 +0,0 @@
|
||||||
import { marked } from 'marked';
|
|
||||||
import { escapeAttribute, highlightCode } from './highlight.js';
|
|
||||||
|
|
||||||
const modules = import.meta.glob('../../../../doc/manual/souce/**/*.md', {
|
|
||||||
query: '?raw',
|
|
||||||
import: 'default',
|
|
||||||
eager: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const docs = Object.fromEntries(
|
|
||||||
Object.entries(modules).map(([path, content]) => {
|
|
||||||
const slug = path
|
|
||||||
.replace(/^\.\.\/\.\.\/\.\.\/\.\.\/doc\/manual\/souce\//, '')
|
|
||||||
.replace(/\.md$/, '')
|
|
||||||
.replace(/\/index$/, '');
|
|
||||||
return [slug || 'index', content];
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
export const nav = [
|
|
||||||
{ title: 'Introduction', slug: 'introduction' },
|
|
||||||
{
|
|
||||||
title: 'Language Specification',
|
|
||||||
slug: 'language',
|
|
||||||
children: [
|
|
||||||
{ title: 'Syntax', slug: 'language/syntax' },
|
|
||||||
{
|
|
||||||
title: 'Value',
|
|
||||||
slug: 'language/value',
|
|
||||||
children: [
|
|
||||||
{ title: 'String', slug: 'language/value/string' },
|
|
||||||
{ title: 'Int', slug: 'language/value/int' },
|
|
||||||
{ title: 'Float', slug: 'language/value/float' },
|
|
||||||
{ title: 'Bool', slug: 'language/value/bool' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Expression',
|
|
||||||
slug: 'language/expression',
|
|
||||||
children: [
|
|
||||||
{ title: 'Literal', slug: 'language/expression/literal' },
|
|
||||||
{ title: 'Identifier', slug: 'language/expression/identifier' },
|
|
||||||
{ title: 'Path Reference', slug: 'language/expression/path-reference' },
|
|
||||||
{ title: 'Object', slug: 'language/expression/object' },
|
|
||||||
{ title: 'Array', slug: 'language/expression/array' },
|
|
||||||
{ title: 'Function', slug: 'language/expression/function' },
|
|
||||||
{ title: 'Function Call', slug: 'language/expression/function-call' },
|
|
||||||
{ title: 'Let', slug: 'language/expression/let' },
|
|
||||||
{ title: 'Match', slug: 'language/expression/match' },
|
|
||||||
{ title: 'Import', slug: 'language/expression/import' },
|
|
||||||
{ title: 'Composition', slug: 'language/expression/composition' },
|
|
||||||
{ title: 'Default', slug: 'language/expression/default' },
|
|
||||||
{ title: 'Arithmetic', slug: 'language/expression/arithmetic' },
|
|
||||||
{ title: 'Logical and Comparison', slug: 'language/expression/logical-and-comparison' },
|
|
||||||
{ title: 'String Interpolation', slug: 'language/expression/string-interpolation' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{ title: 'Constraints and Defaults', slug: 'language/constraints-and-defaults' },
|
|
||||||
{ title: 'Operators', slug: 'language/operators' },
|
|
||||||
{ title: 'Functions', slug: 'language/functions' },
|
|
||||||
{ title: 'Modules and Imports', slug: 'language/modules-and-imports' },
|
|
||||||
{ title: 'Evaluation Semantics', slug: 'language/evaluation' },
|
|
||||||
{ title: 'Materialization and Errors', slug: 'language/materialization-and-errors' },
|
|
||||||
{ title: 'Naming', slug: 'language/naming' },
|
|
||||||
{ title: 'Examples', slug: 'language/examples' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Implementation Design',
|
|
||||||
slug: 'design',
|
|
||||||
children: [
|
|
||||||
{ title: 'Execution Pipeline', slug: 'design/execution-pipeline' },
|
|
||||||
{ title: 'Runtime Model', slug: 'design/runtime-model' },
|
|
||||||
{ title: 'Thunk and Lazy Evaluation', slug: 'design/thunk-and-lazy-evaluation' },
|
|
||||||
{ title: 'Composition and Materialization', slug: 'design/composition-and-materialization' },
|
|
||||||
{ title: 'Diagnostics and Fallback', slug: 'design/diagnostics-and-fallback' },
|
|
||||||
{ title: 'Embedding API', slug: 'design/embedding-api' },
|
|
||||||
{ title: 'Features', slug: 'design/features' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{ title: 'Development', slug: 'development' },
|
|
||||||
{ title: 'Open Issues', slug: 'open-issues' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const renderer = new marked.Renderer();
|
|
||||||
|
|
||||||
renderer.code = (code, language = '') => {
|
|
||||||
const normalizedLanguage = language.split(/\s+/)[0] ?? '';
|
|
||||||
const className = normalizedLanguage ? ` class="language-${escapeAttribute(normalizedLanguage)}"` : '';
|
|
||||||
return `<pre class="code-block"><code${className}>${highlightCode(code, normalizedLanguage)}</code></pre>`;
|
|
||||||
};
|
|
||||||
|
|
||||||
renderer.codespan = (code) => `<code>${code}</code>`;
|
|
||||||
|
|
||||||
marked.setOptions({ gfm: true, renderer });
|
|
||||||
|
|
||||||
export function allDocSlugs() {
|
|
||||||
return Object.keys(docs).filter((slug) => slug !== 'index');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function renderMarkdown(slug) {
|
|
||||||
const source = docs[slug] ?? docs.index;
|
|
||||||
const html = marked.parse(source ?? '# Not found\n');
|
|
||||||
return html.replace(/href="([^"#][^"]*)\.md(#[^"]*)?"/g, (_all, href, hash = '') => {
|
|
||||||
const target = normalizeDocLink(slug, href);
|
|
||||||
return `href="/docs/${target}/${hash}"`;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeDocLink(currentSlug, href) {
|
|
||||||
const base = currentSlug.includes('/') ? currentSlug.split('/').slice(0, -1) : [];
|
|
||||||
const parts = [...base, ...href.split('/')];
|
|
||||||
const out = [];
|
|
||||||
for (const part of parts) {
|
|
||||||
if (!part || part === '.') continue;
|
|
||||||
if (part === '..') out.pop();
|
|
||||||
else out.push(part);
|
|
||||||
}
|
|
||||||
if (out[out.length - 1] === 'index') out.pop();
|
|
||||||
return out.join('/') || 'index';
|
|
||||||
}
|
|
||||||
|
|
@ -1,203 +0,0 @@
|
||||||
const DECODAL_KEYWORDS = new Set(['let', 'in', 'fn', 'match', 'import', 'default']);
|
|
||||||
const DECODAL_TYPES = new Set(['String', 'Int', 'Float', 'Bool']);
|
|
||||||
const DECODAL_LITERALS = new Set(['true', 'false']);
|
|
||||||
|
|
||||||
const HTML_ESCAPE = {
|
|
||||||
'&': '&',
|
|
||||||
'<': '<',
|
|
||||||
'>': '>',
|
|
||||||
'"': '"',
|
|
||||||
"'": ''',
|
|
||||||
};
|
|
||||||
|
|
||||||
export function escapeHtml(value) {
|
|
||||||
return String(value).replace(/[&<>"']/g, (char) => HTML_ESCAPE[char]);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function escapeAttribute(value) {
|
|
||||||
return escapeHtml(value).replace(/`/g, '`');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function highlightCode(code, language = '') {
|
|
||||||
const normalized = language.toLowerCase();
|
|
||||||
if (normalized === 'dcdl' || normalized === 'decodal') return highlightDecodal(code);
|
|
||||||
if (normalized === 'sh' || normalized === 'bash' || normalized === 'shell') return highlightShell(code);
|
|
||||||
return escapeHtml(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function highlightDecodal(source) {
|
|
||||||
let html = '';
|
|
||||||
let index = 0;
|
|
||||||
let canEndExpression = false;
|
|
||||||
|
|
||||||
while (index < source.length) {
|
|
||||||
const char = source[index];
|
|
||||||
const next = source[index + 1];
|
|
||||||
|
|
||||||
if (char === '#') {
|
|
||||||
const end = readUntilLineEnd(source, index);
|
|
||||||
html += token('comment', source.slice(index, end));
|
|
||||||
index = end;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (char === '"') {
|
|
||||||
const end = readString(source, index);
|
|
||||||
html += token('string', source.slice(index, end));
|
|
||||||
index = end;
|
|
||||||
canEndExpression = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (char === '/' && next && next !== '/' && !canEndExpression) {
|
|
||||||
const end = readRegex(source, index);
|
|
||||||
html += token('regex', source.slice(index, end));
|
|
||||||
index = end;
|
|
||||||
canEndExpression = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNumberStart(source, index)) {
|
|
||||||
const end = readNumber(source, index);
|
|
||||||
html += token('number', source.slice(index, end));
|
|
||||||
index = end;
|
|
||||||
canEndExpression = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isIdentifierStart(char)) {
|
|
||||||
const end = readIdentifier(source, index);
|
|
||||||
const ident = source.slice(index, end);
|
|
||||||
if (DECODAL_KEYWORDS.has(ident)) html += token('keyword', ident);
|
|
||||||
else if (DECODAL_TYPES.has(ident)) html += token('type', ident);
|
|
||||||
else if (DECODAL_LITERALS.has(ident)) html += token('literal', ident);
|
|
||||||
else html += escapeHtml(ident);
|
|
||||||
canEndExpression = true;
|
|
||||||
index = end;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isOperatorStart(char)) {
|
|
||||||
const end = readOperator(source, index);
|
|
||||||
html += token('operator', source.slice(index, end));
|
|
||||||
canEndExpression = /[})\]]/.test(char);
|
|
||||||
index = end;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
html += escapeHtml(char);
|
|
||||||
index += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return html;
|
|
||||||
}
|
|
||||||
|
|
||||||
function highlightShell(source) {
|
|
||||||
return source
|
|
||||||
.split(/(\n)/)
|
|
||||||
.map((line) => {
|
|
||||||
if (line === '\n') return line;
|
|
||||||
const commentIndex = line.indexOf('#');
|
|
||||||
const code = commentIndex >= 0 ? line.slice(0, commentIndex) : line;
|
|
||||||
const comment = commentIndex >= 0 ? line.slice(commentIndex) : '';
|
|
||||||
return `${highlightShellCode(code)}${comment ? token('comment', comment) : ''}`;
|
|
||||||
})
|
|
||||||
.join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
function highlightShellCode(source) {
|
|
||||||
const escaped = escapeHtml(source);
|
|
||||||
return escaped
|
|
||||||
.replace(/("[^&]*(?:&(?!quot;)[^&]*)*"|'[^']*')/g, '<span class="tok-string">$1</span>')
|
|
||||||
.replace(/(^|\s)(--?[A-Za-z0-9][A-Za-z0-9-]*)/g, '$1<span class="tok-operator">$2</span>');
|
|
||||||
}
|
|
||||||
|
|
||||||
function token(kind, value) {
|
|
||||||
return `<span class="tok-${kind}">${escapeHtml(value)}</span>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function readUntilLineEnd(source, start) {
|
|
||||||
const end = source.indexOf('\n', start);
|
|
||||||
return end < 0 ? source.length : end;
|
|
||||||
}
|
|
||||||
|
|
||||||
function readString(source, start) {
|
|
||||||
let index = start + 1;
|
|
||||||
while (index < source.length) {
|
|
||||||
if (source[index] === '\\') {
|
|
||||||
index += 2;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (source[index] === '"') return index + 1;
|
|
||||||
index += 1;
|
|
||||||
}
|
|
||||||
return source.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
function readRegex(source, start) {
|
|
||||||
let index = start + 1;
|
|
||||||
let inClass = false;
|
|
||||||
while (index < source.length) {
|
|
||||||
const char = source[index];
|
|
||||||
if (char === '\\') {
|
|
||||||
index += 2;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (char === '[') inClass = true;
|
|
||||||
else if (char === ']') inClass = false;
|
|
||||||
else if (char === '/' && !inClass) {
|
|
||||||
index += 1;
|
|
||||||
while (/[A-Za-z]/.test(source[index] ?? '')) index += 1;
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
if (char === '\n') return index;
|
|
||||||
index += 1;
|
|
||||||
}
|
|
||||||
return source.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isNumberStart(source, index) {
|
|
||||||
const char = source[index];
|
|
||||||
const next = source[index + 1];
|
|
||||||
const prev = source[index - 1];
|
|
||||||
if (/[0-9]/.test(char)) return true;
|
|
||||||
return char === '-' && /[0-9]/.test(next ?? '') && !isIdentifierPart(prev ?? '');
|
|
||||||
}
|
|
||||||
|
|
||||||
function readNumber(source, start) {
|
|
||||||
let index = start;
|
|
||||||
if (source[index] === '-') index += 1;
|
|
||||||
while (/[0-9]/.test(source[index] ?? '')) index += 1;
|
|
||||||
if (source[index] === '.' && /[0-9]/.test(source[index + 1] ?? '')) {
|
|
||||||
index += 1;
|
|
||||||
while (/[0-9]/.test(source[index] ?? '')) index += 1;
|
|
||||||
}
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isIdentifierStart(char) {
|
|
||||||
return /[A-Za-z_]/.test(char ?? '');
|
|
||||||
}
|
|
||||||
|
|
||||||
function isIdentifierPart(char) {
|
|
||||||
return /[A-Za-z0-9_-]/.test(char ?? '');
|
|
||||||
}
|
|
||||||
|
|
||||||
function readIdentifier(source, start) {
|
|
||||||
let index = start + 1;
|
|
||||||
while (isIdentifierPart(source[index])) index += 1;
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isOperatorStart(char) {
|
|
||||||
return '&=<>!|:;,.{}()[]+-*/'.includes(char ?? '');
|
|
||||||
}
|
|
||||||
|
|
||||||
function readOperator(source, start) {
|
|
||||||
let index = start + 1;
|
|
||||||
if ('&|/+'.includes(source[start]) && source[index] === source[start]) return index + 1;
|
|
||||||
if ((source[start] === '<' || source[start] === '>' || source[start] === '=' || source[start] === '!') && source[index] === '=') {
|
|
||||||
index += 1;
|
|
||||||
}
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
---
|
|
||||||
import ManualLayout from '../../layouts/ManualLayout.astro';
|
|
||||||
import { allDocSlugs, renderMarkdown } from '../../lib/docs.js';
|
|
||||||
|
|
||||||
export function getStaticPaths() {
|
|
||||||
return allDocSlugs().map((slug) => ({
|
|
||||||
params: { slug },
|
|
||||||
props: { slug },
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
const { slug } = Astro.props;
|
|
||||||
const html = renderMarkdown(slug);
|
|
||||||
---
|
|
||||||
<ManualLayout title={`Decodal - ${slug}`} active={slug}>
|
|
||||||
<article class="markdown" set:html={html} />
|
|
||||||
</ManualLayout>
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
---
|
|
||||||
return Astro.redirect('/docs/introduction/');
|
|
||||||
---
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
---
|
|
||||||
return Astro.redirect('/docs/introduction/');
|
|
||||||
---
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
---
|
|
||||||
import ManualLayout from '../layouts/ManualLayout.astro';
|
|
||||||
---
|
|
||||||
<ManualLayout title="Decodal Playground" playground>
|
|
||||||
<section class="playground-page">
|
|
||||||
<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">
|
|
||||||
<p id="status" class="status">Loading WASM...</p>
|
|
||||||
<button id="run" disabled>Run</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="playground-shell">
|
|
||||||
<aside class="file-panel">
|
|
||||||
<div class="panel-header">
|
|
||||||
<span>Files</span>
|
|
||||||
<button id="new-file" type="button">New</button>
|
|
||||||
</div>
|
|
||||||
<div id="file-tree" class="file-tree"></div>
|
|
||||||
<button id="delete-file" class="danger-button" type="button">Delete file</button>
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
<label class="pane input-pane">
|
|
||||||
<span id="active-file">Input</span>
|
|
||||||
<div class="editor-wrap">
|
|
||||||
<pre id="source-highlight" aria-hidden="true"></pre>
|
|
||||||
<textarea id="source" spellcheck="false"></textarea>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<section class="pane output-pane">
|
|
||||||
<span>Output</span>
|
|
||||||
<pre id="output"></pre>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<script>
|
|
||||||
import '../scripts/playground.js';
|
|
||||||
</script>
|
|
||||||
</ManualLayout>
|
|
||||||
|
|
@ -1,193 +0,0 @@
|
||||||
import init, { evaluateProject } from '../wasm/decodal_wasm.js';
|
|
||||||
import { highlightDecodal } from '../lib/highlight.js';
|
|
||||||
|
|
||||||
const STORAGE_KEY = 'decodal-playground-project-v1';
|
|
||||||
|
|
||||||
const starterFiles = {
|
|
||||||
'main.dcdl': `let
|
|
||||||
schema = import "./schemas/service.dcdl";
|
|
||||||
in
|
|
||||||
schema.Service & {
|
|
||||||
name = "api";
|
|
||||||
port = 9000 + 443;
|
|
||||||
tags = ["web"] ++ ["prod"];
|
|
||||||
feature.enable = 9000 + 443 > 9000 && true;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
'schemas/service.dcdl': `Service = {
|
|
||||||
name = String;
|
|
||||||
port = Int & > 443 default 8443;
|
|
||||||
feature.enable = Bool default true;
|
|
||||||
};
|
|
||||||
`,
|
|
||||||
};
|
|
||||||
|
|
||||||
const source = document.getElementById('source');
|
|
||||||
const sourceHighlight = document.getElementById('source-highlight');
|
|
||||||
const output = document.getElementById('output');
|
|
||||||
const run = document.getElementById('run');
|
|
||||||
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 project = loadProject();
|
|
||||||
|
|
||||||
setActiveFile(project.activePath);
|
|
||||||
renderFileTree();
|
|
||||||
updateHighlight();
|
|
||||||
|
|
||||||
function loadProject() {
|
|
||||||
try {
|
|
||||||
const stored = JSON.parse(localStorage.getItem(STORAGE_KEY) ?? 'null');
|
|
||||||
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 };
|
|
||||||
}
|
|
||||||
} catch (_error) {
|
|
||||||
// Fall back to the starter project.
|
|
||||||
}
|
|
||||||
return { files: { ...starterFiles }, activePath: 'main.dcdl' };
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeFiles(files) {
|
|
||||||
return Object.fromEntries(
|
|
||||||
Object.entries(files)
|
|
||||||
.filter(([_path, value]) => typeof value === 'string')
|
|
||||||
.map(([path, value]) => [normalizePath(path), value])
|
|
||||||
.filter(([path]) => path),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveProject() {
|
|
||||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(project));
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizePath(path) {
|
|
||||||
const parts = [];
|
|
||||||
for (const part of String(path).replaceAll('\\', '/').split('/')) {
|
|
||||||
if (!part || part === '.') continue;
|
|
||||||
if (part === '..') parts.pop();
|
|
||||||
else parts.push(part);
|
|
||||||
}
|
|
||||||
return parts.join('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
function setActiveFile(path) {
|
|
||||||
const normalized = normalizePath(path);
|
|
||||||
if (project.files[normalized] === undefined) return;
|
|
||||||
project.activePath = normalized;
|
|
||||||
source.value = project.files[normalized];
|
|
||||||
activeFile.textContent = normalized;
|
|
||||||
deleteFile.disabled = Object.keys(project.files).length <= 1;
|
|
||||||
updateHighlight();
|
|
||||||
syncHighlightScroll();
|
|
||||||
renderFileTree();
|
|
||||||
saveProject();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateHighlight() {
|
|
||||||
sourceHighlight.innerHTML = `${highlightDecodal(source.value)}\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function syncHighlightScroll() {
|
|
||||||
sourceHighlight.scrollTop = source.scrollTop;
|
|
||||||
sourceHighlight.scrollLeft = source.scrollLeft;
|
|
||||||
}
|
|
||||||
|
|
||||||
function execute() {
|
|
||||||
project.files[project.activePath] = source.value;
|
|
||||||
saveProject();
|
|
||||||
const result = JSON.parse(evaluateProject(project.activePath, JSON.stringify(project.files)));
|
|
||||||
output.textContent = result.ok ? result.output : result.error;
|
|
||||||
output.classList.toggle('error', !result.ok);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderFileTree() {
|
|
||||||
const tree = buildTree(Object.keys(project.files).sort());
|
|
||||||
fileTree.replaceChildren(renderTreeList(tree.children));
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildTree(paths) {
|
|
||||||
const root = { name: '', children: new Map(), path: '' };
|
|
||||||
for (const path of paths) {
|
|
||||||
const parts = path.split('/');
|
|
||||||
let node = root;
|
|
||||||
let currentPath = '';
|
|
||||||
parts.forEach((part, index) => {
|
|
||||||
currentPath = currentPath ? `${currentPath}/${part}` : part;
|
|
||||||
if (!node.children.has(part)) {
|
|
||||||
node.children.set(part, { name: part, children: new Map(), path: currentPath, file: index + 1 === parts.length });
|
|
||||||
}
|
|
||||||
node = node.children.get(part);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderTreeList(children) {
|
|
||||||
const list = document.createElement('ul');
|
|
||||||
for (const child of [...children.values()].sort(compareNodes)) {
|
|
||||||
const item = document.createElement('li');
|
|
||||||
if (child.file) {
|
|
||||||
const button = document.createElement('button');
|
|
||||||
button.type = 'button';
|
|
||||||
button.className = child.path === project.activePath ? 'file active' : 'file';
|
|
||||||
button.textContent = child.name;
|
|
||||||
button.title = child.path;
|
|
||||||
button.addEventListener('click', () => setActiveFile(child.path));
|
|
||||||
item.append(button);
|
|
||||||
} else {
|
|
||||||
const label = document.createElement('span');
|
|
||||||
label.className = 'folder';
|
|
||||||
label.textContent = `${child.name}/`;
|
|
||||||
item.append(label, renderTreeList(child.children));
|
|
||||||
}
|
|
||||||
list.append(item);
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
function compareNodes(a, b) {
|
|
||||||
if (a.file !== b.file) return a.file ? 1 : -1;
|
|
||||||
return a.name.localeCompare(b.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await init();
|
|
||||||
run.disabled = false;
|
|
||||||
status.textContent = '';
|
|
||||||
execute();
|
|
||||||
} catch (error) {
|
|
||||||
status.textContent = `Failed to load WASM: ${error?.message ?? error}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
run.addEventListener('click', execute);
|
|
||||||
newFile.addEventListener('click', () => {
|
|
||||||
const path = normalizePath(prompt('New virtual file path', 'schemas/types.dcdl') ?? '');
|
|
||||||
if (!path) return;
|
|
||||||
if (project.files[path] !== undefined) {
|
|
||||||
setActiveFile(path);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
project.files[path] = path.endsWith('.dcdl') ? '' : '// Decodal source\n';
|
|
||||||
setActiveFile(path);
|
|
||||||
});
|
|
||||||
deleteFile.addEventListener('click', () => {
|
|
||||||
if (Object.keys(project.files).length <= 1) return;
|
|
||||||
if (!confirm(`Delete ${project.activePath}?`)) return;
|
|
||||||
delete project.files[project.activePath];
|
|
||||||
setActiveFile(Object.keys(project.files).sort()[0]);
|
|
||||||
});
|
|
||||||
source.addEventListener('input', () => {
|
|
||||||
project.files[project.activePath] = source.value;
|
|
||||||
updateHighlight();
|
|
||||||
syncHighlightScroll();
|
|
||||||
saveProject();
|
|
||||||
});
|
|
||||||
source.addEventListener('scroll', syncHighlightScroll);
|
|
||||||
source.addEventListener('keydown', (event) => {
|
|
||||||
if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') execute();
|
|
||||||
});
|
|
||||||
|
|
@ -1,467 +0,0 @@
|
||||||
:root {
|
|
||||||
color-scheme: light;
|
|
||||||
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
||||||
--bg: #f7f7f8;
|
|
||||||
--surface: #ffffff;
|
|
||||||
--text: #1f2328;
|
|
||||||
--heading: #111827;
|
|
||||||
--muted: #4b5563;
|
|
||||||
--subtle: #64748b;
|
|
||||||
--border: #e5e7eb;
|
|
||||||
--link: #2563eb;
|
|
||||||
--topbar-bg: #111827;
|
|
||||||
--topbar-text: #ffffff;
|
|
||||||
--topbar-link: #dbeafe;
|
|
||||||
--active-bg: #dbeafe;
|
|
||||||
--active-text: #1d4ed8;
|
|
||||||
--nav-number: #94a3b8;
|
|
||||||
--shadow: 0 8px 24px rgb(15 23 42 / 0.05);
|
|
||||||
--inline-code-bg: #eef2ff;
|
|
||||||
--inline-code-text: #3730a3;
|
|
||||||
--editor-bg: #0f172a;
|
|
||||||
--editor-text: #e5e7eb;
|
|
||||||
--danger: #b91c1c;
|
|
||||||
--error: #fecaca;
|
|
||||||
--selection: rgb(59 130 246 / 0.35);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
color-scheme: dark;
|
|
||||||
--bg: #0b1120;
|
|
||||||
--surface: #111827;
|
|
||||||
--text: #d1d5db;
|
|
||||||
--heading: #f9fafb;
|
|
||||||
--muted: #9ca3af;
|
|
||||||
--subtle: #94a3b8;
|
|
||||||
--border: #334155;
|
|
||||||
--link: #60a5fa;
|
|
||||||
--topbar-bg: #020617;
|
|
||||||
--topbar-text: #f8fafc;
|
|
||||||
--topbar-link: #bfdbfe;
|
|
||||||
--active-bg: #1e3a8a;
|
|
||||||
--active-text: #bfdbfe;
|
|
||||||
--nav-number: #64748b;
|
|
||||||
--shadow: 0 8px 24px rgb(0 0 0 / 0.25);
|
|
||||||
--inline-code-bg: #1e293b;
|
|
||||||
--inline-code-text: #bfdbfe;
|
|
||||||
--danger: #fca5a5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background: var(--bg);
|
|
||||||
color: var(--text);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--link);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.topbar {
|
|
||||||
align-items: center;
|
|
||||||
background: var(--topbar-bg);
|
|
||||||
color: var(--topbar-text);
|
|
||||||
display: flex;
|
|
||||||
height: 56px;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 0 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand {
|
|
||||||
color: var(--topbar-text);
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.topnav {
|
|
||||||
display: flex;
|
|
||||||
gap: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.topnav a {
|
|
||||||
color: var(--topbar-link);
|
|
||||||
}
|
|
||||||
|
|
||||||
.layout {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 300px minmax(0, 1fr);
|
|
||||||
min-height: calc(100vh - 56px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.playground-layout {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar {
|
|
||||||
background: var(--surface);
|
|
||||||
border-right: 1px solid var(--border);
|
|
||||||
overflow: auto;
|
|
||||||
padding: 22px 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-title {
|
|
||||||
color: var(--heading);
|
|
||||||
display: block;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-tree {
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-tree .nav-tree {
|
|
||||||
margin: 4px 0 6px 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-tree a {
|
|
||||||
border-radius: 6px;
|
|
||||||
color: var(--text);
|
|
||||||
display: flex;
|
|
||||||
font-size: 14px;
|
|
||||||
gap: 6px;
|
|
||||||
line-height: 1.35;
|
|
||||||
padding: 4px 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-number {
|
|
||||||
color: var(--nav-number);
|
|
||||||
flex: 0 0 auto;
|
|
||||||
font-variant-numeric: tabular-nums;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-tree a.active {
|
|
||||||
background: var(--active-bg);
|
|
||||||
color: var(--active-text);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-tree a.active .nav-number {
|
|
||||||
color: var(--link);
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
|
||||||
min-width: 0;
|
|
||||||
padding: 36px;
|
|
||||||
}
|
|
||||||
|
|
||||||
main.playground {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown {
|
|
||||||
background: var(--surface);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 12px;
|
|
||||||
box-shadow: var(--shadow);
|
|
||||||
margin: 0 auto;
|
|
||||||
max-width: 920px;
|
|
||||||
padding: 24px 36px 42px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown h1,
|
|
||||||
.markdown h2,
|
|
||||||
.markdown h3 {
|
|
||||||
color: var(--heading);
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown pre,
|
|
||||||
.output-pane pre {
|
|
||||||
background: var(--editor-bg);
|
|
||||||
border-radius: 10px;
|
|
||||||
color: var(--editor-text);
|
|
||||||
overflow: auto;
|
|
||||||
padding: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown code {
|
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown :not(pre) > code {
|
|
||||||
background: var(--inline-code-bg);
|
|
||||||
border-radius: 4px;
|
|
||||||
color: var(--inline-code-text);
|
|
||||||
padding: 1px 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
display: block;
|
|
||||||
margin: 20px 0;
|
|
||||||
overflow-x: auto;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown th,
|
|
||||||
.markdown td {
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
padding: 8px 10px;
|
|
||||||
text-align: left;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown th {
|
|
||||||
background: var(--inline-code-bg);
|
|
||||||
color: var(--heading);
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown tr:nth-child(even) td {
|
|
||||||
background: color-mix(in srgb, var(--surface) 92%, var(--border));
|
|
||||||
}
|
|
||||||
|
|
||||||
.tok-keyword {
|
|
||||||
color: #93c5fd;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tok-type {
|
|
||||||
color: #67e8f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tok-literal,
|
|
||||||
.tok-number {
|
|
||||||
color: #fbbf24;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tok-string,
|
|
||||||
.tok-regex {
|
|
||||||
color: #86efac;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tok-comment {
|
|
||||||
color: var(--nav-number);
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tok-operator {
|
|
||||||
color: #f9a8d4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.playground-page {
|
|
||||||
height: calc(100vh - 76px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.playground-header {
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
gap: 16px;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.playground-header h1 {
|
|
||||||
font-size: 20px;
|
|
||||||
margin: 0 0 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.playground-header p {
|
|
||||||
color: var(--muted);
|
|
||||||
font-size: 13px;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.playground-header code {
|
|
||||||
background: var(--inline-code-bg);
|
|
||||||
border-radius: 4px;
|
|
||||||
color: var(--inline-code-text);
|
|
||||||
padding: 1px 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.playground-actions {
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
background: var(--link);
|
|
||||||
border: 0;
|
|
||||||
border-radius: 8px;
|
|
||||||
color: var(--topbar-text);
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: 700;
|
|
||||||
padding: 8px 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:disabled {
|
|
||||||
cursor: not-allowed;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.playground-shell {
|
|
||||||
display: grid;
|
|
||||||
gap: 10px;
|
|
||||||
grid-template-columns: 220px minmax(360px, 1.25fr) minmax(320px, 1fr);
|
|
||||||
height: calc(100% - 52px);
|
|
||||||
min-height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-panel,
|
|
||||||
.pane {
|
|
||||||
background: var(--surface);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 10px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
min-height: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-header,
|
|
||||||
.pane > span {
|
|
||||||
align-items: center;
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
color: var(--text);
|
|
||||||
display: flex;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 700;
|
|
||||||
justify-content: space-between;
|
|
||||||
min-height: 38px;
|
|
||||||
padding: 0 10px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-header button {
|
|
||||||
font-size: 12px;
|
|
||||||
padding: 5px 8px;
|
|
||||||
text-transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-tree {
|
|
||||||
flex: 1;
|
|
||||||
min-height: 0;
|
|
||||||
overflow: auto;
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-tree ul {
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
padding-left: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-tree > ul {
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-tree li {
|
|
||||||
margin: 2px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-tree .folder {
|
|
||||||
color: var(--subtle);
|
|
||||||
display: block;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 700;
|
|
||||||
padding: 4px 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-tree .file {
|
|
||||||
background: transparent;
|
|
||||||
border-radius: 6px;
|
|
||||||
color: var(--text);
|
|
||||||
display: block;
|
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 500;
|
|
||||||
padding: 5px 7px;
|
|
||||||
text-align: left;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-tree .file:hover,
|
|
||||||
.file-tree .file.active {
|
|
||||||
background: var(--active-bg);
|
|
||||||
color: var(--active-text);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.danger-button {
|
|
||||||
background: transparent;
|
|
||||||
border-top: 1px solid var(--border);
|
|
||||||
border-radius: 0;
|
|
||||||
color: var(--danger);
|
|
||||||
padding: 9px 10px;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor-wrap {
|
|
||||||
background: var(--editor-bg);
|
|
||||||
flex: 1;
|
|
||||||
min-height: 0;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor-wrap pre,
|
|
||||||
.editor-wrap textarea {
|
|
||||||
border: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
font: 14px/1.5 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
|
||||||
inset: 0;
|
|
||||||
margin: 0;
|
|
||||||
overflow: auto;
|
|
||||||
padding: 12px;
|
|
||||||
position: absolute;
|
|
||||||
tab-size: 2;
|
|
||||||
white-space: pre;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor-wrap pre {
|
|
||||||
color: var(--editor-text);
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor-wrap textarea {
|
|
||||||
background: transparent;
|
|
||||||
caret-color: var(--editor-text);
|
|
||||||
color: transparent;
|
|
||||||
outline: none;
|
|
||||||
resize: none;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor-wrap textarea::selection {
|
|
||||||
background: var(--selection);
|
|
||||||
}
|
|
||||||
|
|
||||||
.output-pane pre {
|
|
||||||
border-radius: 0;
|
|
||||||
flex: 1;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.output-pane pre.error {
|
|
||||||
color: var(--error);
|
|
||||||
}
|
|
||||||
|
|
||||||
.status {
|
|
||||||
color: var(--muted);
|
|
||||||
font-size: 13px;
|
|
||||||
margin: 0;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
|
||||||
.layout {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
.sidebar {
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
border-right: 0;
|
|
||||||
max-height: 260px;
|
|
||||||
}
|
|
||||||
.playground-shell {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1
site/decodal-site/src/wasm/.gitignore
vendored
1
site/decodal-site/src/wasm/.gitignore
vendored
|
|
@ -1 +0,0 @@
|
||||||
# wasm-pack output is committed for the playground build.
|
|
||||||
40
site/decodal-site/src/wasm/decodal_wasm.d.ts
vendored
40
site/decodal-site/src/wasm/decodal_wasm.d.ts
vendored
|
|
@ -1,40 +0,0 @@
|
||||||
/* tslint:disable */
|
|
||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
export function evaluate(source: string): string;
|
|
||||||
|
|
||||||
export function evaluateProject(entry: string, files_json: string): string;
|
|
||||||
|
|
||||||
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
|
||||||
|
|
||||||
export interface InitOutput {
|
|
||||||
readonly memory: WebAssembly.Memory;
|
|
||||||
readonly evaluate: (a: number, b: number, c: number) => void;
|
|
||||||
readonly evaluateProject: (a: number, b: number, c: number, d: number, e: number) => void;
|
|
||||||
readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
|
|
||||||
readonly __wbindgen_export: (a: number, b: number) => number;
|
|
||||||
readonly __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
|
|
||||||
readonly __wbindgen_export3: (a: number, b: number, c: number) => 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>;
|
|
||||||
|
|
@ -1,238 +0,0 @@
|
||||||
/* @ts-self-types="./decodal_wasm.d.ts" */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} source
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
export function evaluate(source) {
|
|
||||||
let deferred2_0;
|
|
||||||
let deferred2_1;
|
|
||||||
try {
|
|
||||||
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
||||||
const ptr0 = passStringToWasm0(source, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
||||||
const len0 = WASM_VECTOR_LEN;
|
|
||||||
wasm.evaluate(retptr, ptr0, len0);
|
|
||||||
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
||||||
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
||||||
deferred2_0 = r0;
|
|
||||||
deferred2_1 = r1;
|
|
||||||
return getStringFromWasm0(r0, r1);
|
|
||||||
} finally {
|
|
||||||
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
||||||
wasm.__wbindgen_export3(deferred2_0, deferred2_1, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} entry
|
|
||||||
* @param {string} files_json
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
export function evaluateProject(entry, files_json) {
|
|
||||||
let deferred3_0;
|
|
||||||
let deferred3_1;
|
|
||||||
try {
|
|
||||||
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
||||||
const ptr0 = passStringToWasm0(entry, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
||||||
const len0 = WASM_VECTOR_LEN;
|
|
||||||
const ptr1 = passStringToWasm0(files_json, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
||||||
const len1 = WASM_VECTOR_LEN;
|
|
||||||
wasm.evaluateProject(retptr, ptr0, len0, ptr1, len1);
|
|
||||||
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
||||||
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
||||||
deferred3_0 = r0;
|
|
||||||
deferred3_1 = r1;
|
|
||||||
return getStringFromWasm0(r0, r1);
|
|
||||||
} finally {
|
|
||||||
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
||||||
wasm.__wbindgen_export3(deferred3_0, deferred3_1, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function __wbg_get_imports() {
|
|
||||||
const import0 = {
|
|
||||||
__proto__: null,
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
__proto__: null,
|
|
||||||
"./decodal_wasm_bg.js": import0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let cachedDataViewMemory0 = null;
|
|
||||||
function getDataViewMemory0() {
|
|
||||||
if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
|
|
||||||
cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
|
|
||||||
}
|
|
||||||
return cachedDataViewMemory0;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
cachedDataViewMemory0 = null;
|
|
||||||
cachedUint8ArrayMemory0 = null;
|
|
||||||
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_wasm_bg.wasm', import.meta.url);
|
|
||||||
}
|
|
||||||
const imports = __wbg_get_imports();
|
|
||||||
|
|
||||||
if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) {
|
|
||||||
module_or_path = fetch(module_or_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { instance, module } = await __wbg_load(await module_or_path, imports);
|
|
||||||
|
|
||||||
return __wbg_finalize_init(instance, module);
|
|
||||||
}
|
|
||||||
|
|
||||||
export { initSync, __wbg_init as default };
|
|
||||||
Binary file not shown.
|
|
@ -1,9 +0,0 @@
|
||||||
/* tslint:disable */
|
|
||||||
/* eslint-disable */
|
|
||||||
export const memory: WebAssembly.Memory;
|
|
||||||
export const evaluate: (a: number, b: number, c: number) => void;
|
|
||||||
export const evaluateProject: (a: number, b: number, c: number, d: number, e: number) => void;
|
|
||||||
export const __wbindgen_add_to_stack_pointer: (a: number) => number;
|
|
||||||
export const __wbindgen_export: (a: number, b: number) => number;
|
|
||||||
export const __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
|
|
||||||
export const __wbindgen_export3: (a: number, b: number, c: number) => void;
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
{
|
|
||||||
"name": "decodal-wasm",
|
|
||||||
"type": "module",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"files": [
|
|
||||||
"decodal_wasm_bg.wasm",
|
|
||||||
"decodal_wasm.js",
|
|
||||||
"decodal_wasm.d.ts"
|
|
||||||
],
|
|
||||||
"main": "decodal_wasm.js",
|
|
||||||
"types": "decodal_wasm.d.ts",
|
|
||||||
"sideEffects": [
|
|
||||||
"./snippets/*"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user