Compare commits
6
Commits
e0a9a8efb7
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5c73c647d | ||
|
|
cda32cc260 | ||
|
|
3645a2cc2d | ||
|
|
e50458595b | ||
|
|
6653607ed3 | ||
|
|
e0970acdcc |
@@ -9,3 +9,4 @@ site/decodal-site/dist
|
||||
|
||||
# Astro
|
||||
site/decodal-site/.astro
|
||||
site/decodal-site/.wrangler
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
# Developing Decodal
|
||||
|
||||
Development notes live here rather than in the public manual. The manual describes released language behavior and public integration APIs; implementation plans and unresolved design work belong in issues or pull requests.
|
||||
|
||||
## Repository map
|
||||
|
||||
- `crates/decodal-core`: parser, evaluator, public Rust API, and tests.
|
||||
- `crates/decodal-derive`: `Decodal` derive macros.
|
||||
- `crates/decodal-language-service`: host-configurable semantic analysis and completion.
|
||||
- `crates/decodal-language-tools`: source-preserving formatter and tooling.
|
||||
- `crates/decodal-lsp`: LSP transport and document synchronization.
|
||||
- `crates/decodal-wasm`: WebAssembly bindings.
|
||||
- `packages/decodal-wasm`: generated npm and JSR runtime package.
|
||||
- `packages/decodal-codemirror`: CodeMirror language support and formatter WebAssembly.
|
||||
- `editors/lezer-decodal` and `editors/tree-sitter-decodal`: downstream editor grammars.
|
||||
- `site/decodal-site`: documentation site and playground.
|
||||
- `doc/manual/source`: English public manual consumed by the site.
|
||||
- `doc/manual/source/jp`: Japanese translation of the public manual.
|
||||
|
||||
## Runtime architecture
|
||||
|
||||
The host owns I/O. `ImportLoader` turns an import specifier into DCDL source or a structured `Value`, and `HostEnvironment` installs the same loader and globals for production and editor tooling.
|
||||
|
||||
Evaluation proceeds through parse, lazy expression evaluation, and materialization. Object fields, module roots, imports, arguments, and defaults are represented by memoized thunks. A thunk re-entered while it is being evaluated produces a cycle diagnostic.
|
||||
|
||||
Runtime values distinguish concrete values from abstract ranges. Composition and `as` preserve that distinction; materialization selects defaults and rejects unresolved ranges or functions. `Data` is the output-only representation.
|
||||
|
||||
Diagnostics retain Decodal source spans. Structured host values use their stable import key and logical value path rather than synthetic source spans.
|
||||
|
||||
## Checks
|
||||
|
||||
Run the release validation commands documented in [`RELEASING.md`](../../RELEASING.md). For a focused change, run the tests for the affected crate or package first, then the full workspace and site checks before release.
|
||||
|
||||
The documentation site can be checked without regenerating committed WebAssembly artifacts:
|
||||
|
||||
```sh
|
||||
npm --prefix site/decodal-site test
|
||||
npm --prefix site/decodal-site run build
|
||||
```
|
||||
|
||||
Use `npm --prefix site/decodal-site run build:wasm` when Rust bindings or formatter behavior changed.
|
||||
|
||||
## Documentation site delivery
|
||||
|
||||
The canonical site origin is `https://decodal.hareworks.net`, configured through Astro's `site` option. A normal site build generates the sitemap, robots policy, social metadata, structured data, 404 page, and source-backed Markdown endpoints. `/llms.txt` indexes the individual English and Japanese Markdown pages; `/llms-full.txt` and `/ja/llms-full.txt` provide complete manuals. These resources are built directly from `doc/manual/source` and do not use Cloudflare's HTML-to-Markdown conversion.
|
||||
|
||||
Deploy the already prepared site with:
|
||||
|
||||
```sh
|
||||
npm --prefix site/decodal-site run deploy
|
||||
```
|
||||
|
||||
Cloudflare Pages `_redirects` handles legacy paths within the site, but not hostname redirects. In the Cloudflare account, configure a Bulk Redirect from `https://decodal-site.pages.dev/` to `https://decodal.hareworks.net/` with status `301`, subpath matching, path-suffix preservation, and query-string preservation enabled. This prevents the Pages project hostname from becoming a second public origin.
|
||||
|
||||
## Syntax changes
|
||||
|
||||
When syntax changes:
|
||||
|
||||
1. Update the public EBNF in `doc/manual/source/language/grammar.md` and the relevant language page.
|
||||
2. Update the Rust lexer/parser and tests.
|
||||
3. Update the Lezer and Tree-sitter grammars and regenerate their committed outputs.
|
||||
4. Update formatter, playground, and language-service tests as applicable.
|
||||
5. Run the validation commands in `RELEASING.md`.
|
||||
|
||||
The Rust parser is authoritative for evaluation. Lezer and Tree-sitter are downstream editor integrations and are not runtime or formatter dependencies.
|
||||
|
||||
## Releases
|
||||
|
||||
Package versions, validation commands, publish order, and registry commands are maintained only in [`RELEASING.md`](../../RELEASING.md).
|
||||
@@ -1,140 +0,0 @@
|
||||
# Components
|
||||
|
||||
Decodal is split into a small runtime core and separate syntax tooling components.
|
||||
The public surface is intentionally organized by use case: execute Decodal with Rust or WebAssembly, edit Decodal on the Web with Lezer and CodeMirror, and integrate Decodal into general-purpose editors with Tree-sitter.
|
||||
|
||||
## Runtime components
|
||||
|
||||
### Rust crate
|
||||
|
||||
The `decodal` crate is the primary Rust runtime and embedding API.
|
||||
It owns parsing, evaluation, materialization, diagnostics, host-provided values, and schema/decode traits.
|
||||
Rust applications should use this crate when they want to load Decodal source, evaluate it, or embed Decodal into a host program.
|
||||
|
||||
Important paths:
|
||||
|
||||
```text
|
||||
crates/decodal-core/
|
||||
crates/decodal-derive/
|
||||
```
|
||||
|
||||
`decodal-derive` provides optional derive macros for Rust struct integration.
|
||||
It is a companion to the runtime crate rather than an editor or syntax-highlighting component.
|
||||
|
||||
### WebAssembly package
|
||||
|
||||
`decodal-wasm` exposes the runtime to browsers.
|
||||
The documentation site playground uses it to evaluate Decodal entirely in the browser.
|
||||
|
||||
Important paths:
|
||||
|
||||
```text
|
||||
crates/decodal-wasm/
|
||||
packages/decodal-wasm/
|
||||
```
|
||||
|
||||
The npm package is `decodal-wasm`.
|
||||
The JSR package is `@hare/decodal-wasm`.
|
||||
|
||||
The generated files in `packages/decodal-wasm/` are committed so the site can build without requiring every consumer to run `wasm-pack` first.
|
||||
The WebAssembly package is for execution, not syntax highlighting.
|
||||
|
||||
## Language tools
|
||||
|
||||
Semantic editor integration lives in the host-configurable language service crate.
|
||||
It depends only on the runtime and accepts the same `HostEnvironment` implementation used by a production application.
|
||||
The LSP crate adapts that service to the Language Server Protocol over stdin/stdout.
|
||||
It provides full document synchronization, semantic diagnostics, and whole-document formatting.
|
||||
A host-specific LSP binary injects its loader and global schema configuration without reimplementing evaluation rules.
|
||||
|
||||
Important paths:
|
||||
|
||||
```text
|
||||
crates/decodal-language-service/
|
||||
crates/decodal-lsp/
|
||||
```
|
||||
|
||||
The default `decodal-lsp` binary reads Decodal imports from the filesystem.
|
||||
Embedded hosts can call its library entry point with a custom `LspEnvironment` to reuse structured imports and to make unsaved external documents, such as Markdown, visible to the loader.
|
||||
|
||||
Source formatting lives in a separate Rust language tools crate.
|
||||
It uses the canonical Decodal AST together with the runtime lexer's lossless syntax tokens, so native LSP and WebAssembly callers execute the same formatter implementation.
|
||||
|
||||
This component is responsible for operations that must preserve source text details such as comments and whitespace.
|
||||
It is used by the CodeMirror package's bundled formatter WebAssembly and can also be used by an LSP adapter for formatting.
|
||||
It does not depend on Tree-sitter or Lezer.
|
||||
|
||||
Important paths:
|
||||
|
||||
```text
|
||||
crates/decodal-language-tools/
|
||||
```
|
||||
|
||||
The current language tools crate exposes the formatter.
|
||||
|
||||
## Web editor components
|
||||
|
||||
The Web playground editor uses CodeMirror 6 with a generated Lezer parser.
|
||||
Lezer provides syntax highlighting, folding, indentation, and editor syntax tree behavior. The browser formatter command calls the canonical Rust formatter compiled to WebAssembly.
|
||||
|
||||
Important paths:
|
||||
|
||||
```text
|
||||
editors/lezer-decodal/decodal.grammar
|
||||
packages/decodal-codemirror/src/decodal.js
|
||||
packages/decodal-codemirror/src/decodal-parser.js
|
||||
packages/decodal-codemirror/src/decodal-parser.terms.js
|
||||
packages/decodal-codemirror/src/format.js
|
||||
packages/decodal-codemirror/wasm/
|
||||
```
|
||||
|
||||
The npm package is `decodal-codemirror`.
|
||||
The JSR package is `@hare/decodal-codemirror`.
|
||||
|
||||
The Lezer grammar is derived from the canonical grammar documentation, but it is not a literal copy of the EBNF.
|
||||
Precedence and token conflict handling are represented in the Lezer grammar in the form CodeMirror needs.
|
||||
|
||||
## General editor components
|
||||
|
||||
Tree-sitter is the portable editor-integration grammar.
|
||||
Editors such as Zed, Neovim, Helix, and Emacs should consume this component when they need Decodal parsing or highlighting outside the Web playground.
|
||||
|
||||
Important paths:
|
||||
|
||||
```text
|
||||
editors/tree-sitter-decodal/grammar.js
|
||||
editors/tree-sitter-decodal/queries/highlights.scm
|
||||
editors/tree-sitter-decodal/queries/locals.scm
|
||||
editors/tree-sitter-decodal/src/
|
||||
```
|
||||
|
||||
The generated parser sources under `editors/tree-sitter-decodal/src/` are committed so editor integrations can consume the grammar without regenerating it first.
|
||||
|
||||
## Canonical grammar
|
||||
|
||||
The human-readable grammar lives in:
|
||||
|
||||
```text
|
||||
doc/manual/souce/language/grammar.md
|
||||
```
|
||||
|
||||
This EBNF is the language-level reference.
|
||||
The Rust parser, Lezer grammar, and Tree-sitter grammar should be kept aligned with it, but each implementation may encode precedence and recovery behavior in the form required by its parser generator or runtime.
|
||||
|
||||
## Syntax token API
|
||||
|
||||
The `decodal` crate exposes `tokenize_source`, `tokenize_source_with_source_id`, `SyntaxToken`, and `SyntaxTokenKind` for source-preserving tooling.
|
||||
Comments have explicit tokens, while whitespace is represented by gaps between token spans and can be recovered from the original source.
|
||||
The evaluator parser and formatter therefore share one lexical definition without making Tree-sitter an upstream dependency.
|
||||
|
||||
Consumers should otherwise use the component matching their environment:
|
||||
|
||||
- Rust execution and embedding: `decodal`
|
||||
- Browser execution: `decodal-wasm`
|
||||
- Web formatting and editor syntax: canonical formatter / CodeMirror / Lezer
|
||||
- Semantic editor analysis: `decodal-language-service`
|
||||
- Language Server Protocol integration: `decodal-lsp`
|
||||
- Rust formatting: `decodal-language-tools`
|
||||
- General editor syntax: Tree-sitter
|
||||
|
||||
Tree-sitter and Lezer remain downstream editor grammars and are not dependencies of the runtime formatter.
|
||||
@@ -1,143 +0,0 @@
|
||||
# Composition and Materialization
|
||||
|
||||
`&`、`//`、`as`、`default`、materialize は、runtime value の variant に基づいて処理する。
|
||||
|
||||
## `&`
|
||||
|
||||
`&` は制約を保った合成である。
|
||||
|
||||
```text
|
||||
compose_and(a: RuntimeValue, b: RuntimeValue) -> RuntimeValue | Diagnostic
|
||||
```
|
||||
|
||||
基本規則:
|
||||
|
||||
```text
|
||||
Abstract(a) & Abstract(b)
|
||||
-> Abstract {
|
||||
constraints: a.constraints + b.constraints,
|
||||
default: merge_default(a.default, b.default)
|
||||
}
|
||||
|
||||
Abstract(a) & Concrete(v)
|
||||
-> if satisfies(v, a.constraints) then Concrete(v)
|
||||
else constraint diagnostic
|
||||
|
||||
Concrete(v) & Abstract(a)
|
||||
-> if satisfies(v, a.constraints) then Concrete(v)
|
||||
else constraint diagnostic
|
||||
|
||||
Concrete(Object(a)) & Concrete(Object(b))
|
||||
-> Concrete(Object(fieldwise_and(a, b)))
|
||||
|
||||
Concrete(a) & Concrete(b)
|
||||
-> if a == b then Concrete(a)
|
||||
else conflict diagnostic
|
||||
```
|
||||
|
||||
`Abstract & Concrete` が成功した場合、default は消える。
|
||||
明示値があるなら fallback は不要だからである。
|
||||
|
||||
## object の合成
|
||||
|
||||
object は concrete structure だが、field の値は thunk 経由で concrete / abstract のどちらにもなりうる。
|
||||
object 同士の `&` は field ごとに再帰合成する。
|
||||
|
||||
```dcdl
|
||||
MyConfig = {
|
||||
host = String;
|
||||
port = Int default 8080;
|
||||
};
|
||||
|
||||
Config = MyConfig & {
|
||||
host = "localhost";
|
||||
};
|
||||
```
|
||||
|
||||
`host` は `Abstract(String) & Concrete("localhost")` として検証され、成功すれば `Concrete("localhost")` になる。
|
||||
`port` は右辺に明示値がないため、`Abstract(Int, default 8080)` のまま残る。
|
||||
|
||||
## default の合成
|
||||
|
||||
`&` で片方だけが default を持つ場合、その default を保持する。
|
||||
同じ thunk 由来の default 同士は同一 default として保持する。
|
||||
異なる default 同士は conflict とする。
|
||||
|
||||
```text
|
||||
merge_default(None, None) -> None
|
||||
merge_default(Some(a), None) -> Some(a)
|
||||
merge_default(None, Some(b)) -> Some(b)
|
||||
merge_default(Some(a), Some(b)) -> if same(a, b) then Some(a) else conflict
|
||||
```
|
||||
|
||||
`//` では右辺 default が左辺 default を置き換える。
|
||||
|
||||
## `//`
|
||||
|
||||
`//` は右辺優先の deep patch である。
|
||||
|
||||
```text
|
||||
patch(a: RuntimeValue, b: RuntimeValue) -> RuntimeValue
|
||||
```
|
||||
|
||||
基本規則:
|
||||
|
||||
- object / object は field ごとに再帰 patch する。
|
||||
- object / object 以外は右辺で置き換える。
|
||||
- 左辺にしかない field は保持する。
|
||||
- 右辺にしかない field は追加する。
|
||||
- 配列、scalar、function は右辺置換とする。
|
||||
|
||||
`//` は制約を保持するための演算子ではない。
|
||||
対称な制約合成には `&` を使い、左辺が右辺より狭いことを確認しながら合成する場合は `as` を使う。
|
||||
|
||||
## `as`
|
||||
|
||||
`as` は narrower と wider の向きを固定した範囲包含確認と合成である。
|
||||
|
||||
```text
|
||||
apply_as(narrower: RuntimeValue, wider: RuntimeValue)
|
||||
-> RuntimeValue | Diagnostic
|
||||
```
|
||||
|
||||
abstract / abstract では、左辺 constraints が右辺 constraints を包含することを確認し、左辺を結果にする。
|
||||
concrete / abstract では、concrete value が右辺 constraints を満たすことを確認する。
|
||||
|
||||
object / object では右辺を field domain とする。左辺にしかない field は診断し、両側にある field は再帰的に `apply_as` し、右辺にしかない field は thunk を force せずそのまま結果へ残す。
|
||||
この扱いは default の有無に依存しない。default は包含判定の根拠でも `as` の補完値でもなく、後の materialize だけが選択する。
|
||||
|
||||
`ArrayItems(wider)` は concrete array の全要素を、`MapValues(wider)` は concrete object の全 field value を同じ関数で絞り込む。
|
||||
|
||||
## materialize
|
||||
|
||||
materialize は runtime value を出力可能な `Data` に変換する。
|
||||
|
||||
```text
|
||||
materialize(RuntimeValue) -> Data | Diagnostic
|
||||
```
|
||||
|
||||
処理規則:
|
||||
|
||||
```text
|
||||
Concrete(String/Int/Float/Bool) -> Data
|
||||
Concrete(Array(items)) -> each item を force して materialize
|
||||
Concrete(Object(fields)) -> each field を force して materialize
|
||||
Concrete(Function) -> materialize 不能
|
||||
|
||||
Abstract { constraints, default: Some(d) }
|
||||
-> force(d)
|
||||
-> result が constraints を満たすか検証
|
||||
-> materialize(result)
|
||||
|
||||
Abstract { constraints, default: None }
|
||||
-> 未解決 abstract value として diagnostic
|
||||
```
|
||||
|
||||
materialize は default を採用する唯一の段階である。
|
||||
通常評価中に明示値が得られた場合、default は採用されない。
|
||||
|
||||
## Diagnostic context
|
||||
|
||||
Composition and materialization keep source spans for object fields, constraints, defaults, and thunks where possible.
|
||||
When a conflict occurs, the diagnostic should identify the operation span and related participant spans, such as the left field, right field, constraint, or default value.
|
||||
For object materialization errors, diagnostics also include the field path being processed when known.
|
||||
@@ -1,79 +0,0 @@
|
||||
# Diagnostics and Fallback
|
||||
|
||||
エラーは runtime value ではなく diagnostic として扱う。
|
||||
処理系はエラー内容に基づく汎用的な実行時分岐を提供しない。
|
||||
|
||||
## Diagnostic
|
||||
|
||||
```text
|
||||
Diagnostic {
|
||||
kind: DiagnosticKind
|
||||
span: Span
|
||||
message: String
|
||||
labels: Vec<DiagnosticLabel>
|
||||
notes: Vec<String>
|
||||
}
|
||||
|
||||
DiagnosticLabel {
|
||||
span: Span
|
||||
message: String
|
||||
}
|
||||
```
|
||||
|
||||
`span` は primary location を示す。
|
||||
表示時には `Span.source` を source id のまま出すのではなく、可能な限り file path や virtual file name に解決する。
|
||||
`labels` は同じ error に関係する追加 location を示す。
|
||||
`notes` は source location を持たない semantic context を示す。
|
||||
合成や materialize の失敗では、衝突した constraint、value、default、または処理中 field path を label に含める。
|
||||
|
||||
代表的な diagnostic kind:
|
||||
|
||||
- syntax error
|
||||
- unresolved identifier
|
||||
- type mismatch
|
||||
- constraint violation
|
||||
- composition conflict
|
||||
- default conflict
|
||||
- cycle dependency
|
||||
- import failure
|
||||
- match failure
|
||||
- materialization failure
|
||||
|
||||
Structured imports use semantic provenance rather than synthetic source spans.
|
||||
Constraint failures report the imported value's stable key and logical field or array path alongside the source span of the Decodal constraint that rejected it.
|
||||
|
||||
## エラーは値ではない
|
||||
|
||||
評価失敗は `RuntimeValue` ではなく `Diagnostic` を返す。
|
||||
そのため、通常の式はエラー内容に基づいて分岐できない。
|
||||
|
||||
```text
|
||||
Result<RuntimeValue, Diagnostic>
|
||||
```
|
||||
|
||||
これにより、制約違反、未定義識別子、循環依存、import 失敗などが通常値として流れることを避ける。
|
||||
|
||||
## 合成と materialize の diagnostic
|
||||
|
||||
合成や materialize の失敗は、以下を示す。
|
||||
|
||||
1. どの段階で失敗したか: composition、patch、materialization
|
||||
2. どの field path を処理中だったか
|
||||
3. どの constraint、value、default が衝突したか
|
||||
4. なぜ合成または materialize できないか
|
||||
|
||||
例えば default が constraint を満たさない場合は、constraint の位置と default value の位置の両方を label として持つ。
|
||||
object field の合成で concrete value が衝突する場合は、左辺 field と右辺 field の位置を label として持つ。
|
||||
|
||||
## `try / catch` は core に入れない
|
||||
|
||||
汎用 `try / catch` は core に入れない。
|
||||
エラーを制御フローとして扱うと、どの失敗を捕捉できるか、捕捉後の thunk state をどう扱うか、制約違反を握りつぶしてよいか、といった仕様が重くなる。
|
||||
|
||||
fallback は有限で明示的な仕組みに限定する。
|
||||
|
||||
- `default`: 未指定値の fallback。
|
||||
- `match`: 有限 pattern に基づく分岐。
|
||||
|
||||
`Unknown` は明示的に書く最上位のabstract rangeであり、評価エラーを包む値ではない。
|
||||
field 不在や未解決 identifier は `Unknown` へ変換せず、diagnostic として報告する。
|
||||
@@ -1,258 +0,0 @@
|
||||
# Embedding API
|
||||
|
||||
Decodal core can be embedded without giving the core crate access to a filesystem.
|
||||
The host supplies imports through `ImportLoader` and may also provide global bindings through the host prelude API.
|
||||
|
||||
## Host prelude
|
||||
|
||||
`Engine` owns a prelude environment.
|
||||
Bindings in this environment are visible from every module loaded by the engine.
|
||||
|
||||
```text
|
||||
prelude env
|
||||
↓
|
||||
module root env
|
||||
↓
|
||||
let / function env
|
||||
```
|
||||
|
||||
Module top-level bindings shadow prelude bindings.
|
||||
Primitive type names such as `String`, `Int`, `Float`, and `Bool`, plus the top range `Unknown`, are handled before environment lookup, so they are reserved and cannot be shadowed by host bindings.
|
||||
|
||||
## Global bindings
|
||||
|
||||
The host can bind values before adding or evaluating user sources.
|
||||
|
||||
```rust
|
||||
use decodal::{EmptyLoader, Engine, Value};
|
||||
|
||||
let mut engine = Engine::new(EmptyLoader);
|
||||
|
||||
engine.bind_global(
|
||||
"Service",
|
||||
Value::object([
|
||||
("name", Value::string_type()),
|
||||
("port", Value::int_type().gt(443).default_int(8443)?),
|
||||
("enabled", Value::bool_type().default_bool(true)?),
|
||||
]),
|
||||
)?;
|
||||
```
|
||||
|
||||
A user source can then refer to `Service` without importing it.
|
||||
|
||||
```dcdl
|
||||
{
|
||||
name = "api";
|
||||
port = 9443;
|
||||
} as Service
|
||||
```
|
||||
|
||||
## Value
|
||||
|
||||
`Value` is the public builder-facing value representation for embedding.
|
||||
It keeps host code from constructing internal `ThunkId` or `ObjectValue` values directly.
|
||||
|
||||
```text
|
||||
Value =
|
||||
String
|
||||
Int
|
||||
Float
|
||||
Bool
|
||||
Array(Vec<Value>)
|
||||
ArrayRange { item, constraints, default }
|
||||
MapRange { value, constraints, default }
|
||||
Object { fields, rest: Option<Value> }
|
||||
Range { constraints, default }
|
||||
```
|
||||
|
||||
When a value is bound by the host, the engine internalizes it into `RuntimeValue` and allocates value thunks for object fields, array items, and defaults.
|
||||
|
||||
`Value::array_of(item)` builds an array constraint with a required element schema.
|
||||
There is no `Value` constructor for an unconstrained abstract array.
|
||||
|
||||
`Value::map_of(value)` builds a map constraint whose arbitrary object field values must satisfy `value`.
|
||||
`BTreeMap<String, T>` and, with `std`, `HashMap<String, T>` implement `DecodalSchema`, `DecodalDecode`, and `IntoValue` using this representation.
|
||||
|
||||
`Value::unknown()` builds the top abstract range. `Value::object_with_rest(fields, rest)` builds an object with named fields and a range for all remaining fields.
|
||||
|
||||
```rust
|
||||
Value::object_with_rest(
|
||||
[("enabled", Value::bool_type().default_bool(true)?)],
|
||||
Value::unknown(),
|
||||
)
|
||||
```
|
||||
|
||||
## Objects containing ranges
|
||||
|
||||
A host-provided schema object is represented as a concrete object structure whose fields may contain ranges.
|
||||
|
||||
```rust
|
||||
Value::object([
|
||||
("name", Value::string_type()),
|
||||
("port", Value::int_type().gt(443).default_int(8443)?),
|
||||
])
|
||||
```
|
||||
|
||||
Conceptually this becomes:
|
||||
|
||||
```text
|
||||
Concrete(Object {
|
||||
name -> Thunk(Abstract { constraints: [String], default: none })
|
||||
port -> Thunk(Abstract { constraints: [Int, > 443], default: 8443 })
|
||||
})
|
||||
```
|
||||
|
||||
This matches the runtime model used for Decodal source-defined schema objects.
|
||||
|
||||
## Typed Rust integration
|
||||
|
||||
Hosts can enable the `derive` feature on `decodal` to keep a Rust struct, the Decodal schema, and the decoded result in sync.
|
||||
The derive implements two traits from the `decodal` crate:
|
||||
|
||||
- `DecodalSchema`: builds a `Value` schema that can be passed to `Engine::bind_global`.
|
||||
- `DecodalDecode`: decodes materialized `Data` back into the Rust type.
|
||||
|
||||
An explicitly marked map field can receive the open portion of an object.
|
||||
|
||||
```rust
|
||||
use std::collections::BTreeMap;
|
||||
use decodal::{Data, Decodal};
|
||||
|
||||
#[derive(Decodal)]
|
||||
struct OpenConfig {
|
||||
enabled: bool,
|
||||
|
||||
#[decodal(rest)]
|
||||
extra: BTreeMap<String, Data>,
|
||||
}
|
||||
```
|
||||
|
||||
The schema generated for `extra` is `...Unknown`. A typed receiver such as `BTreeMap<String, String>` generates `...String` instead. During decode, named top-level fields are excluded and all remaining fields are collected into the map.
|
||||
|
||||
Only one `#[decodal(rest)]` field is allowed. It must implement `DecodalRest`; the core provides implementations for `BTreeMap<String, T>` and, with `std`, `HashMap<String, T>`. `rename`, `default`, and field constraints cannot be combined with `rest`. Without a rest receiver, a derived struct remains a closed object range.
|
||||
|
||||
```rust
|
||||
use decodal::{Decodal, DecodalDecode, DecodalSchema, EmptyLoader, Engine};
|
||||
|
||||
#[derive(Decodal)]
|
||||
struct Service {
|
||||
name: String,
|
||||
|
||||
#[decodal(gt = 443, default = 8443)]
|
||||
port: i64,
|
||||
|
||||
#[decodal(rename = "feature.enable", default = true)]
|
||||
feature_enabled: bool,
|
||||
}
|
||||
|
||||
let mut engine = Engine::new(EmptyLoader);
|
||||
engine.bind_global("Service", Service::decodal_schema())?;
|
||||
|
||||
let value = engine.eval_module(module)?;
|
||||
let data = engine.materialize(&value)?;
|
||||
let service = Service::decodal_decode(&data)?;
|
||||
```
|
||||
|
||||
Supported field attributes are intentionally small:
|
||||
|
||||
- `rename = "path.to.field"`
|
||||
- `default`
|
||||
- `default = value`
|
||||
- numeric constraints: `gt`, `gte`, `lt`, `lte`
|
||||
|
||||
The derive does not add host callbacks or reflection.
|
||||
It only generates schema construction and typed decoding code.
|
||||
|
||||
## ImportLoader and prelude together
|
||||
|
||||
`ImportLoader` and host prelude bindings are independent mechanisms.
|
||||
|
||||
- Use `ImportLoader` when user sources should explicitly import host-provided sources or values.
|
||||
- Use prelude bindings when host-provided schemas or constants should be globally available.
|
||||
|
||||
Both mechanisms share the same runtime evaluator, thunk model, and materialization rules.
|
||||
|
||||
## Shared host environment
|
||||
|
||||
An embedded application can implement `HostEnvironment` to keep loader creation and global binding setup in one place.
|
||||
Both production evaluation and semantic editor tooling create their engines from this environment, preventing the editor from drifting onto a separate validation path.
|
||||
|
||||
```rust
|
||||
use decodal::{Engine, HostEnvironment};
|
||||
|
||||
struct AppEnvironment;
|
||||
|
||||
impl HostEnvironment for AppEnvironment {
|
||||
type Loader = ContentLoader;
|
||||
|
||||
fn create_loader(&self) -> Self::Loader {
|
||||
ContentLoader::new()
|
||||
}
|
||||
|
||||
fn configure_engine(
|
||||
&self,
|
||||
engine: &mut Engine<Self::Loader>,
|
||||
) -> decodal::Result<()> {
|
||||
engine.bind_global("Site", site_schema())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
let environment = AppEnvironment;
|
||||
let mut engine = environment.create_engine()?;
|
||||
```
|
||||
|
||||
The semantic service accepts the same environment by value:
|
||||
|
||||
```rust
|
||||
use decodal_language_service::LanguageService;
|
||||
|
||||
let service = LanguageService::new(&environment);
|
||||
let analysis = service.analyze("site.dcdl", "site.dcdl", source);
|
||||
```
|
||||
|
||||
Each analysis uses a fresh engine and runs the normal parse, evaluate, and materialize pipeline.
|
||||
An environment may create loaders backed by shared filesystem, database, or editor-overlay state when repeated analysis needs a current workspace snapshot.
|
||||
|
||||
The LSP adapter constructs that environment after receiving the client's initialization parameters:
|
||||
|
||||
```rust
|
||||
use decodal_lsp::{LspEnvironment, run_stdio};
|
||||
|
||||
impl LspEnvironment for AppEnvironment {}
|
||||
|
||||
run_stdio(|initialize| {
|
||||
let _ = initialize;
|
||||
Ok(AppEnvironment)
|
||||
})?;
|
||||
```
|
||||
|
||||
`LspEnvironment` adds document lifecycle hooks on top of `HostEnvironment`.
|
||||
A `run_stdio` environment factory always receives the client's `InitializeParams`; the host decides whether to use its workspace folders, initialization options, and capabilities or ignore them.
|
||||
A host that keeps unsaved buffers in shared state can update them from `open_document`, `change_document`, and `close_document`; every subsequent diagnostic pass creates the normal import loader from that updated environment.
|
||||
Synchronized non-Decodal documents are passed through these hooks but are not evaluated as Decodal roots, so a loader can parse an unsaved Markdown file into `Value` and immediately revalidate the open Decodal documents that import it.
|
||||
|
||||
## Structured imports
|
||||
|
||||
`ImportLoader::load` returns either `LoadedImport::Source` or `LoadedImport::Value`.
|
||||
The value variant carries a `Value`, allowing a host to parse non-Decodal resources such as Markdown into an application-specific structure.
|
||||
|
||||
```text
|
||||
import "./post.md"
|
||||
-> host parses content
|
||||
-> LoadedImport::Value {
|
||||
key: "content/post.md",
|
||||
value: { frontmatter: {...}, body: "..." }
|
||||
}
|
||||
-> Engine internalizes Value
|
||||
-> normal composition and materialization
|
||||
```
|
||||
|
||||
The core does not select content types or bundle Markdown/frontmatter parsers.
|
||||
The loader owns path resolution, media or extension dispatch, parsing rules, and parse diagnostics.
|
||||
The stable loader key is also used to cache structured imports.
|
||||
|
||||
When a structured value fails a Decodal constraint, the diagnostic keeps the Decodal constraint span and identifies the imported value by its stable key and logical value path, such as `content/post.md` and `frontmatter.draft`.
|
||||
`Value` does not need source spans: syntax diagnostics for the external format remain the loader's responsibility, while cross-value validation reports semantic provenance.
|
||||
|
||||
`load` is the single import hook: loaders dispatch by extension, media type, or another host-defined rule and return the appropriate variant directly.
|
||||
@@ -1,163 +0,0 @@
|
||||
# Execution Pipeline
|
||||
|
||||
処理系は、source を AST に変換し、必要な値だけを AST interpreter で評価する。
|
||||
|
||||
```text
|
||||
source
|
||||
↓
|
||||
lexer / parser
|
||||
↓
|
||||
desugar
|
||||
↓
|
||||
register root module
|
||||
↓
|
||||
demand-driven evaluation
|
||||
├─ force thunk
|
||||
├─ load imported module on demand
|
||||
├─ evaluate expression
|
||||
├─ compose `&`
|
||||
├─ patch `//`
|
||||
└─ validate `as`
|
||||
↓
|
||||
materialize
|
||||
↓
|
||||
data / diagnostics
|
||||
```
|
||||
|
||||
Production applications and semantic language services can construct this pipeline through the same host-defined `HostEnvironment`.
|
||||
The environment creates the `ImportLoader` and installs host globals before the root module is registered.
|
||||
|
||||
## lexer / parser
|
||||
|
||||
lexer / parser は source を AST に変換する。
|
||||
構文エラーはこの段階で diagnostic として報告する。
|
||||
|
||||
AST は arena に格納し、式や pattern は `ExprId`、`PatternId` のような ID で参照する。
|
||||
|
||||
## desugar
|
||||
|
||||
desugar は、意味論を単純にするための表層構文変換を行う。
|
||||
|
||||
例として、dot-path field は nested object に変換できる。
|
||||
|
||||
```dcdl
|
||||
{
|
||||
feature_hoge.enable = false;
|
||||
}
|
||||
```
|
||||
|
||||
```dcdl
|
||||
{
|
||||
feature_hoge = {
|
||||
enable = false;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
この変換により、評価器は object field の再帰構造だけを扱えばよい。
|
||||
|
||||
## module registry
|
||||
|
||||
module registry は、読み込んだ module を loader が返す安定 key で管理する。
|
||||
CLI では canonical path を key とする。
|
||||
組み込み利用では、resource name や static source table の key を使える。
|
||||
|
||||
```text
|
||||
ModuleRegistry:
|
||||
ModuleKey -> ModuleId
|
||||
```
|
||||
|
||||
処理系は、まず root module を parse / desugar して registry に登録する。
|
||||
import 先 module は、この段階で全て読み込む必要はない。
|
||||
|
||||
import expression が評価されたとき、処理系は `ImportLoader::load` に現在の module key と import specifier を渡す。
|
||||
loader は module key、表示名、および DCDL source text または構造化済み `Value` を返す。
|
||||
DCDL source の場合、module registry は key が未登録なら対象 module を parse / desugar して登録する。
|
||||
登録された source module は module root thunk を持つ。
|
||||
構造化値の場合、host が返した `Value` を runtime value に internalize した root thunk を key でキャッシュする。
|
||||
同じ key が複数回 import された場合は、同じ source module または structured root thunk を使う。
|
||||
|
||||
つまり source import は module を即時評価しない。
|
||||
module を読み込み、module root を thunk として登録するだけにする。
|
||||
structured import も root value を thunk として保持し、object field や array item の既存 thunk model を利用する。
|
||||
|
||||
AST の `ExprId` は module-local である。
|
||||
そのため runtime が保持する式参照は `ExprRef { module, expr }` として module-qualified にする。
|
||||
|
||||
## demand-driven evaluation
|
||||
|
||||
評価器は、必要になった thunk だけを force する。
|
||||
未参照の field、let binding、import 先の field は評価しない。
|
||||
|
||||
この方式により、module 間に循環 import があっても、実際に force された thunk の依存が循環しない限り評価できる。
|
||||
|
||||
## composition
|
||||
|
||||
composition は、module 全体に後からかける global pass ではない。
|
||||
`&` や `//` の式を評価するときに、demand-driven evaluation の中で呼ばれる演算である。
|
||||
|
||||
```text
|
||||
eval(A & B):
|
||||
a = eval(A)
|
||||
b = eval(B)
|
||||
compose_and(a, b)
|
||||
|
||||
eval(A // B):
|
||||
a = eval(A)
|
||||
b = eval(B)
|
||||
patch(a, b)
|
||||
|
||||
eval(A as S):
|
||||
narrower = eval(A)
|
||||
wider = eval(S)
|
||||
apply_as(narrower, wider)
|
||||
```
|
||||
|
||||
`&` は制約を保った対称な合成を行い、`//` は右辺優先の deep patch を行う。
|
||||
`as` は左辺が右辺より狭いことを再帰的に確認する。左辺で絞られた部分は左辺を使い、右辺だけの部分は abstract のまま保持する。
|
||||
default は `as` では選択せず、materialize まで遅延する。
|
||||
詳細は [Composition and Materialization](./composition-and-materialization.md) に置く。
|
||||
|
||||
## resolver / binder
|
||||
|
||||
resolver / binder は初期実装では必須ではない。
|
||||
評価時に environment lookup を行えば、識別子参照は実装できる。
|
||||
|
||||
ただし、将来的には optional phase として追加できる余地を残す。
|
||||
|
||||
```text
|
||||
source
|
||||
↓
|
||||
lexer / parser
|
||||
↓
|
||||
desugar
|
||||
↓
|
||||
resolver / binder
|
||||
↓
|
||||
register root module
|
||||
↓
|
||||
demand-driven evaluation
|
||||
```
|
||||
|
||||
resolver / binder を追加すると、以下を早期に診断しやすくなる。
|
||||
|
||||
- 未定義識別子
|
||||
- shadowing の扱い
|
||||
- reserved word の扱い
|
||||
- symbol interning
|
||||
- import path の一部静的解決
|
||||
- span 付き diagnostic の精度向上
|
||||
|
||||
ただし、Decodal の制約検証は独立した type checking pass ではなく、`&` の合成時、`as` の適用時、materialize 時に行う。
|
||||
|
||||
## materialize
|
||||
|
||||
通常の評価結果は runtime value であり、抽象値や default を含みうる。
|
||||
外部へ出力するときだけ materialize を行い、出力可能な data に変換する。
|
||||
|
||||
materialize は以下を行う。
|
||||
|
||||
- 必要な thunk を force する。
|
||||
- abstract value の default を必要に応じて force する。
|
||||
- concrete value が constraint を満たすか検証する。
|
||||
- 未解決の abstract value、function value などを diagnostic にする。
|
||||
@@ -1,68 +0,0 @@
|
||||
# Features
|
||||
|
||||
Decodal は embedded use と小さい runtime を優先する。
|
||||
言語機能を追加するときは、値の合成・検証・materialization に直接必要なものを core に残し、重い依存や高度な推論は optional feature または外部 tooling に分ける。
|
||||
|
||||
## Core feature boundary
|
||||
|
||||
Core に入れる機能は、基本的に deterministic な value transformation に限る。
|
||||
|
||||
- arithmetic / logical / comparison operators
|
||||
- array concat
|
||||
- object / constraint composition
|
||||
- asymmetric range refinement, `Unknown`, map constraints, and object rest constraints
|
||||
- default materialization
|
||||
- pure function evaluation
|
||||
- host supplied import evaluation
|
||||
|
||||
Core に入れないものは以下である。
|
||||
|
||||
- filesystem / network / environment access
|
||||
- time / random
|
||||
- mutation
|
||||
- reflection or existence probing
|
||||
- arbitrary host function calls
|
||||
- symbolic constraint solving beyond simple normalization
|
||||
|
||||
未解決 identifier や missing field は明示的な `Unknown` range とは異なり、diagnostic になる。
|
||||
この方針により、存在チェックや optional chaining のような dynamic object inspection は core language の対象外とする。
|
||||
|
||||
## Constraint reasoning
|
||||
|
||||
Constraint normalization は軽量な範囲に留める。
|
||||
primitive type conflict や明らかな numeric bound conflict は合成時に検出してよい。
|
||||
一方で、symbolic arithmetic、boolean algebra、regex intersection、array length dependent typing のような重い推論は行わない。
|
||||
|
||||
評価済みの concrete value に対する検証は runtime / materialization で行う。
|
||||
静的に完全な型検査フェーズを増やすのではなく、parse、evaluate、compose、materialize の各段階で自然に分かる error を diagnostic として返す。
|
||||
|
||||
## Core defaults
|
||||
|
||||
`decodal` defaults to `std` only.
|
||||
|
||||
```toml
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = []
|
||||
regex = ["std", "dep:regex"]
|
||||
```
|
||||
|
||||
Building `decodal` with `--no-default-features` keeps the core in `no_std + alloc` mode and avoids optional dependencies.
|
||||
|
||||
## Regex
|
||||
|
||||
Regex constraints are implemented behind the `regex` feature.
|
||||
When the feature is disabled, regex constraints parse and compose, but validating a concrete value against them returns an unsupported feature diagnostic.
|
||||
|
||||
```sh
|
||||
cargo run -q -p decodal-cli --features regex -- examples/regex/main.dcdl
|
||||
```
|
||||
|
||||
Regex constraints are accumulated during `&` composition.
|
||||
The implementation does not try to prove whether the intersection of two regex constraints is empty.
|
||||
Concrete strings must match every regex constraint attached to the abstract value.
|
||||
|
||||
## CLI features
|
||||
|
||||
`decodal-cli` exposes a matching `regex` feature that enables `decodal/regex`.
|
||||
The feature is not enabled by default so the default CLI binary remains small.
|
||||
@@ -1,19 +0,0 @@
|
||||
# 処理系設計
|
||||
|
||||
この章では、言語仕様を実装するための処理系モデルを定義する。
|
||||
言語仕様そのものは [Language Specification](../language/index.md) に置き、この章では AST interpreter、遅延評価、thunk、runtime value、materialize の実装方針を扱う。
|
||||
|
||||
## 方針
|
||||
|
||||
初期処理系は AST interpreter として実装する。
|
||||
bytecode VM や JIT ではなく、AST を demand-driven に評価することで、遅延評価、循環参照、`default`、`&`、`//`、`as` の意味論を小さく実装する。
|
||||
|
||||
## 構成
|
||||
|
||||
1. [Execution Pipeline](./execution-pipeline.md)
|
||||
2. [Runtime Model](./runtime-model.md)
|
||||
3. [Thunk and Lazy Evaluation](./thunk-and-lazy-evaluation.md)
|
||||
4. [Composition and Materialization](./composition-and-materialization.md)
|
||||
5. [Diagnostics and Fallback](./diagnostics-and-fallback.md)
|
||||
6. [Embedding API](./embedding-api.md)
|
||||
7. [Features](./features.md)
|
||||
@@ -1,179 +0,0 @@
|
||||
# Runtime Model
|
||||
|
||||
処理系の評価結果は、具体値と抽象値を区別した runtime value として扱う。
|
||||
`value`、`constraints`、`default` を横並びに持つ構造にはしない。
|
||||
|
||||
## RuntimeValue
|
||||
|
||||
```text
|
||||
RuntimeValue =
|
||||
Concrete(ConcreteValue)
|
||||
Abstract(AbstractValue)
|
||||
```
|
||||
|
||||
`Concrete` は明示的な値である。
|
||||
`Abstract` は、まだ具体値に確定していない制約付きの値である。
|
||||
|
||||
Decodal は `Unknown` を、すべてのDecodal値を包含する最上位の abstract range として持つ。
|
||||
これは存在する具体値の型情報を消す `Any` ではない。具体値を `Unknown` に対して検証した場合は具体値を保持し、`Unknown` のままmaterializeしようとした場合は diagnostic になる。
|
||||
識別子や field が解決できない状態とは区別し、それらは従来どおりその場で diagnostic になる。
|
||||
|
||||
## ConcreteValue
|
||||
|
||||
```text
|
||||
ConcreteValue =
|
||||
String(String)
|
||||
Int(i64)
|
||||
Float(f64)
|
||||
Bool(bool)
|
||||
Array(Vec<ThunkId>)
|
||||
Object(ObjectValue)
|
||||
Function(FunctionValue)
|
||||
```
|
||||
|
||||
object は concrete structure として扱う。
|
||||
ただし、各 field の中身は concrete value でも abstract value でもよい。
|
||||
|
||||
```text
|
||||
ObjectValue:
|
||||
fields: Map<Symbol, ObjectField>
|
||||
rest: Option<ObjectRest>
|
||||
|
||||
ObjectField:
|
||||
value: ThunkId
|
||||
span: Span
|
||||
|
||||
ObjectRest:
|
||||
value: ThunkId
|
||||
span: Span
|
||||
```
|
||||
|
||||
例えば以下の schema object は、object 自体は concrete だが、field の値は abstract value になる。
|
||||
|
||||
```dcdl
|
||||
MyConfig = {
|
||||
host = String;
|
||||
port = Int default 8080;
|
||||
};
|
||||
```
|
||||
|
||||
概念的には以下である。
|
||||
|
||||
```text
|
||||
Concrete(Object {
|
||||
host -> Thunk(Abstract { constraints: [String], default: none })
|
||||
port -> Thunk(Abstract { constraints: [Int], default: 8080 })
|
||||
})
|
||||
```
|
||||
|
||||
## AbstractValue
|
||||
|
||||
```text
|
||||
AbstractValue {
|
||||
constraints: Vec<ConstraintEntry>
|
||||
default: Option<ThunkId>
|
||||
}
|
||||
|
||||
ConstraintEntry {
|
||||
constraint: Constraint
|
||||
span: Span
|
||||
}
|
||||
```
|
||||
|
||||
`default` は `AbstractValue` にだけ存在する。
|
||||
明示的な concrete value がある場合、default は保持しない。
|
||||
|
||||
```dcdl
|
||||
port = Int default 8080;
|
||||
```
|
||||
|
||||
これは以下の runtime value になる。
|
||||
|
||||
```text
|
||||
Abstract {
|
||||
constraints: [Type(Int)]
|
||||
default: Some(Thunk(8080))
|
||||
}
|
||||
```
|
||||
|
||||
```dcdl
|
||||
port = 8000;
|
||||
```
|
||||
|
||||
これは以下である。
|
||||
|
||||
```text
|
||||
Concrete(Int(8000))
|
||||
```
|
||||
|
||||
## Runtime scope
|
||||
|
||||
Decodal runtime は application runtime ではなく、pure value evaluator である。
|
||||
同じ source、同じ import results、同じ host globals が与えられた場合、評価結果は決定的である。
|
||||
|
||||
runtime が扱う責務は以下に限る。
|
||||
|
||||
- expression を評価する。
|
||||
- thunk を必要に応じて force する。
|
||||
- concrete / abstract value を合成する。
|
||||
- materialize 時に constraint を検証する。
|
||||
|
||||
runtime は filesystem、network、environment variable、time、random、mutation を扱わない。
|
||||
core における import は host supplied source または structured value を受け取る境界であり、filesystem access ではない。
|
||||
|
||||
## Constraint
|
||||
|
||||
constraint は concrete value とは別の型として扱う。
|
||||
|
||||
```text
|
||||
Constraint =
|
||||
Unknown
|
||||
Type(PrimitiveType)
|
||||
ArrayItems(ThunkId)
|
||||
MapValues(ThunkId)
|
||||
Compare(Op, Literal)
|
||||
Regex(Pattern)
|
||||
BuiltinPredicate(Symbol)
|
||||
ObjectConstraint(...)
|
||||
```
|
||||
|
||||
`ArrayItems` は配列そのものの型と、すべての要素へ合成する schema thunk を表す。
|
||||
配列用の primitive type は持たず、抽象配列には必ず要素制約が必要である。
|
||||
|
||||
`MapValues` は object の key 集合を制限せず、すべての field value へ適用する schema thunk を表す。
|
||||
連想配列は materialize 後も `Data::Object` になり、別の data variant は持たない。
|
||||
|
||||
`ObjectValue.rest` は名前付きfieldを持つobjectの残余field rangeを表す。rest自体はfieldを生成せず、materializeは実在するfieldだけを出力する。
|
||||
|
||||
初期実装では、object の形は主に `Concrete(Object)` の field に `Abstract` を置くことで表現する。
|
||||
object 全体にかかる constraint は必要になった時点で追加する。
|
||||
|
||||
## Data
|
||||
|
||||
materialize 後の出力可能な値は runtime value とは別型にする。
|
||||
|
||||
```text
|
||||
Data =
|
||||
String(String)
|
||||
Int(i64)
|
||||
Float(f64)
|
||||
Bool(bool)
|
||||
Array(Vec<Data>)
|
||||
Object(Map<Symbol, Data>)
|
||||
```
|
||||
|
||||
`Function`、未解決の `Abstract`、未評価の thunk は `Data` にはならない。
|
||||
|
||||
## 命名
|
||||
|
||||
実装内部では `RuntimeValue` を短く `Value` と呼んでもよい。
|
||||
ただし、materialize 後の出力値とは区別する。
|
||||
|
||||
推奨する区別:
|
||||
|
||||
```text
|
||||
RuntimeValue / Value 言語内部の評価結果。Abstract を含む。
|
||||
ConcreteValue 明示的な具体値。
|
||||
AbstractValue constraint と default を持つ抽象値。
|
||||
Data 外部へ出力可能な最終データ。
|
||||
```
|
||||
@@ -1,88 +0,0 @@
|
||||
# Thunk and Lazy Evaluation
|
||||
|
||||
thunk は、まだ評価していない式をあとで評価できるように包んだ遅延計算である。
|
||||
この処理系では、循環検出と memoize の単位として thunk を使う。
|
||||
|
||||
## Thunk
|
||||
|
||||
```text
|
||||
Thunk {
|
||||
expr: ExprRef
|
||||
env: EnvId
|
||||
state: ThunkState
|
||||
}
|
||||
|
||||
ExprRef {
|
||||
module: ModuleId
|
||||
expr: ExprId
|
||||
}
|
||||
|
||||
ThunkState =
|
||||
Unevaluated
|
||||
Evaluating
|
||||
Evaluated(RuntimeValue)
|
||||
Error(Diagnostic)
|
||||
```
|
||||
|
||||
`expr` は評価対象の AST node を module-qualified に指す。
|
||||
`ExprId` は module-local な ID なので、runtime では `ModuleId` と組み合わせた `ExprRef` を保持する。
|
||||
`env` は、その式を評価するときに使う lexical environment を指す。
|
||||
|
||||
式だけではなく environment も保持するのは、遅延評価された式が定義時の名前解決文脈を必要とするためである。
|
||||
|
||||
## force
|
||||
|
||||
thunk を評価する操作を force と呼ぶ。
|
||||
|
||||
```text
|
||||
force(thunk):
|
||||
Unevaluated -> Evaluating -> Evaluated(value)
|
||||
Evaluated(value) -> value
|
||||
Evaluating -> cycle diagnostic
|
||||
Error(diagnostic) -> diagnostic
|
||||
```
|
||||
|
||||
一度 `Evaluated` になった thunk は memoize される。
|
||||
同じ thunk を複数回 force しても、式は一度だけ評価される。
|
||||
|
||||
## 循環検出
|
||||
|
||||
評価中の thunk を再度 force しようとした場合は循環依存である。
|
||||
|
||||
```dcdl
|
||||
{
|
||||
a = b;
|
||||
b = a;
|
||||
}
|
||||
```
|
||||
|
||||
`a` を force すると、`a -> b -> a` と戻る。
|
||||
このとき `a` は `Evaluating` なので cycle diagnostic を返す。
|
||||
|
||||
## 遅延評価の単位
|
||||
|
||||
thunk は主に以下に使う。
|
||||
|
||||
- module root
|
||||
- object field
|
||||
- let binding
|
||||
- function argument
|
||||
- default expression
|
||||
|
||||
object は field ごとに thunk を持つ。
|
||||
そのため、object の一部だけが必要な場合、他の field は評価されない。
|
||||
|
||||
## module import
|
||||
|
||||
import は module を登録するが、module 全体を即時評価しない。
|
||||
module root や field は thunk として保持され、参照されたときだけ force される。
|
||||
|
||||
これにより、module 間に循環 import があっても、force された thunk の依存が循環しなければ評価できる。
|
||||
|
||||
## function call
|
||||
|
||||
関数引数は thunk として関数の environment に束縛する。
|
||||
関数本体で引数が参照されたときだけ force する。
|
||||
|
||||
関数呼び出し結果そのものはグローバルには memoize しない。
|
||||
field に束縛された関数呼び出し結果は、その field thunk の評価結果として memoize される。
|
||||
@@ -1,242 +0,0 @@
|
||||
# Development
|
||||
|
||||
This document describes the development workflow for Decodal itself.
|
||||
|
||||
## Rust checks
|
||||
|
||||
Run the normal Rust checks from the repository root.
|
||||
|
||||
```sh
|
||||
cargo fmt --check
|
||||
cargo test
|
||||
cargo check -p decodal --no-default-features
|
||||
nix flake check
|
||||
```
|
||||
|
||||
Run the default stdio language server with:
|
||||
|
||||
```sh
|
||||
cargo run -q -p decodal-lsp
|
||||
```
|
||||
|
||||
The protocol integration tests use an in-memory LSP connection and can be run independently:
|
||||
|
||||
```sh
|
||||
cargo test -p decodal-lsp
|
||||
```
|
||||
|
||||
Regex support is optional and should be tested explicitly when touched.
|
||||
|
||||
```sh
|
||||
cargo test -p decodal --features regex
|
||||
cargo run -q -p decodal-cli --features regex -- examples/regex/main.dcdl
|
||||
```
|
||||
|
||||
## crates.io release
|
||||
|
||||
The crates.io release contains `decodal`, `decodal-derive`, `decodal-language-service`, `decodal-language-tools`, and `decodal-lsp`.
|
||||
`decodal-cli` and the Rust source crate `decodal-wasm` remain repository-only packages.
|
||||
The generated WebAssembly package under `packages/decodal-wasm/` is published to npm and JSR.
|
||||
The CodeMirror package bundles the generated formatter WebAssembly from `decodal-language-tools`.
|
||||
The project is dual licensed as `MIT OR Apache-2.0`.
|
||||
|
||||
The authoritative validation commands, dependency order, and publish commands are maintained in `RELEASING.md` at the repository root.
|
||||
|
||||
## 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` for evaluation and `decodal-codemirror` for editor features and source formatting.
|
||||
|
||||
Important files:
|
||||
|
||||
```text
|
||||
site/decodal-site/src/pages/docs/[...slug].astro
|
||||
site/decodal-site/src/pages/playground.astro
|
||||
site/decodal-site/src/scripts/playground.js
|
||||
site/decodal-site/src/scripts/playground-examples.js
|
||||
site/decodal-site/src/layouts/ManualLayout.astro
|
||||
site/decodal-site/src/lib/docs.js
|
||||
site/decodal-site/src/lib/highlight.js
|
||||
packages/decodal-codemirror/src/decodal.js
|
||||
packages/decodal-codemirror/src/decodal-parser.js
|
||||
packages/decodal-wasm/decodal_wasm.js
|
||||
packages/decodal-wasm/decodal_wasm_bg.wasm
|
||||
packages/decodal-codemirror/wasm/decodal_language_tools.js
|
||||
packages/decodal-codemirror/wasm/decodal_language_tools_bg.wasm
|
||||
crates/decodal-wasm/src/lib.rs
|
||||
crates/decodal-language-tools/src/lib.rs
|
||||
```
|
||||
|
||||
Build the WebAssembly packages before building the site:
|
||||
|
||||
```sh
|
||||
cd site/decodal-site
|
||||
npm install
|
||||
npm run build:wasm
|
||||
npm run build
|
||||
```
|
||||
|
||||
`npm run build:wasm` builds both generated WebAssembly packages.
|
||||
`npm run build:wasm:runtime` writes generated runtime files into `packages/decodal-wasm/`.
|
||||
`npm run build:wasm:formatter` writes generated formatter files into `packages/decodal-codemirror/wasm/`.
|
||||
`npm run build` builds only the static site from the current package files.
|
||||
Use `npm run build:all` to regenerate both WebAssembly packages and then build the static site in one command.
|
||||
|
||||
These generated files are committed so the site can be built without requiring every consumer to regenerate the wasm packages first.
|
||||
|
||||
Publish the generated WebAssembly package from its package directory:
|
||||
|
||||
```sh
|
||||
cd packages/decodal-wasm
|
||||
npm publish
|
||||
npx jsr publish
|
||||
```
|
||||
|
||||
Run dry-runs first when preparing a release:
|
||||
|
||||
```sh
|
||||
npm pack --dry-run
|
||||
npx jsr publish --dry-run
|
||||
```
|
||||
|
||||
The npm package name is `decodal-wasm`.
|
||||
The JSR package name is `@hare/decodal-wasm`.
|
||||
The npm package advertises `MIT OR Apache-2.0`; JSR metadata uses `MIT` because its publisher requires a single recognized license identifier. Both license files are included in the package.
|
||||
|
||||
The playground editor uses the `decodal-codemirror` package with the generated Lezer parser in `packages/decodal-codemirror/src/decodal-parser.js`.
|
||||
The canonical grammar is documented in `doc/manual/souce/language/grammar.md`; regenerate the Lezer parser when that grammar or `editors/lezer-decodal/decodal.grammar` changes.
|
||||
|
||||
Publish the CodeMirror package from its package directory:
|
||||
|
||||
```sh
|
||||
cd packages/decodal-codemirror
|
||||
npm install
|
||||
npm publish
|
||||
npx jsr publish
|
||||
```
|
||||
|
||||
Run dry-runs first when preparing a release:
|
||||
|
||||
```sh
|
||||
npm install
|
||||
npm pack --dry-run
|
||||
npx jsr publish --dry-run
|
||||
```
|
||||
|
||||
The npm package name is `decodal-codemirror`.
|
||||
The JSR package name is `@hare/decodal-codemirror`.
|
||||
As with `decodal-wasm`, JSR metadata uses `MIT` while both the MIT and Apache-2.0 license files remain included.
|
||||
|
||||
The documentation build still uses the lightweight JavaScript fallback highlighter so Astro can render Markdown without initializing WASM at build time.
|
||||
|
||||
To run the site locally:
|
||||
|
||||
```sh
|
||||
cd site/decodal-site
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Deploy the static site to Cloudflare Pages with Wrangler direct upload:
|
||||
|
||||
```sh
|
||||
cd site/decodal-site
|
||||
npm run deploy
|
||||
```
|
||||
|
||||
The deploy script runs `npm run build` and then uploads `dist/` to the Pages project named `decodal-site` on branch `master`.
|
||||
Cloudflare Pages treats this as production when the project production branch is `master`.
|
||||
Use `CLOUDFLARE_PROJECT_NAME` when deploying to a differently named Pages project.
|
||||
|
||||
```sh
|
||||
CLOUDFLARE_PROJECT_NAME=my-pages-project npm run deploy
|
||||
```
|
||||
|
||||
If the committed WASM package must be regenerated before deploy, use:
|
||||
|
||||
```sh
|
||||
npm run deploy:all
|
||||
```
|
||||
|
||||
## Tree-sitter grammar
|
||||
|
||||
The Tree-sitter grammar is kept in:
|
||||
|
||||
```text
|
||||
editors/tree-sitter-decodal/
|
||||
```
|
||||
|
||||
Important files:
|
||||
|
||||
```text
|
||||
editors/tree-sitter-decodal/grammar.js
|
||||
editors/tree-sitter-decodal/queries/highlights.scm
|
||||
editors/tree-sitter-decodal/queries/locals.scm
|
||||
editors/tree-sitter-decodal/corpus/basic.txt
|
||||
```
|
||||
|
||||
The grammar is intended to be portable across editors such as Zed, Neovim, Helix, and Emacs.
|
||||
Zed support should consume this grammar rather than relying on a TextMate grammar.
|
||||
|
||||
## Tree-sitter commands
|
||||
|
||||
From the grammar directory:
|
||||
|
||||
```sh
|
||||
cd editors/tree-sitter-decodal
|
||||
npm install
|
||||
npx tree-sitter generate
|
||||
npx tree-sitter test
|
||||
```
|
||||
|
||||
To inspect a parse tree:
|
||||
|
||||
```sh
|
||||
npx tree-sitter parse ../../examples/advanced/main.dcdl
|
||||
```
|
||||
|
||||
The generated parser files under `editors/tree-sitter-decodal/src/` are committed so editor integrations can consume the grammar without regenerating it first.
|
||||
`node_modules/` is ignored and must not be committed.
|
||||
|
||||
## Updating the grammar
|
||||
|
||||
When the Decodal syntax changes:
|
||||
|
||||
1. Update the canonical EBNF in `doc/manual/souce/language/grammar.md`.
|
||||
2. Update the Rust parser/lexer as needed.
|
||||
3. Update `editors/tree-sitter-decodal/grammar.js` and run `npx tree-sitter generate` / `npx tree-sitter test`.
|
||||
4. Update `editors/lezer-decodal/decodal.grammar` and regenerate the CodeMirror parser:
|
||||
|
||||
```sh
|
||||
cd site/decodal-site
|
||||
npx lezer-generator ../../editors/lezer-decodal/decodal.grammar -o ../../packages/decodal-codemirror/src/decodal-parser.js
|
||||
```
|
||||
|
||||
5. Add or update corpus/tests/examples.
|
||||
6. Run Rust and site checks from the repository root.
|
||||
|
||||
## Development shell
|
||||
|
||||
The Nix development shell includes Rust tooling, Node.js, Tree-sitter CLI tooling, and wasm-pack tooling.
|
||||
|
||||
```sh
|
||||
nix develop
|
||||
```
|
||||
|
||||
The shell provides:
|
||||
|
||||
- `cargo`
|
||||
- `rustc`
|
||||
- `rustfmt`
|
||||
- `clippy`
|
||||
- `node`
|
||||
- `npm`
|
||||
- `wasm-pack`
|
||||
- `lld`
|
||||
- `tree-sitter`
|
||||
- `nixfmt`
|
||||
@@ -1,49 +0,0 @@
|
||||
# Decodal Manual
|
||||
|
||||
このディレクトリには、Decodal のマニュアル文書を置く。
|
||||
Decodal は Deferred Constraint Data Language、略称 DCDL のプロジェクト名である。
|
||||
|
||||
## 目次
|
||||
|
||||
1. [Introduction](./introduction.md)
|
||||
2. [Components](./components.md)
|
||||
3. [Language Specification](./language/index.md)
|
||||
1. [Lexical Structure and Syntax](./language/syntax.md)
|
||||
2. [Value](./language/value/index.md)
|
||||
1. [String](./language/value/string.md)
|
||||
2. [Int](./language/value/int.md)
|
||||
3. [Float](./language/value/float.md)
|
||||
4. [Bool](./language/value/bool.md)
|
||||
3. [Expression](./language/expression/index.md)
|
||||
1. [Literal](./language/expression/literal.md)
|
||||
2. [Identifier](./language/expression/identifier.md)
|
||||
3. [Path Reference](./language/expression/path-reference.md)
|
||||
4. [Object](./language/expression/object.md)
|
||||
5. [Array](./language/expression/array.md)
|
||||
6. [Function](./language/expression/function.md)
|
||||
7. [Function Call](./language/expression/function-call.md)
|
||||
8. [Let](./language/expression/let.md)
|
||||
9. [Match](./language/expression/match.md)
|
||||
10. [Import](./language/expression/import.md)
|
||||
11. [Composition](./language/expression/composition.md)
|
||||
12. [Range Refinement](./language/expression/ascription.md)
|
||||
13. [Default](./language/expression/default.md)
|
||||
14. [String Interpolation](./language/expression/string-interpolation.md)
|
||||
4. [Constraints and Defaults](./language/constraints-and-defaults.md)
|
||||
5. [Composition Operators](./language/operators.md)
|
||||
6. [Functions](./language/functions.md)
|
||||
7. [Modules and Imports](./language/modules-and-imports.md)
|
||||
8. [Evaluation Semantics](./language/evaluation.md)
|
||||
9. [Materialization and Errors](./language/materialization-and-errors.md)
|
||||
10. [Naming Conventions](./language/naming.md)
|
||||
11. [Examples](./language/examples.md)
|
||||
4. [Implementation Design](./design/index.md)
|
||||
1. [Execution Pipeline](./design/execution-pipeline.md)
|
||||
2. [Runtime Model](./design/runtime-model.md)
|
||||
3. [Thunk and Lazy Evaluation](./design/thunk-and-lazy-evaluation.md)
|
||||
4. [Composition and Materialization](./design/composition-and-materialization.md)
|
||||
5. [Diagnostics and Fallback](./design/diagnostics-and-fallback.md)
|
||||
6. [Embedding API](./design/embedding-api.md)
|
||||
7. [Features](./design/features.md)
|
||||
5. [Development](./development.md)
|
||||
6. [Open Issues](./open-issues.md)
|
||||
@@ -1,94 +0,0 @@
|
||||
# Introduction
|
||||
|
||||
このマニュアルは、Decodal の目的、設計方針、言語仕様をまとめる。
|
||||
Decodal は **Deferred Constraint Data Language**、略称 **DCDL** のプロジェクト名である。
|
||||
ファイル拡張子は `.dcdl` とする。
|
||||
|
||||
Decodal は、設定値・スキーマ・制約・派生設定を同じ式体系で扱い、組み込み環境でも実装しやすい小さな言語核を提供することを目指す。
|
||||
|
||||
## 目的
|
||||
|
||||
一般的な設定ファイルでは、値の記述、スキーマ定義、デフォルト値、派生設定、バリデーションが別々の仕組みとして扱われやすい。
|
||||
この言語では、それらを単一の式体系に寄せる。
|
||||
|
||||
例えば、以下のように制約と値を同じ構文で合成できる。
|
||||
|
||||
```dcdl
|
||||
Port = Int & >= 1 & <= 65535;
|
||||
NarrowedPort = Port & > 443;
|
||||
|
||||
MyConfig = {
|
||||
host = String;
|
||||
port = NarrowedPort default 8080;
|
||||
};
|
||||
|
||||
Config = MyConfig & {
|
||||
host = "127.0.0.1";
|
||||
port = 8000;
|
||||
};
|
||||
```
|
||||
|
||||
`MyConfig` は設定の形と制約を表し、`Config` はそこへ具体値を合成した設定を表す。
|
||||
具体値は対応する制約を満たす必要がある。
|
||||
|
||||
## 設計目標
|
||||
|
||||
- データ記述とスキーマ記述を同じ構文で表現できる。
|
||||
- 制約を `&` で合成し、値が制約を満たすか検証できる。
|
||||
- 設定値やスキーマを `//` で構造的に patch できる。
|
||||
- `default` により、最終評価時の fallback 値を定義できる。
|
||||
- フィールド単位の遅延評価により、未使用値の評価を避ける。
|
||||
- import 循環があっても、必要なフィールド依存が循環していなければ評価できる。
|
||||
- 処理系を組み込み向けに小さく保てるよう、意味論を明示的かつ決定的にする。
|
||||
|
||||
## 非目標
|
||||
|
||||
初期仕様では以下を必須にしない。
|
||||
|
||||
- 高度な型推論。
|
||||
- match の完全な網羅性検査。
|
||||
- 到達不能分岐の静的検査。
|
||||
- 任意の関数呼び出し結果のグローバル memoize。
|
||||
- 正規表現エンジンの必須搭載。
|
||||
- 汎用 `try / catch` の core 搭載。
|
||||
- 完全なプログラミング言語としての汎用性。
|
||||
|
||||
## 中心概念
|
||||
|
||||
この言語の中心概念は以下である。
|
||||
|
||||
- 値と制約を同じ式として扱う。
|
||||
- `&` で制約を保った合成を行う。
|
||||
- `//` で右辺優先の構造的 patch を行う。
|
||||
- `default` は制約ではなく、最終評価時の fallback として扱う。
|
||||
- フィールド単位で遅延評価する。
|
||||
- import 循環は、実際に必要なフィールド依存が循環しない限り許容する。
|
||||
|
||||
## 組み込み向けの方針
|
||||
|
||||
この言語は、汎用プログラミング言語を目指すものではない。
|
||||
主対象は、設定、スキーマ、制約、派生データの記述である。
|
||||
|
||||
そのため、言語核は「値・制約・構造の合成」と「遅延評価」に寄せる。
|
||||
便利な機能であっても、実装サイズ・評価モデル・エラー決定性を大きく複雑にするものは optional feature または将来拡張として扱う。
|
||||
|
||||
## ドキュメント構成
|
||||
|
||||
言語仕様の解説は [Language Specification](./language/index.md) にまとめる。
|
||||
`language/` 配下には、構文、値、式、制約、演算子、評価意味論など、言語仕様そのものの説明だけを置く。
|
||||
|
||||
処理系の設計は [Implementation Design](./design/index.md) にまとめる。
|
||||
ここでは、AST interpreter、runtime value、thunk、合成処理、materialize、diagnostic の扱いを説明する。
|
||||
|
||||
主な章は以下である。
|
||||
|
||||
- [Value](./language/value/index.md): `String`、`Int`、`Float`、`Bool` などの値・プリミティブ制約。
|
||||
- [Expression](./language/expression/index.md): literal、object、array、function、let、match、import などの式。
|
||||
- [Constraints and Defaults](./language/constraints-and-defaults.md): 制約と `default` の意味。
|
||||
- [Composition Operators](./language/operators.md): `&` と `//` の意味。
|
||||
- [Evaluation Semantics](./language/evaluation.md): 遅延評価、thunk、循環検出。
|
||||
- [Materialization and Errors](./language/materialization-and-errors.md): 最終評価とエラー分類。
|
||||
- [Runtime Model](./design/runtime-model.md): concrete value と abstract value の内部表現。
|
||||
- [Thunk and Lazy Evaluation](./design/thunk-and-lazy-evaluation.md): 遅延計算と循環検出の処理系モデル。
|
||||
|
||||
未確定事項は [Open Issues](./open-issues.md) に集約する。
|
||||
@@ -1,251 +0,0 @@
|
||||
# 制約と default
|
||||
|
||||
この章では、制約と `default` の意味を定義する。
|
||||
|
||||
## 制約
|
||||
|
||||
制約は、値が満たすべき条件を表す。
|
||||
|
||||
```dcdl
|
||||
Int
|
||||
String
|
||||
>= 1
|
||||
<= 65535
|
||||
/Hello! .*/
|
||||
```
|
||||
|
||||
制約は `&` により合成できる。
|
||||
|
||||
```dcdl
|
||||
Port = Int & >= 1 & <= 65535;
|
||||
NarrowedPort = Port & > 443;
|
||||
```
|
||||
|
||||
制約合成の意味は、すべての制約を同時に満たすことである。
|
||||
|
||||
```text
|
||||
A & B = A と B の両方を満たす値または制約
|
||||
```
|
||||
|
||||
矛盾する制約はエラーになる。
|
||||
|
||||
```dcdl
|
||||
Int & String # エラー
|
||||
> 10 & < 5 # エラー
|
||||
Int & > 10 & < 11 # エラー。整数値の候補が存在しない
|
||||
```
|
||||
|
||||
## 制約の正規化
|
||||
|
||||
`&` によって abstract value 同士を合成した場合、処理系は軽量に判定できる制約を正規化する。
|
||||
|
||||
正規化対象:
|
||||
|
||||
- primitive type 制約。
|
||||
- 数値比較制約。
|
||||
|
||||
primitive type 制約は、異なる型が同時に要求された場合 conflict になる。
|
||||
|
||||
```dcdl
|
||||
Int & Float
|
||||
Int & String
|
||||
```
|
||||
|
||||
数値比較制約は上下限として正規化される。
|
||||
|
||||
```dcdl
|
||||
Int & >= 1 & <= 65535 & > 443
|
||||
```
|
||||
|
||||
これは概念的に以下へ正規化される。
|
||||
|
||||
```text
|
||||
Type(Int)
|
||||
> 443
|
||||
<= 65535
|
||||
```
|
||||
|
||||
上下限の交差が空であれば conflict になる。
|
||||
`Int` 制約がある場合は、整数候補が存在するかも判定する。
|
||||
|
||||
```dcdl
|
||||
> 10 & < 5 # conflict
|
||||
Int & > 10 & < 11 # conflict
|
||||
Int & >= 10 & <= 10 # OK
|
||||
```
|
||||
|
||||
`Int` の比較制約は整数リテラルを使う。
|
||||
`Float` の比較制約は整数リテラルまたは浮動小数リテラルを使える。
|
||||
|
||||
## 組み込み制約
|
||||
|
||||
最小の組み込み制約は以下である。
|
||||
|
||||
```dcdl
|
||||
Unknown
|
||||
String
|
||||
Int
|
||||
Float
|
||||
Bool
|
||||
```
|
||||
|
||||
`Unknown` はすべてのDecodal値を含む最上位rangeである。検査を無効化する `Any` ではなく、具体的な値またはより狭いrangeがまだ決まっていないことを表す。
|
||||
|
||||
```dcdl
|
||||
Int & Unknown # Int
|
||||
42 as Unknown # 42
|
||||
Unknown as Int # エラー
|
||||
```
|
||||
|
||||
`Unknown` 自体は具体値を持たないためmaterializeできない。defaultを与えるか、具体値で絞り込む必要がある。
|
||||
|
||||
```dcdl
|
||||
Unknown default {} # {}
|
||||
```
|
||||
|
||||
追加の述語制約はライブラリまたは組み込みとして提供できる。
|
||||
|
||||
```dcdl
|
||||
IPv4Address
|
||||
```
|
||||
|
||||
## 正規表現制約
|
||||
|
||||
正規表現リテラルは文字列制約として使える。
|
||||
|
||||
```dcdl
|
||||
Host = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
|
||||
```
|
||||
|
||||
正規表現制約は積み重ね可能である。
|
||||
複数の正規表現制約が同じ abstract value に付与された場合、具体文字列はすべての正規表現制約に一致しなければならない。
|
||||
|
||||
```dcdl
|
||||
String & /^a/ & /z$/
|
||||
```
|
||||
|
||||
処理系は、正規表現制約同士の交差が空であるかを合成時に判定する必要はない。
|
||||
つまり、以下は合成時には conflict にならず、具体値検証時に失敗する。
|
||||
|
||||
```dcdl
|
||||
String & /^a$/ & /^b$/
|
||||
```
|
||||
|
||||
正規表現エンジンは optional feature にできる。
|
||||
正規表現 feature が無効な処理系では、正規表現制約の検証は unsupported feature diagnostic になる。
|
||||
軽量実装では代表的な制約を組み込み述語として提供してもよい。
|
||||
|
||||
```dcdl
|
||||
Host = IPv4Address;
|
||||
```
|
||||
|
||||
## 配列制約
|
||||
|
||||
配列制約には要素制約が必須であり、`[...T]` と書く。
|
||||
|
||||
```dcdl
|
||||
Names = [...String];
|
||||
PositiveInts = [...(Int & > 0)];
|
||||
```
|
||||
|
||||
複数の配列制約を `&` で合成した場合、各 concrete 要素をすべての要素 range で絞り込む。
|
||||
要素が object range の場合、右辺にしかない field は default の有無にかかわらず abstract のまま各要素へ残る。
|
||||
左辺にしかない field は右辺の field domain 外なのでエラーになる。
|
||||
要素制約のない `Array` primitive type は存在しない。
|
||||
|
||||
## 連想配列制約
|
||||
|
||||
連想配列制約は `{...T}` と書き、object の任意の field value を `T` に対して絞り込む。
|
||||
|
||||
```dcdl
|
||||
Services = {...{
|
||||
port = Int;
|
||||
enabled = Bool default true;
|
||||
}};
|
||||
```
|
||||
|
||||
key は schema で列挙せず、空 object も許容する。
|
||||
固定 object field と任意 key の value constraint は、末尾の `...T` で混在できる。
|
||||
|
||||
```dcdl
|
||||
{
|
||||
enabled = Bool default true;
|
||||
...Unknown
|
||||
}
|
||||
```
|
||||
|
||||
このrest constraintは明示されていないfieldだけに適用され、field自体は生成しない。
|
||||
|
||||
## default
|
||||
|
||||
`default` は制約ではない。
|
||||
`default` は、最終評価時に明示値が存在しない場合だけ使われる fallback 値である。
|
||||
|
||||
```dcdl
|
||||
port = NarrowedPort default 8080;
|
||||
```
|
||||
|
||||
これは概念的には以下を表す。
|
||||
|
||||
```text
|
||||
Abstract {
|
||||
constraints: [NarrowedPort]
|
||||
default: 8080
|
||||
}
|
||||
```
|
||||
|
||||
明示値が合成された場合、`default` は採用されない。
|
||||
|
||||
```dcdl
|
||||
MyConfig = {
|
||||
port = NarrowedPort default 8080;
|
||||
};
|
||||
|
||||
Config = MyConfig & {
|
||||
port = 8000;
|
||||
};
|
||||
```
|
||||
|
||||
この場合、最終値は `8000` である。
|
||||
`8080` は評価されない、または評価されても採用されない。
|
||||
|
||||
明示値がない場合、最終 materialize 時に `default` が採用される。
|
||||
採用された default 値は、同じフィールドに定義された制約を満たす必要がある。
|
||||
|
||||
```text
|
||||
Abstract {
|
||||
constraints: [NarrowedPort]
|
||||
default: 8080
|
||||
}
|
||||
|
||||
finalize => 8080 が NarrowedPort を満たせば成功
|
||||
```
|
||||
|
||||
## default の内部表現
|
||||
|
||||
`default` は abstract value に付随する fallback thunk として保持できる。
|
||||
これにより、default 値自体も必要になるまで評価しない。
|
||||
|
||||
```text
|
||||
RuntimeValue =
|
||||
Concrete(ConcreteValue)
|
||||
Abstract {
|
||||
constraints: Vec<Constraint>
|
||||
default: Option<Thunk>
|
||||
}
|
||||
```
|
||||
|
||||
明示値は `Concrete` として表現し、`default` を保持しない。
|
||||
`Abstract & Concrete` が成功した場合、制約検証後に `Concrete` になり、default は消える。
|
||||
|
||||
## default の合成
|
||||
|
||||
`default` の合成規則は以下である。
|
||||
|
||||
- `&` で片方だけが default を持つ場合、その default を保持する。
|
||||
- `&` で両方が異なる default を持つ場合、conflict になる。
|
||||
- `Abstract & Concrete` が成功した場合、結果は concrete value になり default は消える。
|
||||
- `//` では右辺が左辺を置き換える。object/object の場合は field ごとに再帰 patch されるため、右辺 field の default が左辺 field の default を置き換える。
|
||||
|
||||
`default` thunk は materialize 時に必要になった時点で評価する。
|
||||
評価された default value は、同じ abstract value に残っている制約を満たす必要がある。
|
||||
@@ -1,78 +0,0 @@
|
||||
# 遅延評価
|
||||
|
||||
この言語はフィールド単位で遅延評価する。
|
||||
|
||||
## 基本方針
|
||||
|
||||
```dcdl
|
||||
{
|
||||
schema = {
|
||||
hoge = String;
|
||||
};
|
||||
|
||||
result = expensive(schema);
|
||||
}
|
||||
```
|
||||
|
||||
`schema` のみが必要な場合、`result` は評価されない。
|
||||
|
||||
## thunk
|
||||
|
||||
各フィールドや let 束縛は thunk として保持できる。
|
||||
|
||||
```text
|
||||
Thunk {
|
||||
expr: ExprId
|
||||
env: EnvRef
|
||||
state: Unevaluated | Evaluating | Evaluated(Value) | Error
|
||||
}
|
||||
```
|
||||
|
||||
評価済み thunk は memoize する。
|
||||
同じフィールドを複数回参照しても、評価は一度だけでよい。
|
||||
|
||||
## 評価状態
|
||||
|
||||
thunk は以下の状態を持つ。
|
||||
|
||||
```text
|
||||
Unevaluated 未評価
|
||||
Evaluating 評価中
|
||||
Evaluated 評価済み
|
||||
Error 評価失敗
|
||||
```
|
||||
|
||||
`Evaluating` の thunk を再度評価しようとした場合、循環依存として扱う。
|
||||
|
||||
## 循環検出
|
||||
|
||||
```dcdl
|
||||
{
|
||||
a = b + 1;
|
||||
b = a + 1;
|
||||
}
|
||||
```
|
||||
|
||||
この場合、`a` または `b` を評価すると循環エラーになる。
|
||||
|
||||
一方、同じモジュール内または import 間に循環があっても、評価対象のフィールドが循環していなければ成功する。
|
||||
|
||||
## 評価と materialize の分離
|
||||
|
||||
通常の評価では、制約や default を含む中間値が残ることがある。
|
||||
外部へデータとして出力する段階で materialize を行う。
|
||||
|
||||
この分離により、以下が可能になる。
|
||||
|
||||
- スキーマを値として扱う。
|
||||
- default を必要になるまで評価しない。
|
||||
- import されたモジュールの未使用フィールドを評価しない。
|
||||
- 制約だけのフィールドを中間状態として保持する。
|
||||
|
||||
## 関数呼び出しとの関係
|
||||
|
||||
関数引数は thunk として渡せる。
|
||||
関数本体内で引数が参照されたときに評価する。
|
||||
|
||||
関数呼び出し結果そのものはグローバルには memoize しない。
|
||||
ただし、フィールドに束縛された呼び出し結果は、そのフィールド thunk の評価結果として memoize される。
|
||||
@@ -1,15 +0,0 @@
|
||||
# Default Expression
|
||||
|
||||
`default` expression は、明示値が存在しない場合に materialize 時に採用される fallback を指定する。
|
||||
|
||||
```dcdl
|
||||
port = Int default 8080;
|
||||
```
|
||||
|
||||
`default` は制約ではない。
|
||||
詳細は [制約と default](../constraints-and-defaults.md) に置く。
|
||||
|
||||
## 評価
|
||||
|
||||
fallback 値は thunk として保持できる。
|
||||
明示値がある場合、default は採用されない。
|
||||
@@ -1,14 +0,0 @@
|
||||
# Identifier Expression
|
||||
|
||||
identifier expression は、現在の環境に束縛された名前を参照する式である。
|
||||
|
||||
```dcdl
|
||||
Port
|
||||
MyConfig
|
||||
mkConfig
|
||||
```
|
||||
|
||||
## 評価
|
||||
|
||||
識別子は、対応する束縛の thunk を参照する。
|
||||
束縛が存在しない場合は未定義識別子エラーになる。
|
||||
@@ -1,15 +0,0 @@
|
||||
# Import Expression
|
||||
|
||||
import expression は、外部ファイルを読み込み、そのファイルの評価結果を返す。
|
||||
|
||||
```dcdl
|
||||
import ./config.n
|
||||
import "./config.n"
|
||||
```
|
||||
|
||||
import 仕様の詳細は [モジュールと import](../modules-and-imports.md) に置く。
|
||||
|
||||
## 評価
|
||||
|
||||
import 先はモジュール単位で読み込まれる。
|
||||
ただし、各 field は thunk として保持され、必要になるまで評価されない。
|
||||
@@ -1,16 +0,0 @@
|
||||
# Let Expression
|
||||
|
||||
let expression は、ローカル束縛を作る。
|
||||
|
||||
```dcdl
|
||||
let
|
||||
base = 8000;
|
||||
offset = 80;
|
||||
in
|
||||
base + offset
|
||||
```
|
||||
|
||||
## 評価
|
||||
|
||||
let 束縛は thunk として保持される。
|
||||
参照されない束縛は評価されない。
|
||||
@@ -1,8 +0,0 @@
|
||||
# String Interpolation
|
||||
|
||||
文字列補間は初期実装には含めない。
|
||||
|
||||
Decodal の string literal は、現時点では literal text として扱う。
|
||||
式を埋め込む構文は定義しない。
|
||||
|
||||
必要になった場合は、文字列連結や明示的な formatting function として別途設計する。
|
||||
@@ -1,59 +0,0 @@
|
||||
# 関数
|
||||
|
||||
関数は構造を受け取り、構造を返す式として扱う。
|
||||
|
||||
## 構文
|
||||
|
||||
```dcdl
|
||||
(value: Int) => value + 1
|
||||
```
|
||||
|
||||
関数呼び出しは通常の呼び出し構文で行う。
|
||||
|
||||
```dcdl
|
||||
let
|
||||
increment = (value: Int) => value + 1;
|
||||
in
|
||||
increment(41)
|
||||
```
|
||||
|
||||
複数引数も指定できる。
|
||||
|
||||
```dcdl
|
||||
(input_a: { hoge = Int & >= 0; }, input_b: { fuga = Int; }) =>
|
||||
{
|
||||
hoge = input_a.hoge;
|
||||
fuga = input_b.fuga;
|
||||
}
|
||||
```
|
||||
|
||||
## 関数の意味
|
||||
|
||||
関数は runtime value として扱えるが、最終データとして materialize することはできない。
|
||||
未適用の関数値が materialize 対象に残っている場合は diagnostic になる。
|
||||
|
||||
関数値は opaque であり、関数値同士の等価性は提供しない。
|
||||
`&` で関数値同士を合成すると conflict になる。
|
||||
|
||||
## 評価方針
|
||||
|
||||
関数は以下の方針で評価する。
|
||||
|
||||
- 関数は純粋である。
|
||||
- 関数はレキシカルスコープを持つ。
|
||||
- 関数は定義時の環境を参照として保持する。
|
||||
- 引数は thunk として渡され、必要になるまで評価されない。
|
||||
- parameter range がある引数は、force 時に `as` と同じ範囲包含・絞り込み規則で合成される。
|
||||
- 関数呼び出し結果そのものはグローバルには memoize しない。
|
||||
- フィールドに束縛された関数呼び出し結果は、そのフィールド thunk の評価結果として memoize される。
|
||||
- 再帰的な依存は thunk cycle として diagnostic になる。
|
||||
|
||||
関数値の内部モデル例:
|
||||
|
||||
```text
|
||||
Function {
|
||||
params: Vec<Param>
|
||||
body: ExprId
|
||||
env: EnvRef
|
||||
}
|
||||
```
|
||||
@@ -1,12 +0,0 @@
|
||||
# 言語仕様
|
||||
|
||||
このディレクトリは、Decodal / DCDL の仕様本文を章ごとに分割して管理する。
|
||||
|
||||
目次はマニュアル直下の [Manual Index](../index.md) に集約する。
|
||||
このファイルは `Language Specification` 章の入口としてだけ使う。
|
||||
|
||||
## Language
|
||||
|
||||
言語仕様は、構文、値、式、制約、合成演算子、関数、モジュール、評価意味論、materialize、エラーを定義する。
|
||||
|
||||
詳細な章構成と各ファイルへのリンクは [Manual Index](../index.md) を参照する。
|
||||
@@ -1,99 +0,0 @@
|
||||
# materialize とエラー
|
||||
|
||||
通常の評価では、制約や default を含む中間値が残ることがある。
|
||||
外部へデータとして出力する段階では、materialize を行う。
|
||||
|
||||
## materialize の責務
|
||||
|
||||
materialize は以下を行う。
|
||||
|
||||
- 必要なフィールドを評価する。
|
||||
- 明示値がない abstract value に default を適用する。
|
||||
- 採用された値が制約を満たすか検証する。
|
||||
- default を持たない未解決の abstract value をエラーにする。
|
||||
- 未適用の関数など、データとして出力できない値をエラーにする。
|
||||
|
||||
## 例
|
||||
|
||||
```dcdl
|
||||
MyConfig = {
|
||||
host = String;
|
||||
port = Int default 8080;
|
||||
};
|
||||
```
|
||||
|
||||
`MyConfig` を materialize すると、`host` は具体値も default もないためエラーになる。
|
||||
`port` は `8080` が採用される。
|
||||
|
||||
```dcdl
|
||||
Config = MyConfig & {
|
||||
host = "localhost";
|
||||
};
|
||||
```
|
||||
|
||||
`Config` を materialize すると以下になる。
|
||||
|
||||
```dcdl
|
||||
{
|
||||
host = "localhost";
|
||||
port = 8080;
|
||||
}
|
||||
```
|
||||
|
||||
## default の適用
|
||||
|
||||
`default` は materialize 時にのみ fallback として採用される。
|
||||
|
||||
```text
|
||||
Abstract {
|
||||
constraints: [Int]
|
||||
default: 8080
|
||||
}
|
||||
```
|
||||
|
||||
この abstract value を materialize すると、`8080` が採用され、`Int` を満たすか検証される。
|
||||
|
||||
明示値がある場合、default は採用しない。
|
||||
明示値は concrete value として表現され、default を保持しない。
|
||||
|
||||
```text
|
||||
Concrete(Int(9000))
|
||||
```
|
||||
|
||||
この値の最終値は `9000` である。
|
||||
|
||||
## エラー分類
|
||||
|
||||
代表的なエラー:
|
||||
|
||||
- 構文エラー
|
||||
- 未定義識別子
|
||||
- 型不一致
|
||||
- 制約違反
|
||||
- `&` の conflict
|
||||
- `default` の conflict
|
||||
- 循環依存
|
||||
- import 失敗
|
||||
- match の非網羅による失敗
|
||||
- materialize 不能な値の出力
|
||||
|
||||
## match の失敗
|
||||
|
||||
`match` に fallback 分岐がなく、どの分岐にも一致しなかった場合はエラーになる。
|
||||
|
||||
```dcdl
|
||||
match value {
|
||||
>= 10: "large";
|
||||
}
|
||||
```
|
||||
|
||||
`value` が `10` 未満であれば失敗する。
|
||||
|
||||
## エラーは値ではない
|
||||
|
||||
エラーは runtime value ではなく diagnostic として扱う。
|
||||
通常の式はエラー内容に基づいて分岐できない。
|
||||
|
||||
汎用 `try / catch` は core には含めない。
|
||||
fallback は `default` と `match` で表現する。
|
||||
optional import や optional field access は core には含めない。
|
||||
@@ -1,159 +0,0 @@
|
||||
# モジュールと import
|
||||
|
||||
`import` は外部ファイルを読み込み、そのファイルの評価結果を返す。
|
||||
|
||||
## 構文
|
||||
|
||||
```dcdl
|
||||
import "./config.dcdl"
|
||||
```
|
||||
|
||||
import specifier は文字列リテラルとする。
|
||||
パスリテラル構文は採用しない。
|
||||
|
||||
## モジュール
|
||||
|
||||
import 先はモジュール単位で読み込まれる。
|
||||
ただし、モジュール全体を即時評価する必要はない。
|
||||
各フィールドは thunk として保持され、必要になったときだけ評価される。
|
||||
|
||||
top-level に field 定義列を書いた module は、recursive module scope を作る。
|
||||
つまり、top-level field は同じ module の他の top-level field から識別子として参照できる。
|
||||
|
||||
```dcdl
|
||||
schema = {
|
||||
hoge = String;
|
||||
};
|
||||
|
||||
result = schema;
|
||||
```
|
||||
|
||||
この場合、`result` の右辺の `schema` は同じ module の top-level field `schema` を参照する。
|
||||
通常の object literal 内の field は、その object 内の sibling field を暗黙には識別子として参照できない。
|
||||
object 内の値を参照する場合は、外側で束縛された値や明示的な path reference を使う。
|
||||
|
||||
## ImportLoader
|
||||
|
||||
`import` specifier の解決は処理系 core ではなく host 側の `ImportLoader` が行う。
|
||||
CLI では、specifier を現在の module path からの相対 path として解決する。
|
||||
組み込み利用では、resource table や static source map など、filesystem 以外の loader を使える。
|
||||
|
||||
module cache の key は loader が返す安定 key を使う。
|
||||
CLI では canonical path を key とする。
|
||||
|
||||
### 構造化 import
|
||||
|
||||
`ImportLoader::load` は DCDL source または host が構築した `Value` を import 結果として返す。
|
||||
Markdown、JSON、TOML などの解釈規則は core に固定せず、loader がファイル種別を判定して構造化する。
|
||||
|
||||
```rust
|
||||
use decodal::{Value, ImportLoader, LoadedImport};
|
||||
|
||||
struct ContentLoader;
|
||||
|
||||
impl ImportLoader for ContentLoader {
|
||||
fn load(
|
||||
&mut self,
|
||||
current_key: Option<&str>,
|
||||
specifier: &str,
|
||||
) -> decodal::Result<LoadedImport> {
|
||||
if specifier.ends_with(".md") {
|
||||
let markdown = read_content(current_key, specifier)?;
|
||||
let parsed = parse_frontmatter(&markdown)?;
|
||||
return Ok(LoadedImport::value(
|
||||
parsed.key,
|
||||
Value::object([
|
||||
("frontmatter", parsed.frontmatter),
|
||||
("body", Value::string(parsed.body)),
|
||||
]),
|
||||
));
|
||||
}
|
||||
|
||||
let source = read_dcdl(current_key, specifier)?;
|
||||
Ok(LoadedImport::source(
|
||||
source.key,
|
||||
specifier,
|
||||
source.text,
|
||||
))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`read_content` と `parse_frontmatter` は host 独自の処理であり、Decodal core は Markdown や YAML parser に依存しない。
|
||||
上の loader を使うと、DCDL 側から次のように扱える。
|
||||
|
||||
```dcdl
|
||||
Post = {
|
||||
frontmatter = {
|
||||
title = String;
|
||||
draft = Bool default false;
|
||||
};
|
||||
body = String;
|
||||
};
|
||||
|
||||
post = Post & import "./hello.md";
|
||||
title = post.frontmatter.title;
|
||||
body = post.body;
|
||||
```
|
||||
|
||||
`LoadedImport::Value` は通常の concrete runtime value に internalize される。
|
||||
そのため、path reference、object composition、constraint validation、materialize は source 由来の値と同じ規則を使う。
|
||||
安定した `key` が同じ構造化 import は、engine 内で同じ値としてキャッシュされる。
|
||||
|
||||
`load` が唯一の import hook である。
|
||||
loader は拡張子、media type、または host 独自の規則で振り分け、対応する `LoadedImport` variant を直接返す。
|
||||
|
||||
## 循環 import
|
||||
|
||||
モジュール間に循環参照があっても、必要なフィールドの依存関係が循環していなければ評価できる。
|
||||
|
||||
例:
|
||||
|
||||
```dcdl
|
||||
# main.dcdl
|
||||
{
|
||||
schema = {
|
||||
hoge = String;
|
||||
};
|
||||
|
||||
result = (import "./func.dcdl")(schema);
|
||||
}
|
||||
```
|
||||
|
||||
```dcdl
|
||||
# func.dcdl
|
||||
(input: (import "./main.dcdl").schema) =>
|
||||
{
|
||||
# ...
|
||||
}
|
||||
```
|
||||
|
||||
`func.dcdl` は `main.dcdl` を import しているが、参照しているのは `main.schema` である。
|
||||
`main.schema` が `main.result` に依存していなければ、この循環 import は成立する。
|
||||
|
||||
## import の評価単位
|
||||
|
||||
実装上は、以下の単位で管理するのが自然である。
|
||||
|
||||
```text
|
||||
Module main
|
||||
schema -> thunk
|
||||
result -> thunk
|
||||
|
||||
Module func
|
||||
root -> thunk
|
||||
```
|
||||
|
||||
各 thunk は一度だけ評価して memoize する。
|
||||
評価中に同じ thunk へ戻った場合は循環依存としてエラーにする。
|
||||
|
||||
## import 失敗
|
||||
|
||||
以下は import 失敗として扱う。
|
||||
|
||||
- ファイルが存在しない。
|
||||
- ファイルが読めない。
|
||||
- import 先の構文解析に失敗する。
|
||||
- import 先の評価で必要な値がエラーになる。
|
||||
- host による非 DCDL content の読み込みまたは構造化に失敗する。
|
||||
- 実装が禁止する import 循環に該当する。
|
||||
@@ -1,27 +0,0 @@
|
||||
# 命名規約
|
||||
|
||||
具体値と抽象値がグラデーションになるため、大文字・小文字による厳密な意味分けは設けない。
|
||||
|
||||
ただし、読みやすさのために慣習を定める。
|
||||
|
||||
## 推奨規約
|
||||
|
||||
- object 値: `lower_snake`
|
||||
- 関数: `lowerCamel`
|
||||
- 組み込み型・抽象的な制約名: `UpperCamel`
|
||||
|
||||
例:
|
||||
|
||||
```dcdl
|
||||
IPv4Address
|
||||
MyConfig
|
||||
new_config
|
||||
mkConfig
|
||||
```
|
||||
|
||||
## 厳密な規則にしない理由
|
||||
|
||||
この言語では、値・制約・スキーマ・派生設定が同じ式体系に乗る。
|
||||
そのため、ある名前が「具体値」か「抽象的な制約」かは文脈によってグラデーションになる。
|
||||
|
||||
大文字なら型、小文字なら値、のような厳密な規則を設けると、実際の利用に対して過剰に硬くなる可能性がある。
|
||||
@@ -1,23 +0,0 @@
|
||||
# 未確定事項
|
||||
|
||||
今後決める必要がある事項を管理する。
|
||||
実装または仕様方針が固まった項目は、該当する仕様ファイルへ反映してここから外す。
|
||||
|
||||
## Materialize target and host projection
|
||||
|
||||
Decodal の評価結果は、制約・default・関数値を含む中間値になり得る。
|
||||
そのため、Decodal 単独で常に「最終成果物」を一意に決めるのではなく、host 側が期待する型や出力形式を与えて materialize / decode する経路を明確にする必要がある。
|
||||
|
||||
決めること:
|
||||
|
||||
- Rust API で評価結果の field/path を選択して decode / materialize できるようにするか。
|
||||
- `decodal-derive` の struct schema と評価結果を合成して decode する経路を、主要な materialize path として位置づけるか。
|
||||
- CLI / WASM では target path を指定して JSON-compatible value へ materialize する形にするか。
|
||||
- 制約や関数値が残った値を出力したい場合、materialize ではなく inspect/debug API として分けるか。
|
||||
|
||||
現時点の案:
|
||||
|
||||
- Rust では `evaluate -> select field/path -> expected schema と合成 -> decode` を主経路にする。
|
||||
- CLI / WASM では、明示された target path または module 全体を JSON-compatible value として materialize する。
|
||||
- materialize できない unresolved abstract value、default のない制約値、関数値は diagnostic にする。
|
||||
- Decodal 言語内には materialize 構文を追加せず、host API / CLI / WASM の責務として扱う。
|
||||
@@ -0,0 +1,81 @@
|
||||
# Packages and Integrations
|
||||
|
||||
Decodal provides separate packages for execution, host embedding, language services, and editor integration.
|
||||
|
||||
## Rust
|
||||
|
||||
### `decodal`
|
||||
|
||||
The runtime and embedding API for Rust applications.
|
||||
It handles parsing, evaluation, and materialization of source; host globals; and imports containing either source or structured values.
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
decodal = "0.4"
|
||||
```
|
||||
|
||||
Enable the `derive` feature to generate a Decodal schema and decoder from a Rust struct.
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
decodal = { version = "0.4", features = ["derive"] }
|
||||
```
|
||||
|
||||
Enable the `regex` feature to validate regular-expression constraints.
|
||||
This feature requires `std`.
|
||||
|
||||
### `decodal-language-service`
|
||||
|
||||
Provides transport-independent semantic evaluation and completion.
|
||||
It accepts an application's `HostEnvironment` directly, allowing production and editor evaluation to share global bindings and import rules.
|
||||
|
||||
### `decodal-lsp`
|
||||
|
||||
Connects the language service to the Language Server Protocol.
|
||||
In addition to a stdio server, it exposes a library API that constructs an application-specific environment from the client's `InitializeParams`.
|
||||
|
||||
### `decodal-language-tools`
|
||||
|
||||
Provides shared tools for source text, including the formatter.
|
||||
The Rust, LSP, and WebAssembly integrations all use the same formatter implementation.
|
||||
|
||||
## JavaScript and WebAssembly
|
||||
|
||||
### `decodal-wasm`
|
||||
|
||||
Provides the evaluator and language service for browsers and other WebAssembly runtimes.
|
||||
|
||||
```sh
|
||||
npm install decodal-wasm
|
||||
```
|
||||
|
||||
It is also published on JSR as `@hare/decodal-wasm`.
|
||||
Pass `globals`, `loadImport`, and `completeImport` to `DecodalLanguageService` so JavaScript-owned environments are shared by evaluation and completion.
|
||||
|
||||
### `decodal-codemirror`
|
||||
|
||||
Language support for CodeMirror 6.
|
||||
It provides syntax highlighting, folding, indentation, and integration with the Decodal formatter.
|
||||
|
||||
```sh
|
||||
npm install decodal-codemirror
|
||||
```
|
||||
|
||||
It is also published on JSR as `@hare/decodal-codemirror`.
|
||||
Combine it with the `decodal-wasm` language service when semantic evaluation and completion are required.
|
||||
|
||||
## Editor syntax
|
||||
|
||||
The Tree-sitter grammar is an integration for parsing and highlighting in editors that use Tree-sitter.
|
||||
Tree-sitter is not required by the Decodal runtime, formatter, or LSP.
|
||||
|
||||
Choose packages according to the integration you need:
|
||||
|
||||
- Execution and embedding in Rust: `decodal`
|
||||
- Execution and semantic tooling in a browser: `decodal-wasm`
|
||||
- CodeMirror 6: `decodal-codemirror`
|
||||
- Transport-independent Rust language service: `decodal-language-service`
|
||||
- General editor clients: `decodal-lsp`
|
||||
- Syntax grammar for editors that use Tree-sitter: the Tree-sitter integration
|
||||
|
||||
See [Embedding](./embedding.md) for concrete environment setup.
|
||||
@@ -0,0 +1,193 @@
|
||||
# Embedding
|
||||
|
||||
The Decodal runtime does not read filesystems, networks, or environment variables directly.
|
||||
The host injects global bindings and import resolution, then receives the evaluation result as `Data` or an application type.
|
||||
|
||||
## Rust runtime
|
||||
|
||||
`Engine` owns an `ImportLoader` and global bindings.
|
||||
Use `EmptyLoader` for source that does not require external resources.
|
||||
|
||||
```rust
|
||||
use decodal::{EmptyLoader, Engine, Value};
|
||||
|
||||
let mut engine = Engine::new(EmptyLoader);
|
||||
engine.bind_global(
|
||||
"Service",
|
||||
Value::object([
|
||||
("name", Value::string_type()),
|
||||
("port", Value::int_type().gt(443).default_int(8443)?),
|
||||
("enabled", Value::bool_type().default_bool(true)?),
|
||||
]),
|
||||
)?;
|
||||
|
||||
let module = engine.add_root_source(
|
||||
"service.dcdl",
|
||||
"service.dcdl",
|
||||
r#"{ name = "api"; port = 9443; } as Service"#,
|
||||
)?;
|
||||
let value = engine.eval_module(module)?;
|
||||
let data = engine.materialize(&value)?;
|
||||
```
|
||||
|
||||
`Value` is the public type for both concrete values and host-defined ranges.
|
||||
Its main constructors are:
|
||||
|
||||
- `Value::string`, `int`, `float`, `bool`, `array`, and `object`: concrete values.
|
||||
- `Value::string_type`, `int_type`, `float_type`, and `bool_type`: primitive ranges.
|
||||
- `Value::unknown`: the top range, `Unknown`.
|
||||
- `Value::array_of`: an array range that applies one range to every element.
|
||||
- `Value::map_of`: an associative-array range that applies one range to every field value.
|
||||
- `Value::object_with_rest`: an object with named fields and a range for remaining fields.
|
||||
|
||||
## Typed Rust integration
|
||||
|
||||
The `Decodal` derive from the `derive` feature generates `DecodalSchema` and `DecodalDecode` implementations from a Rust struct.
|
||||
|
||||
```rust
|
||||
use decodal::{Decodal, DecodalDecode, DecodalSchema};
|
||||
|
||||
#[derive(Decodal)]
|
||||
struct Service {
|
||||
name: String,
|
||||
|
||||
#[decodal(gt = 443, default = 8443)]
|
||||
port: i64,
|
||||
|
||||
#[decodal(rename = "feature.enable", default = true)]
|
||||
feature_enabled: bool,
|
||||
}
|
||||
```
|
||||
|
||||
`DecodalSchema::decodal_schema()` returns a `Value` suitable for `Engine::bind_global`.
|
||||
Materialized `Data` can be converted to a Rust type with `DecodalDecode::decodal_decode`.
|
||||
|
||||
For a struct that accepts additional fields, mark a map field with `#[decodal(rest)]`.
|
||||
|
||||
```rust
|
||||
use std::collections::BTreeMap;
|
||||
use decodal::{Data, Decodal};
|
||||
|
||||
#[derive(Decodal)]
|
||||
struct OpenConfig {
|
||||
enabled: bool,
|
||||
|
||||
#[decodal(rest)]
|
||||
extra: BTreeMap<String, Data>,
|
||||
}
|
||||
```
|
||||
|
||||
`BTreeMap<String, Data>` corresponds to `...Unknown`, while `BTreeMap<String, String>` corresponds to `...String`.
|
||||
Named fields are excluded from the rest map.
|
||||
Only one `#[decodal(rest)]` field is allowed per struct, and it cannot be combined with `rename`, `default`, or field constraints.
|
||||
|
||||
## Imports
|
||||
|
||||
`ImportLoader::load` returns either `LoadedImport::Source` or `LoadedImport::Value`.
|
||||
The host controls specifier resolution, reading from the filesystem or other storage, and content-type detection.
|
||||
|
||||
`LoadedImport::Source` supplies DCDL source.
|
||||
`LoadedImport::Value` is intended for structured host-defined representations of formats such as Markdown, JSON, and TOML.
|
||||
|
||||
```text
|
||||
import "./post.md"
|
||||
-> host reads and parses Markdown
|
||||
-> LoadedImport::Value {
|
||||
key: "content/post.md",
|
||||
value: { frontmatter: {...}, body: "..." }
|
||||
}
|
||||
-> normal Decodal validation and materialization
|
||||
```
|
||||
|
||||
A stable `key` identifies an import and supplies diagnostic provenance.
|
||||
If a structured value violates a Decodal constraint, the diagnostic identifies the import key, logical value path, and source span of the violated DCDL constraint.
|
||||
The loader reports parse errors and positions in the external format itself.
|
||||
|
||||
## Shared host environment
|
||||
|
||||
`HostEnvironment` combines loader construction and global-binding setup.
|
||||
Passing the same environment to the runtime, language service, and LSP keeps runtime and editor evaluation rules aligned.
|
||||
|
||||
```rust
|
||||
use decodal::{Engine, HostEnvironment};
|
||||
|
||||
struct AppEnvironment;
|
||||
|
||||
impl HostEnvironment for AppEnvironment {
|
||||
type Loader = ContentLoader;
|
||||
|
||||
fn create_loader(&self) -> Self::Loader {
|
||||
ContentLoader::new()
|
||||
}
|
||||
|
||||
fn configure_engine(
|
||||
&self,
|
||||
engine: &mut Engine<Self::Loader>,
|
||||
) -> decodal::Result<()> {
|
||||
engine.bind_global("Site", site_schema())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Construct transport-independent tooling with `decodal_language_service::LanguageService::new(&environment)`.
|
||||
For LSP integration, implement `decodal_lsp::LspEnvironment` and start the server from an environment factory.
|
||||
|
||||
```rust
|
||||
use decodal_lsp::{LspEnvironment, run_stdio};
|
||||
|
||||
impl LspEnvironment for AppEnvironment {}
|
||||
|
||||
run_stdio(|initialize| {
|
||||
let _ = initialize;
|
||||
Ok(AppEnvironment)
|
||||
})?;
|
||||
```
|
||||
|
||||
The factory receives the client's `InitializeParams` once, before the server responds with its capabilities.
|
||||
Use the `LspEnvironment` document lifecycle hooks to reflect unsaved DCDL, Markdown, and other resources in a host-owned overlay.
|
||||
|
||||
## JavaScript and browser environments
|
||||
|
||||
The `DecodalLanguageService` in `decodal-wasm` does not assume a filesystem or virtual project.
|
||||
JavaScript supplies `globals`, `loadImport`, and `completeImport`.
|
||||
|
||||
```js
|
||||
import init, { DecodalLanguageService } from 'decodal-wasm';
|
||||
|
||||
await init();
|
||||
|
||||
const files = {
|
||||
'schema.dcdl': 'Server = { port = Int; };',
|
||||
};
|
||||
|
||||
const service = new DecodalLanguageService({
|
||||
globals: {
|
||||
App: {
|
||||
enabled: { $decodal: 'Bool', default: true },
|
||||
},
|
||||
},
|
||||
loadImport(_currentKey, specifier) {
|
||||
const key = specifier.replace(/^\.\//, '');
|
||||
return { kind: 'source', key, name: key, source: files[key] };
|
||||
},
|
||||
completeImport() {
|
||||
return ['./schema.dcdl'];
|
||||
},
|
||||
});
|
||||
|
||||
const result = service.evaluate('main.dcdl', 'main.dcdl', 'App');
|
||||
```
|
||||
|
||||
JavaScript primitives, arrays, and plain objects become concrete values.
|
||||
Use `$decodal` descriptors for ranges:
|
||||
|
||||
- `{ $decodal: 'String' | 'Int' | 'Float' | 'Bool' }`
|
||||
- `{ $decodal: 'Unknown' }`
|
||||
- `{ $decodal: 'Array', item }`
|
||||
- `{ $decodal: 'Map', value }`
|
||||
- `{ $decodal: 'Object', fields, rest }`
|
||||
- `{ $decodal: 'Range', constraints, default }`
|
||||
|
||||
Import callbacks are synchronous.
|
||||
Preload or cache asynchronous inputs such as network resources before evaluation.
|
||||
@@ -0,0 +1,42 @@
|
||||
# Decodal Manual
|
||||
|
||||
This manual describes the Decodal language and the public APIs for embedding it in applications.
|
||||
Decodal is the project name for the Deferred Constraint Data Language, abbreviated DCDL.
|
||||
|
||||
## Contents
|
||||
|
||||
1. [Introduction](./introduction.md)
|
||||
2. [Language Specification](./language/index.md)
|
||||
1. [Lexical Structure and Syntax](./language/syntax.md)
|
||||
2. [Grammar](./language/grammar.md)
|
||||
3. [Value](./language/value/index.md)
|
||||
1. [String](./language/value/string.md)
|
||||
2. [Int](./language/value/int.md)
|
||||
3. [Float](./language/value/float.md)
|
||||
4. [Bool](./language/value/bool.md)
|
||||
4. [Expression](./language/expression/index.md)
|
||||
1. [Literal](./language/expression/literal.md)
|
||||
2. [Identifier](./language/expression/identifier.md)
|
||||
3. [Path Reference](./language/expression/path-reference.md)
|
||||
4. [Object](./language/expression/object.md)
|
||||
5. [Array](./language/expression/array.md)
|
||||
6. [Function](./language/expression/function.md)
|
||||
7. [Function Call](./language/expression/function-call.md)
|
||||
8. [Let](./language/expression/let.md)
|
||||
9. [Match](./language/expression/match.md)
|
||||
10. [Import](./language/expression/import.md)
|
||||
11. [Composition](./language/expression/composition.md)
|
||||
12. [Range Refinement](./language/expression/ascription.md)
|
||||
13. [Default](./language/expression/default.md)
|
||||
14. [Arithmetic](./language/expression/arithmetic.md)
|
||||
15. [Logical and Comparison](./language/expression/logical-and-comparison.md)
|
||||
5. [Constraints and Defaults](./language/constraints-and-defaults.md)
|
||||
6. [Operators](./language/operators.md)
|
||||
7. [Functions](./language/functions.md)
|
||||
8. [Modules and Imports](./language/modules-and-imports.md)
|
||||
9. [Evaluation Semantics](./language/evaluation.md)
|
||||
10. [Materialization and Errors](./language/materialization-and-errors.md)
|
||||
11. [Naming](./language/naming.md)
|
||||
12. [Examples](./language/examples.md)
|
||||
3. [Embedding](./embedding.md)
|
||||
4. [Packages and Integrations](./components.md)
|
||||
@@ -0,0 +1,54 @@
|
||||
# Introduction
|
||||
|
||||
Decodal is the **Deferred Constraint Data Language**, abbreviated **DCDL**.
|
||||
Its source files use the `.dcdl` extension.
|
||||
|
||||
Decodal is a language for describing configuration values, schemas, constraints, and derived configuration in one expression system, producing validated structured data.
|
||||
|
||||
## Purpose
|
||||
|
||||
In conventional configuration systems, values, schemas, defaults, derived settings, and validation are often handled by separate mechanisms.
|
||||
Decodal treats them as composable expressions.
|
||||
|
||||
```dcdl
|
||||
Port = Int & >= 1 & <= 65535;
|
||||
|
||||
Service = {
|
||||
host = String;
|
||||
port = Port default 8080;
|
||||
};
|
||||
|
||||
Config = {
|
||||
host = "127.0.0.1";
|
||||
port = 8000;
|
||||
} as Service;
|
||||
```
|
||||
|
||||
`Service` describes a range of accepted values.
|
||||
Each value in `Config` is validated against `Service`, while ranges that exist only in `Service` remain abstract in the result.
|
||||
During materialization, explicit values take precedence and `default` supplies values for ranges that remain unspecified.
|
||||
|
||||
## Core concepts
|
||||
|
||||
- Values and constraints are expressions that can be referenced and composed in the same way.
|
||||
- `&` performs symmetric composition and preserves constraints from both sides.
|
||||
- `as` verifies that the left side is more specific and narrower than the right side, then combines them.
|
||||
- `//` performs a right-biased structural patch, including overrides.
|
||||
- `default` is not a constraint. It is a fallback selected only during materialization.
|
||||
- Object fields, function arguments, and imports are evaluated when they are needed.
|
||||
- `Unknown` is the top range that accepts any concrete value, but it cannot be materialized while it remains abstract.
|
||||
|
||||
## Scope
|
||||
|
||||
Decodal is specialized for describing configuration, schemas, constraints, and derived data.
|
||||
General-purpose state mutation, time, randomness, network access, and `try / catch` for treating errors as values are not language features.
|
||||
The host provides filesystem and external-format access. Given the same source, import results, and global bindings, Decodal evaluation is deterministic.
|
||||
|
||||
Regular-expression constraints depend on the runtime's `regex` feature.
|
||||
Decodal does not provide advanced type inference, exhaustiveness checking for `match`, or static detection of unreachable branches.
|
||||
|
||||
## Continue reading
|
||||
|
||||
- [Language Specification](./language/index.md): syntax, values, expressions, constraints, evaluation, and materialization.
|
||||
- [Embedding](./embedding.md): Rust and JavaScript execution, host environments, structured imports, and language services.
|
||||
- [Packages and Integrations](./components.md): crates and JavaScript packages for each use case.
|
||||
@@ -0,0 +1,81 @@
|
||||
# Packages and Integrations
|
||||
|
||||
Decodal は実行、ホスト組み込み、言語サービス、エディタ統合を用途別の package として提供する。
|
||||
|
||||
## Rust
|
||||
|
||||
### `decodal`
|
||||
|
||||
Rust アプリケーション向けの runtime と embedding API である。
|
||||
source の parse・evaluate・materialize、host global、source または structured value の import を扱う。
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
decodal = "0.4"
|
||||
```
|
||||
|
||||
Rust struct から Decodal schema と decoder を生成する場合は `derive` feature を有効にする。
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
decodal = { version = "0.4", features = ["derive"] }
|
||||
```
|
||||
|
||||
正規表現制約を検証する場合は `regex` feature を有効にする。
|
||||
この feature は `std` を必要とする。
|
||||
|
||||
### `decodal-language-service`
|
||||
|
||||
transport に依存しない semantic evaluation と completion を提供する。
|
||||
アプリケーションの `HostEnvironment` をそのまま受け取るため、本番評価と編集時評価で global bindings と import 規則を共有できる。
|
||||
|
||||
### `decodal-lsp`
|
||||
|
||||
language service を Language Server Protocol に接続する。
|
||||
stdio server のほか、client の `InitializeParams` からアプリケーション固有の environment を構築する library API を提供する。
|
||||
|
||||
### `decodal-language-tools`
|
||||
|
||||
source formatter など、source text を扱う共通ツールを提供する。
|
||||
formatter は Rust、LSP、WebAssembly の各統合から同じ実装を利用できる。
|
||||
|
||||
## JavaScript and WebAssembly
|
||||
|
||||
### `decodal-wasm`
|
||||
|
||||
browser と WebAssembly 対応 runtime 向けの evaluator と language service である。
|
||||
|
||||
```sh
|
||||
npm install decodal-wasm
|
||||
```
|
||||
|
||||
JSR では `@hare/decodal-wasm` として提供される。
|
||||
`DecodalLanguageService` に `globals`、`loadImport`、`completeImport` を渡すことで、JavaScript が所有する environment を評価と補完で共有できる。
|
||||
|
||||
### `decodal-codemirror`
|
||||
|
||||
CodeMirror 6 向けの language support である。
|
||||
syntax highlighting、folding、indentation、Decodal formatter との統合を提供する。
|
||||
|
||||
```sh
|
||||
npm install decodal-codemirror
|
||||
```
|
||||
|
||||
JSR では `@hare/decodal-codemirror` として提供される。
|
||||
semantic evaluation と completion が必要な場合は `decodal-wasm` の language service と組み合わせる。
|
||||
|
||||
## Editor syntax
|
||||
|
||||
Tree-sitter grammar は、Tree-sitter を採用するエディタで構文解析と highlighting を行うための統合である。
|
||||
Decodal runtime、formatter、LSP の利用には Tree-sitter は必要ない。
|
||||
|
||||
用途ごとの選択は次の通りである。
|
||||
|
||||
- Rust での実行と組み込み: `decodal`
|
||||
- browser での実行と semantic tooling: `decodal-wasm`
|
||||
- CodeMirror 6: `decodal-codemirror`
|
||||
- transport 非依存の Rust language service: `decodal-language-service`
|
||||
- 一般的な editor client: `decodal-lsp`
|
||||
- Tree-sitter 採用 editor の syntax grammar: Tree-sitter integration
|
||||
|
||||
具体的な environment の構築方法は [Embedding](./embedding.md) を参照する。
|
||||
@@ -0,0 +1,193 @@
|
||||
# Embedding
|
||||
|
||||
Decodal runtime は filesystem、network、environment variable を直接読み込まない。
|
||||
ホストは global bindings と import の解決方法を注入し、評価結果を `Data` または application type として受け取る。
|
||||
|
||||
## Rust runtime
|
||||
|
||||
`Engine` は `ImportLoader` と global bindings を持つ。
|
||||
外部 resource を必要としない source には `EmptyLoader` を使える。
|
||||
|
||||
```rust
|
||||
use decodal::{EmptyLoader, Engine, Value};
|
||||
|
||||
let mut engine = Engine::new(EmptyLoader);
|
||||
engine.bind_global(
|
||||
"Service",
|
||||
Value::object([
|
||||
("name", Value::string_type()),
|
||||
("port", Value::int_type().gt(443).default_int(8443)?),
|
||||
("enabled", Value::bool_type().default_bool(true)?),
|
||||
]),
|
||||
)?;
|
||||
|
||||
let module = engine.add_root_source(
|
||||
"service.dcdl",
|
||||
"service.dcdl",
|
||||
r#"{ name = "api"; port = 9443; } as Service"#,
|
||||
)?;
|
||||
let value = engine.eval_module(module)?;
|
||||
let data = engine.materialize(&value)?;
|
||||
```
|
||||
|
||||
`Value` は concrete value と host-defined range の両方を表す公開型である。
|
||||
主な constructor は次の通りである。
|
||||
|
||||
- `Value::string`、`int`、`float`、`bool`、`array`、`object`: concrete value。
|
||||
- `Value::string_type`、`int_type`、`float_type`、`bool_type`: primitive range。
|
||||
- `Value::unknown`: 最上位 range `Unknown`。
|
||||
- `Value::array_of`: 全要素へ同じ range を適用する array range。
|
||||
- `Value::map_of`: 全 field value へ同じ range を適用する associative-array range。
|
||||
- `Value::object_with_rest`: named fields と残りの field range を持つ object。
|
||||
|
||||
## Typed Rust integration
|
||||
|
||||
`derive` feature の `Decodal` derive は、Rust struct から `DecodalSchema` と `DecodalDecode` を生成する。
|
||||
|
||||
```rust
|
||||
use decodal::{Decodal, DecodalDecode, DecodalSchema};
|
||||
|
||||
#[derive(Decodal)]
|
||||
struct Service {
|
||||
name: String,
|
||||
|
||||
#[decodal(gt = 443, default = 8443)]
|
||||
port: i64,
|
||||
|
||||
#[decodal(rename = "feature.enable", default = true)]
|
||||
feature_enabled: bool,
|
||||
}
|
||||
```
|
||||
|
||||
`DecodalSchema::decodal_schema()` は `Engine::bind_global` に渡せる `Value` を返す。
|
||||
materialize 済みの `Data` は `DecodalDecode::decodal_decode` で Rust type に変換できる。
|
||||
|
||||
追加 field を受け取る struct では、map field に `#[decodal(rest)]` を付ける。
|
||||
|
||||
```rust
|
||||
use std::collections::BTreeMap;
|
||||
use decodal::{Data, Decodal};
|
||||
|
||||
#[derive(Decodal)]
|
||||
struct OpenConfig {
|
||||
enabled: bool,
|
||||
|
||||
#[decodal(rest)]
|
||||
extra: BTreeMap<String, Data>,
|
||||
}
|
||||
```
|
||||
|
||||
`BTreeMap<String, Data>` は `...Unknown`、`BTreeMap<String, String>` は `...String` に対応する。
|
||||
named fields は rest map から除外される。
|
||||
`#[decodal(rest)]` は struct ごとに1つだけ指定でき、`rename`、`default`、field constraints とは併用できない。
|
||||
|
||||
## Imports
|
||||
|
||||
`ImportLoader::load` は `LoadedImport::Source` または `LoadedImport::Value` を返す。
|
||||
ホストは specifier の解決、filesystem や他の storage からの読み込み、content type の判定をすべて管理する。
|
||||
|
||||
`LoadedImport::Source` は DCDL source を返す。
|
||||
`LoadedImport::Value` は Markdown、JSON、TOML などをホスト独自の規則で構造化して返す用途に使える。
|
||||
|
||||
```text
|
||||
import "./post.md"
|
||||
-> host reads and parses Markdown
|
||||
-> LoadedImport::Value {
|
||||
key: "content/post.md",
|
||||
value: { frontmatter: {...}, body: "..." }
|
||||
}
|
||||
-> normal Decodal validation and materialization
|
||||
```
|
||||
|
||||
stable `key` は import の同一性と diagnostic の provenance に使われる。
|
||||
構造化した値が Decodal constraint に違反した場合、diagnostic は import key と logical value path、および違反した DCDL constraint の source span を示す。
|
||||
外部形式そのものの parse error と位置情報は loader が報告する。
|
||||
|
||||
## Shared host environment
|
||||
|
||||
`HostEnvironment` は loader の作成と global binding の設定を1つにまとめる。
|
||||
同じ environment を runtime、language service、LSP へ渡すことで、実行時と編集時の評価規則を一致させられる。
|
||||
|
||||
```rust
|
||||
use decodal::{Engine, HostEnvironment};
|
||||
|
||||
struct AppEnvironment;
|
||||
|
||||
impl HostEnvironment for AppEnvironment {
|
||||
type Loader = ContentLoader;
|
||||
|
||||
fn create_loader(&self) -> Self::Loader {
|
||||
ContentLoader::new()
|
||||
}
|
||||
|
||||
fn configure_engine(
|
||||
&self,
|
||||
engine: &mut Engine<Self::Loader>,
|
||||
) -> decodal::Result<()> {
|
||||
engine.bind_global("Site", site_schema())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
transport 非依存の tooling は `decodal_language_service::LanguageService::new(&environment)` で構築する。
|
||||
LSP integration では `decodal_lsp::LspEnvironment` を実装し、environment factory から server を起動する。
|
||||
|
||||
```rust
|
||||
use decodal_lsp::{LspEnvironment, run_stdio};
|
||||
|
||||
impl LspEnvironment for AppEnvironment {}
|
||||
|
||||
run_stdio(|initialize| {
|
||||
let _ = initialize;
|
||||
Ok(AppEnvironment)
|
||||
})?;
|
||||
```
|
||||
|
||||
factory は server capability の応答前に client の `InitializeParams` を一度受け取る。
|
||||
`LspEnvironment` の document lifecycle hooks を使うと、未保存の DCDL、Markdown、その他の resource を host-owned overlay に反映できる。
|
||||
|
||||
## JavaScript and browser environments
|
||||
|
||||
`decodal-wasm` の `DecodalLanguageService` は filesystem や仮想 project を仮定しない。
|
||||
JavaScript 側が `globals`、`loadImport`、`completeImport` を提供する。
|
||||
|
||||
```js
|
||||
import init, { DecodalLanguageService } from 'decodal-wasm';
|
||||
|
||||
await init();
|
||||
|
||||
const files = {
|
||||
'schema.dcdl': 'Server = { port = Int; };',
|
||||
};
|
||||
|
||||
const service = new DecodalLanguageService({
|
||||
globals: {
|
||||
App: {
|
||||
enabled: { $decodal: 'Bool', default: true },
|
||||
},
|
||||
},
|
||||
loadImport(_currentKey, specifier) {
|
||||
const key = specifier.replace(/^\.\//, '');
|
||||
return { kind: 'source', key, name: key, source: files[key] };
|
||||
},
|
||||
completeImport() {
|
||||
return ['./schema.dcdl'];
|
||||
},
|
||||
});
|
||||
|
||||
const result = service.evaluate('main.dcdl', 'main.dcdl', 'App');
|
||||
```
|
||||
|
||||
JavaScript の primitive、array、plain object は concrete value になる。
|
||||
range は `$decodal` descriptor で指定する。
|
||||
|
||||
- `{ $decodal: 'String' | 'Int' | 'Float' | 'Bool' }`
|
||||
- `{ $decodal: 'Unknown' }`
|
||||
- `{ $decodal: 'Array', item }`
|
||||
- `{ $decodal: 'Map', value }`
|
||||
- `{ $decodal: 'Object', fields, rest }`
|
||||
- `{ $decodal: 'Range', constraints, default }`
|
||||
|
||||
import callbacks は同期 API である。
|
||||
network resource などの非同期入力は、評価の前に preload または cache しておく。
|
||||
@@ -0,0 +1,42 @@
|
||||
# Decodal Manual
|
||||
|
||||
このマニュアルでは、Decodal の言語仕様と、アプリケーションへ組み込むための公開 API を説明する。
|
||||
Decodal は Deferred Constraint Data Language、略称 DCDL のプロジェクト名である。
|
||||
|
||||
## 目次
|
||||
|
||||
1. [Introduction](./introduction.md)
|
||||
2. [Language Specification](./language/index.md)
|
||||
1. [Lexical Structure and Syntax](./language/syntax.md)
|
||||
2. [Grammar](./language/grammar.md)
|
||||
3. [Value](./language/value/index.md)
|
||||
1. [String](./language/value/string.md)
|
||||
2. [Int](./language/value/int.md)
|
||||
3. [Float](./language/value/float.md)
|
||||
4. [Bool](./language/value/bool.md)
|
||||
4. [Expression](./language/expression/index.md)
|
||||
1. [Literal](./language/expression/literal.md)
|
||||
2. [Identifier](./language/expression/identifier.md)
|
||||
3. [Path Reference](./language/expression/path-reference.md)
|
||||
4. [Object](./language/expression/object.md)
|
||||
5. [Array](./language/expression/array.md)
|
||||
6. [Function](./language/expression/function.md)
|
||||
7. [Function Call](./language/expression/function-call.md)
|
||||
8. [Let](./language/expression/let.md)
|
||||
9. [Match](./language/expression/match.md)
|
||||
10. [Import](./language/expression/import.md)
|
||||
11. [Composition](./language/expression/composition.md)
|
||||
12. [Range Refinement](./language/expression/ascription.md)
|
||||
13. [Default](./language/expression/default.md)
|
||||
14. [Arithmetic](./language/expression/arithmetic.md)
|
||||
15. [Logical and Comparison](./language/expression/logical-and-comparison.md)
|
||||
5. [Constraints and Defaults](./language/constraints-and-defaults.md)
|
||||
6. [Operators](./language/operators.md)
|
||||
7. [Functions](./language/functions.md)
|
||||
8. [Modules and Imports](./language/modules-and-imports.md)
|
||||
9. [Evaluation Semantics](./language/evaluation.md)
|
||||
10. [Materialization and Errors](./language/materialization-and-errors.md)
|
||||
11. [Naming](./language/naming.md)
|
||||
12. [Examples](./language/examples.md)
|
||||
3. [Embedding](./embedding.md)
|
||||
4. [Packages and Integrations](./components.md)
|
||||
@@ -0,0 +1,54 @@
|
||||
# Introduction
|
||||
|
||||
Decodal は **Deferred Constraint Data Language**、略称 **DCDL** である。
|
||||
ファイル拡張子には `.dcdl` を使う。
|
||||
|
||||
Decodal は、設定値・スキーマ・制約・派生設定を同じ式体系で記述し、検証済みの構造化データを得るための言語である。
|
||||
|
||||
## 目的
|
||||
|
||||
一般的な設定システムでは、値、スキーマ、デフォルト値、派生設定、バリデーションが別々の仕組みになりやすい。
|
||||
Decodal では、それらを合成可能な式として扱う。
|
||||
|
||||
```dcdl
|
||||
Port = Int & >= 1 & <= 65535;
|
||||
|
||||
Service = {
|
||||
host = String;
|
||||
port = Port default 8080;
|
||||
};
|
||||
|
||||
Config = {
|
||||
host = "127.0.0.1";
|
||||
port = 8000;
|
||||
} as Service;
|
||||
```
|
||||
|
||||
`Service` は許容する値の範囲を表す。
|
||||
`Config` の各値は `Service` によって検証され、`Service` にだけ存在する範囲は抽象的なまま結果へ残る。
|
||||
materialize 時には明示値が優先され、値がない範囲には `default` が使われる。
|
||||
|
||||
## 中心概念
|
||||
|
||||
- 値と制約は同じ式として参照・合成できる。
|
||||
- `&` は両辺の制約を保つ対称な合成を行う。
|
||||
- `as` は左辺が右辺より具体的で狭いことを検証しながら合成する。
|
||||
- `//` は右辺優先の構造的な patch を行う。
|
||||
- `default` は制約ではなく、materialize 時にだけ選ばれる fallback である。
|
||||
- object field、関数引数、import は必要になった時点で評価される。
|
||||
- `Unknown` は任意の具体値を受け入れる最上位 range だが、抽象的なまま materialize はできない。
|
||||
|
||||
## 適用範囲
|
||||
|
||||
Decodal は、設定、スキーマ、制約、派生データの記述に特化している。
|
||||
汎用的な状態変更、時刻・乱数・ネットワークアクセス、例外を値として捕捉する `try / catch` は言語機能に含まれない。
|
||||
filesystem や外部形式の読み込みはホストが提供し、Decodal の評価は同じ source・import 結果・global bindings に対して決定的に動作する。
|
||||
|
||||
正規表現制約は利用する runtime の `regex` feature に依存する。
|
||||
高度な型推論、match の網羅性検査、到達不能分岐の静的検査は提供しない。
|
||||
|
||||
## 次に読む章
|
||||
|
||||
- [Language Specification](./language/index.md): 構文、値、式、制約、評価、materialization。
|
||||
- [Embedding](./embedding.md): Rust・JavaScript からの実行、host environment、structured import、language service。
|
||||
- [Packages and Integrations](./components.md): 用途ごとに選ぶ crate と JavaScript package。
|
||||
@@ -0,0 +1,163 @@
|
||||
# 制約と default
|
||||
|
||||
constraint は、値が満たすべき範囲を表す。
|
||||
|
||||
```dcdl
|
||||
Int
|
||||
String
|
||||
>= 1
|
||||
<= 65535
|
||||
/Hello! .*/
|
||||
```
|
||||
|
||||
constraint は `&` で合成できる。
|
||||
|
||||
```dcdl
|
||||
Port = Int & >= 1 & <= 65535;
|
||||
NarrowedPort = Port & > 443;
|
||||
```
|
||||
|
||||
合成結果は両辺を同時に満たす範囲になる。
|
||||
両立しない constraint は conflict になる。
|
||||
|
||||
```dcdl
|
||||
Int & String # conflict
|
||||
> 10 & < 5 # conflict
|
||||
Int & > 10 & < 11 # conflict: integer candidate does not exist
|
||||
```
|
||||
|
||||
## Primitive and comparison constraints
|
||||
|
||||
組み込みの primitive range は次の通りである。
|
||||
|
||||
```dcdl
|
||||
String
|
||||
Int
|
||||
Float
|
||||
Bool
|
||||
```
|
||||
|
||||
異なる primitive type を `&` で合成すると conflict になる。
|
||||
数値比較 constraint は上下限として合成され、空の範囲になる場合は conflict になる。
|
||||
|
||||
```dcdl
|
||||
Int & >= 1 & <= 65535 & > 443
|
||||
```
|
||||
|
||||
`Int` の比較 constraint は integer literal を使う。
|
||||
`Float` の比較 constraint は integer または float literal を使える。
|
||||
|
||||
## Unknown
|
||||
|
||||
`Unknown` はすべての Decodal value を含む最上位 range である。
|
||||
検査を無効化して具体値の情報を消す `Any` ではなく、値の範囲がまだ絞られていないことを表す。
|
||||
|
||||
```dcdl
|
||||
Int & Unknown # Int
|
||||
42 as Unknown # 42
|
||||
Unknown as Int # conflict
|
||||
```
|
||||
|
||||
`Unknown` 自体は concrete value を持たないため materialize できない。
|
||||
concrete value で絞り込むか `default` を指定する必要がある。
|
||||
|
||||
```dcdl
|
||||
Unknown default {}
|
||||
```
|
||||
|
||||
## Regex constraints
|
||||
|
||||
regex literal は string constraint である。
|
||||
|
||||
```dcdl
|
||||
Host = /^api-[0-9]+$/;
|
||||
```
|
||||
|
||||
複数の regex constraint を合成した場合、concrete string はすべてに一致する必要がある。
|
||||
|
||||
```dcdl
|
||||
String & /^a/ & /z$/
|
||||
```
|
||||
|
||||
regex constraint 同士の交差は合成時には判定されない。
|
||||
そのため、次の範囲は合成時には conflict にならず、concrete value の検証時に失敗する。
|
||||
|
||||
```dcdl
|
||||
String & /^a$/ & /^b$/
|
||||
```
|
||||
|
||||
Rust runtime では regex engine を `regex` feature で有効にする。
|
||||
feature が無効な場合、regex constraint の concrete value 検証は unsupported feature diagnostic になる。
|
||||
|
||||
## Array constraints
|
||||
|
||||
array constraint は `[...T]` と書き、すべての要素へ `T` を適用する。
|
||||
要素 constraint は必須である。
|
||||
|
||||
```dcdl
|
||||
Names = [...String];
|
||||
PositiveInts = [...(Int & > 0)];
|
||||
```
|
||||
|
||||
concrete array と合成した場合、各要素は `T` に対して `as` と同じ規則で絞り込まれる。
|
||||
空 array は任意の array constraint を満たす。
|
||||
|
||||
要素 constraint が object range の場合、右辺にだけある field は abstract のまま各要素へ残る。
|
||||
左辺にしかない field は右辺の field domain 外なので conflict になる。
|
||||
|
||||
## Associative-array and object rest constraints
|
||||
|
||||
associative-array constraint は `{...T}` と書き、object の任意の field value へ `T` を適用する。
|
||||
|
||||
```dcdl
|
||||
Services = {...{
|
||||
port = Int;
|
||||
enabled = Bool default true;
|
||||
}};
|
||||
```
|
||||
|
||||
key は列挙されず、空 object も許容される。
|
||||
named field を持つ object の末尾へ `...T` を書くと、列挙されていない field だけに `T` を適用できる。
|
||||
|
||||
```dcdl
|
||||
{
|
||||
enabled = Bool default true;
|
||||
...Unknown
|
||||
}
|
||||
```
|
||||
|
||||
rest constraint は field を生成しない。
|
||||
実在する追加 field の検証にだけ使われる。
|
||||
|
||||
## default
|
||||
|
||||
`default` は constraint ではない。
|
||||
materialize 時に concrete value がない場合だけ使われる fallback である。
|
||||
|
||||
```dcdl
|
||||
Port = Int & >= 1 & <= 65535;
|
||||
|
||||
Service = {
|
||||
port = Port default 8080;
|
||||
};
|
||||
```
|
||||
|
||||
明示値が合成された場合は明示値が使われ、`default` は評価されない。
|
||||
|
||||
```dcdl
|
||||
Config = {
|
||||
port = 9000;
|
||||
} as Service;
|
||||
```
|
||||
|
||||
明示値がない場合、materialize 時に `8080` が採用され、`Port` を満たすか検証される。
|
||||
|
||||
## default composition
|
||||
|
||||
- `&` で片方だけが `default` を持つ場合、その `default` は保持される。
|
||||
- `&` で両辺が異なる `default` を持つ場合は conflict になる。
|
||||
- constraint と concrete value の `&` が成功した場合、結果は concrete value になり `default` は残らない。
|
||||
- `//` は右辺優先なので、同じ field では右辺の値または `default` が左辺を置き換える。
|
||||
- `as` は右辺の `default` を左辺へ注入しない。ただし右辺にだけ存在する field は、その field が持つ `default` とともに abstract なまま結果へ残る。
|
||||
|
||||
`default` expression は materialize 時に必要になった時点で評価され、同じ range の constraint を満たす必要がある。
|
||||
@@ -0,0 +1,48 @@
|
||||
# 遅延評価
|
||||
|
||||
Decodal は値を必要になった時点で評価する。
|
||||
|
||||
## 遅延評価される値
|
||||
|
||||
次の値は参照または materialize されるまで評価されない。
|
||||
|
||||
- module の root と top-level field
|
||||
- object field
|
||||
- `let` binding
|
||||
- function argument
|
||||
- `default` expression
|
||||
- import 先の値
|
||||
|
||||
同じ binding を複数回参照した場合、その評価結果は再利用される。
|
||||
このため、未参照の field や function argument にある失敗は、値が必要になるまで発生しない。
|
||||
|
||||
```dcdl
|
||||
let
|
||||
safe = 42;
|
||||
unused = missing_name;
|
||||
in
|
||||
safe
|
||||
```
|
||||
|
||||
この式は `unused` を参照しないため `42` になる。
|
||||
|
||||
## 循環検出
|
||||
|
||||
評価中の値が自身へ再び依存した場合は cycle diagnostic になる。
|
||||
|
||||
```dcdl
|
||||
{
|
||||
a = b + 1;
|
||||
b = a + 1;
|
||||
}
|
||||
```
|
||||
|
||||
module や import の参照関係自体が循環していても、実際に評価される field の依存関係が循環していなければ評価できる。
|
||||
|
||||
## 評価と materialize
|
||||
|
||||
通常の評価結果には、constraint、`Unknown`、`default`、function などの abstract value が残り得る。
|
||||
外部へ concrete data として取り出すときに materialize を行う。
|
||||
|
||||
この分離により、schema を値として合成し、必要な field だけを評価し、`default` の選択を出力時まで遅らせられる。
|
||||
詳細は [materialize とエラー](./materialization-and-errors.md) を参照する。
|
||||
@@ -1,11 +1,11 @@
|
||||
# 例
|
||||
|
||||
この章には、仕様を説明するための例を置く。
|
||||
この章では、Decodal の主要な記法を組み合わせた例を示す。
|
||||
|
||||
## 基本的な設定スキーマ
|
||||
|
||||
```dcdl
|
||||
Host = IPv4Address;
|
||||
Host = String;
|
||||
|
||||
Port = Int & >= 1 & <= 65535;
|
||||
NarrowedPort = Port & > 443;
|
||||
@@ -144,23 +144,23 @@ Patched = Base // {
|
||||
## 循環 import
|
||||
|
||||
```dcdl
|
||||
# main.n
|
||||
# main.dcdl
|
||||
{
|
||||
schema = {
|
||||
hoge = String;
|
||||
};
|
||||
|
||||
result = (import ./func.n)(schema);
|
||||
result = (import "./func.dcdl")(schema);
|
||||
}
|
||||
```
|
||||
|
||||
```dcdl
|
||||
# func.n
|
||||
(input: (import ./main.n).schema) =>
|
||||
# func.dcdl
|
||||
(input: (import "./main.dcdl").schema) =>
|
||||
{
|
||||
# ...
|
||||
}
|
||||
```
|
||||
|
||||
`func.n` は `main.n` を import しているが、参照しているのは `main.schema` である。
|
||||
`func.dcdl` は `main.dcdl` を import しているが、参照しているのは `main.schema` である。
|
||||
`main.schema` が `main.result` に依存していなければ、この循環 import は成立する。
|
||||
@@ -0,0 +1,43 @@
|
||||
# 算術式
|
||||
|
||||
Decodal は具体的な数値に対する算術演算をサポートする。
|
||||
|
||||
```dcdl
|
||||
{
|
||||
workers = 2 + 2;
|
||||
timeout = 30.0 / 2;
|
||||
port = 8000 + 80;
|
||||
negative = -1;
|
||||
}
|
||||
```
|
||||
|
||||
## 演算子
|
||||
|
||||
- `+`: 加算
|
||||
- `-`: 減算
|
||||
- `*`: 乗算
|
||||
- `/`: 除算
|
||||
- 単項 `-`: 符号反転
|
||||
|
||||
`*` と `/` は `+` と `-` より高い優先順位を持つ。
|
||||
括弧を使ってグループ化を明示できる。
|
||||
|
||||
```dcdl
|
||||
2 + 3 * 4 # 14
|
||||
(2 + 3) * 4 # 20
|
||||
```
|
||||
|
||||
## 数値の扱い
|
||||
|
||||
算術演算には具体的な `Int` または `Float` の operand が必要である。
|
||||
`Int + Int`、`Int - Int`、`Int * Int` は、overflow が発生しない場合に `Int` を返す。
|
||||
`Int` と `Float` が混在する演算は `Float` を返す。
|
||||
除算は常に `Float` を返す。
|
||||
|
||||
ゼロ除算と整数 overflow は評価エラーになる。
|
||||
|
||||
算術式は、default や数値制約を含め、具体的な数値式が必要な任意の場所で使える。
|
||||
|
||||
```dcdl
|
||||
port = Int & > 4000 + 42 default 8080;
|
||||
```
|
||||
+1
-1
@@ -49,7 +49,7 @@ object の要素 range では左辺にしかない field がエラーになり
|
||||
要素制約のない抽象配列型は提供しない。
|
||||
旧来の `Array` primitive type は使用できず、`[...T]` の `T` は必須である。
|
||||
`[String]` は配列制約ではなく、未解決の `String` 制約を 1 要素に持つ concrete array になる。
|
||||
長さ制約、位置別の tuple 制約、unique 制約は現在サポートしない。
|
||||
配列制約は要素範囲だけを表す。長さ制約、位置別の tuple 制約、unique 制約は持たない。
|
||||
|
||||
## Array concat
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ primitive constraint や合成 constraint は通常どおり値を検証する
|
||||
右辺が concrete scalar または array literal の場合は、左辺も同じ値または同じ長さ・要素構造である必要がある。
|
||||
function は右辺の範囲として使用できない。
|
||||
|
||||
左辺は concrete value に限らない。処理系が包含を確認できる constraint 同士であれば abstract value も使用できる。primitive type、numeric bound、同一 regex / predicate、array/map の要素範囲は包含確認の対象になる。
|
||||
左辺は concrete value に限らない。包含関係を判定できる constraint 同士であれば abstract value も使用できる。primitive type、numeric bound、同一 regex / predicate、array/map の要素範囲は包含確認の対象になる。
|
||||
|
||||
関数 parameter の `name: range` も、引数が force された時点で `as` と同じ絞り込み規則を使う。
|
||||
|
||||
+1
-1
@@ -24,4 +24,4 @@ Patched = Base // {
|
||||
};
|
||||
```
|
||||
|
||||
詳細は [合成演算子](../operators.md) に置く。
|
||||
詳細は [合成演算子](../operators.md) を参照する。
|
||||
@@ -0,0 +1,15 @@
|
||||
# Default Expression
|
||||
|
||||
`default` expression は、明示値が存在しない場合に materialize 時に採用される fallback を指定する。
|
||||
|
||||
```dcdl
|
||||
port = Int default 8080;
|
||||
```
|
||||
|
||||
`default` は制約ではない。
|
||||
詳細は [制約と default](../constraints-and-defaults.md) を参照する。
|
||||
|
||||
## 評価
|
||||
|
||||
fallback expression は materialize 時に必要になった場合だけ評価される。
|
||||
明示値がある場合、`default` は評価も採用もされない。
|
||||
+2
-5
@@ -8,10 +8,7 @@ increment(41)
|
||||
|
||||
## 評価
|
||||
|
||||
引数は thunk として渡せる。
|
||||
関数本体内で引数が参照されたときに評価する。
|
||||
引数は関数本体から参照された時点で評価される。
|
||||
同じ引数を複数回参照した場合は評価結果が再利用される。
|
||||
parameter に range が指定されている場合、引数は `narrower as wider` と同じ規則で絞り込まれる。
|
||||
parameter 側だけにある field は abstract のまま残り、default はこの時点では選択されない。
|
||||
|
||||
関数呼び出し結果そのものはグローバルには memoize しない。
|
||||
フィールドに束縛された呼び出し結果は、そのフィールド thunk の評価結果として memoize される。
|
||||
+2
-2
@@ -6,9 +6,9 @@ function expression は、引数を受け取り式を返す値である。
|
||||
(value: Int) => value + 1
|
||||
```
|
||||
|
||||
関数仕様の詳細は [関数](../functions.md) に置く。
|
||||
関数仕様の詳細は [関数](../functions.md) を参照する。
|
||||
|
||||
## 評価
|
||||
|
||||
関数は定義時の環境を参照として保持する。
|
||||
関数は定義された lexical scope の bindings を参照する。
|
||||
関数本体は、関数値の生成時ではなく呼び出し時に評価される。
|
||||
@@ -0,0 +1,14 @@
|
||||
# Identifier Expression
|
||||
|
||||
identifier expression は、lexical scope に束縛された名前を参照する式である。
|
||||
|
||||
```dcdl
|
||||
Port
|
||||
MyConfig
|
||||
mkConfig
|
||||
```
|
||||
|
||||
## 評価
|
||||
|
||||
識別子は対応する binding の値を必要になった時点で評価する。
|
||||
束縛が存在しない場合は未定義識別子エラーになる。
|
||||
@@ -0,0 +1,14 @@
|
||||
# Import Expression
|
||||
|
||||
import expression は、ホストが解決した DCDL module または structured value を返す。
|
||||
|
||||
```dcdl
|
||||
import "./config.dcdl"
|
||||
```
|
||||
|
||||
specifier は string literal であり、path や resource name としての解釈はホストが定義する。
|
||||
詳しくは [モジュールと import](../modules-and-imports.md) を参照する。
|
||||
|
||||
## 評価
|
||||
|
||||
import 先は遅延評価され、参照されない field は評価されない。
|
||||
+1
-2
@@ -20,8 +20,7 @@ Expr
|
||||
├─ import
|
||||
├─ composition
|
||||
├─ range refinement (`as`)
|
||||
├─ default
|
||||
└─ string interpolation
|
||||
└─ default
|
||||
```
|
||||
|
||||
各式の個別仕様へのリンクは [Manual Index](../../index.md) に集約する。
|
||||
@@ -0,0 +1,16 @@
|
||||
# Let Expression
|
||||
|
||||
let expression は、ローカル束縛を作る。
|
||||
|
||||
```dcdl
|
||||
let
|
||||
base = 8000;
|
||||
offset = 80;
|
||||
in
|
||||
base + offset
|
||||
```
|
||||
|
||||
## 評価
|
||||
|
||||
binding は参照された時点で評価される。
|
||||
参照されない binding は評価されず、同じ binding を複数回参照した場合は評価結果が再利用される。
|
||||
@@ -0,0 +1,41 @@
|
||||
# 論理式と比較式
|
||||
|
||||
Decodal は具体的な `Bool` に対する論理演算と、具体的な scalar value に対する比較をサポートする。
|
||||
|
||||
```dcdl
|
||||
{
|
||||
is_prod = env == "prod";
|
||||
high_port = port > 9000;
|
||||
enabled = is_prod && high_port;
|
||||
disabled = !enabled;
|
||||
}
|
||||
```
|
||||
|
||||
## 論理演算子
|
||||
|
||||
- `!expr` は具体的な `Bool` を反転する。
|
||||
- `lhs && rhs` は論理積を返す。
|
||||
- `lhs || rhs` は論理和を返す。
|
||||
|
||||
`&&` と `||` は短絡評価され、右辺は必要な場合にだけ評価される。
|
||||
論理演算の operand は具体的な `Bool` へ評価される必要がある。
|
||||
|
||||
## 比較演算子
|
||||
|
||||
- `==`
|
||||
- `!=`
|
||||
- `<`
|
||||
- `<=`
|
||||
- `>`
|
||||
- `>=`
|
||||
|
||||
`==` と `!=` は、`String`、`Bool`、`Int`、`Float` の具体的な scalar value を比較する。
|
||||
`Int` と `Float` は数値として相互に比較できる。
|
||||
|
||||
順序比較演算子 `<`、`<=`、`>`、`>=` は、具体的な数値だけを比較する。
|
||||
これらは `> 443` のような prefix comparison constraint とは別のものである。
|
||||
|
||||
```dcdl
|
||||
port = Int & > 443 default 9443;
|
||||
is_high = port > 9000;
|
||||
```
|
||||
+2
-2
@@ -50,7 +50,7 @@ Services = {...{
|
||||
```
|
||||
|
||||
`{...T}` は key の集合を固定せず、すべての value が `T` を満たす object を表す。
|
||||
空 object も許容される。runtime と materialize 後の表現は通常の object と共通であり、別の map value variant は持たない。
|
||||
空 object も許容される。materialize 後は named fields を持つ object と同じ object data になる。
|
||||
|
||||
```dcdl
|
||||
services = {
|
||||
@@ -85,4 +85,4 @@ rest constraint は field を生成せず、materialize 時には実際に存在
|
||||
```
|
||||
|
||||
`...T` は object の末尾に一つだけ書ける。省略した object は閉じており、`as` の左辺に未宣言 field があればエラーになる。
|
||||
名前付き field を持たない `{...T}` は従来どおり抽象的な map constraint であり、単独でmaterializeするにはdefaultまたは具体値が必要になる。
|
||||
named field を持たない `{...T}` は abstract な map constraint であり、単独で materialize するには `default` または concrete value が必要になる。
|
||||
@@ -0,0 +1,41 @@
|
||||
# 関数
|
||||
|
||||
関数は値を受け取り、値を返す純粋な式である。
|
||||
|
||||
## 構文
|
||||
|
||||
```dcdl
|
||||
(value: Int) => value + 1
|
||||
```
|
||||
|
||||
関数呼び出しは通常の呼び出し構文で行う。
|
||||
|
||||
```dcdl
|
||||
let
|
||||
increment = (value: Int) => value + 1;
|
||||
in
|
||||
increment(41)
|
||||
```
|
||||
|
||||
複数の parameter を指定できる。
|
||||
|
||||
```dcdl
|
||||
(input_a: { hoge = Int & >= 0; }, input_b: { fuga = Int; }) =>
|
||||
{
|
||||
hoge = input_a.hoge;
|
||||
fuga = input_b.fuga;
|
||||
}
|
||||
```
|
||||
|
||||
parameter range は省略できる。
|
||||
range がある場合、引数は参照された時点で `as` と同じ規則によって検証・絞り込みされる。
|
||||
|
||||
## Scope and evaluation
|
||||
|
||||
関数は lexical scope を持ち、定義された場所の bindings を参照する。
|
||||
引数は遅延評価され、関数本体から参照されない引数は評価されない。
|
||||
再帰的な field または argument の依存は cycle diagnostic になる。
|
||||
|
||||
関数は中間値として参照・呼び出しできるが、data として materialize できない。
|
||||
未適用の関数が materialize 対象に残っている場合は diagnostic になる。
|
||||
関数値同士の等価性は定義されず、`&` で関数値同士を合成すると conflict になる。
|
||||
@@ -0,0 +1,113 @@
|
||||
# 文法
|
||||
|
||||
このページでは、Decodal ソーステキストの文法を定義する。
|
||||
|
||||
## 字句文法
|
||||
|
||||
```ebnf
|
||||
source_character = ? any Unicode scalar value ? ;
|
||||
newline = "\n" | "\r\n" | "\r" ;
|
||||
space = " " | "\t" | newline ;
|
||||
comment = "#" , { ? any character except newline ? } ;
|
||||
|
||||
digit = "0" … "9" ;
|
||||
letter = "A" … "Z" | "a" … "z" ;
|
||||
identifier = letter , { letter | digit | "_" } ;
|
||||
|
||||
integer = digit , { digit } ;
|
||||
float = digit , { digit } , "." , digit , { digit } ;
|
||||
|
||||
string = '"' , { string_character | escape } , '"' ;
|
||||
string_character = ? any character except '"', "\\", or newline ? ;
|
||||
escape = "\\" , source_character ;
|
||||
|
||||
regex = "/" , regex_character , { regex_character } , "/" ;
|
||||
regex_character = escape | ? any character except "/", "\\", or newline ? ;
|
||||
```
|
||||
|
||||
空白とコメントは token を区切り、それ以外では parser に無視される。
|
||||
|
||||
## 構文文法
|
||||
|
||||
```ebnf
|
||||
module = { statement } ;
|
||||
statement = field_definition , [ ";" ]
|
||||
| expression , [ ";" ] ;
|
||||
|
||||
expression = as_expression ;
|
||||
|
||||
as_expression = default_expression , { "as" , default_expression } ;
|
||||
default_expression = patch_expression , [ "default" , default_expression ] ;
|
||||
patch_expression = compose_expression , { "//" , compose_expression } ;
|
||||
compose_expression = logical_or_expression , { "&" , logical_or_expression } ;
|
||||
|
||||
logical_or_expression = logical_and_expression , { "||" , logical_and_expression } ;
|
||||
logical_and_expression = comparison_expression , { "&&" , comparison_expression } ;
|
||||
comparison_expression = concat_expression , [ comparison_operator , concat_expression ] ;
|
||||
concat_expression = additive_expression , { "++" , additive_expression } ;
|
||||
additive_expression = multiplicative_expression , { ( "+" | "-" ) , multiplicative_expression } ;
|
||||
multiplicative_expression = unary_expression , { ( "*" | "/" ) , unary_expression } ;
|
||||
|
||||
unary_expression = [ "!" | "-" ] , postfix_expression ;
|
||||
postfix_expression = primary_expression , { call_suffix | path_suffix } ;
|
||||
call_suffix = "(" , [ argument_list ] , ")" ;
|
||||
path_suffix = "." , identifier ;
|
||||
|
||||
primary_expression = literal
|
||||
| identifier
|
||||
| comparison_constraint
|
||||
| map_constraint
|
||||
| object
|
||||
| array_constraint
|
||||
| array
|
||||
| let_expression
|
||||
| function_expression
|
||||
| match_expression
|
||||
| import_expression
|
||||
| "(" , expression , ")" ;
|
||||
|
||||
literal = string | integer | float | "true" | "false" | regex ;
|
||||
comparison_operator = "==" | "!=" | "<" | "<=" | ">" | ">=" ;
|
||||
comparison_constraint = ( "<" | "<=" | ">" | ">=" ) , expression ;
|
||||
|
||||
object = "{" , [ field_definition , { ";" , field_definition }
|
||||
, [ ";" , object_rest ] , [ ";" ] ] , "}" ;
|
||||
object_rest = "..." , expression ;
|
||||
map_constraint = "{" , "..." , expression , "}" ;
|
||||
field_definition = field_path , "=" , expression ;
|
||||
field_path = identifier , { "." , identifier } ;
|
||||
|
||||
array = "[" , [ expression , { "," , expression } , [ "," ] ] , "]" ;
|
||||
array_constraint = "[" , "..." , expression , [ "," ] , "]" ;
|
||||
|
||||
let_expression = "let" , { field_definition , ";" } , "in" , expression ;
|
||||
function_expression = "(" , [ parameter_list ] , ")" , "=>" , expression ;
|
||||
parameter_list = parameter , { "," , parameter } , [ "," ] ;
|
||||
parameter = identifier , [ ":" , expression ] ;
|
||||
|
||||
match_expression = "match" , expression , "{" , [ match_arm , { ";" , match_arm } , [ ";" ] ] , "}" ;
|
||||
match_arm = pattern , ":" , expression ;
|
||||
pattern = "_" | expression ;
|
||||
|
||||
import_expression = "import" , string ;
|
||||
argument_list = expression , { "," , expression } , [ "," ] ;
|
||||
```
|
||||
|
||||
## 優先順位
|
||||
|
||||
優先順位は高い順に以下の通りである。
|
||||
|
||||
1. 関数呼び出しとフィールドのパス参照
|
||||
2. 単項 `!` と `-`
|
||||
3. `*` と `/`
|
||||
4. `+` と `-`
|
||||
5. `++`
|
||||
6. `==`、`!=`、`<`、`<=`、`>`、`>=`
|
||||
7. `&&`
|
||||
8. `||`
|
||||
9. `&`
|
||||
10. `//`
|
||||
11. `default`
|
||||
12. `as`
|
||||
|
||||
二項演算子は左結合だが、`default` だけは右結合である。
|
||||
@@ -0,0 +1,12 @@
|
||||
# 言語仕様
|
||||
|
||||
この章では、Decodal source の構文と評価結果を決める規則を説明する。
|
||||
|
||||
- syntax と grammar
|
||||
- primitive、object、array、function
|
||||
- constraint、`Unknown`、`default`
|
||||
- `&`、`//`、`as` とその他の operators
|
||||
- module、import、lazy evaluation
|
||||
- materialization と diagnostics
|
||||
|
||||
章の一覧は [Manual Index](../index.md) を参照する。
|
||||
@@ -0,0 +1,73 @@
|
||||
# materialize とエラー
|
||||
|
||||
通常の評価結果には constraint、`default`、function などが残り得る。
|
||||
materialize は評価結果を外部へ渡せる concrete data に変換する。
|
||||
|
||||
## materialize の規則
|
||||
|
||||
materialize は次の処理を行う。
|
||||
|
||||
- 必要な field を評価する。
|
||||
- 明示値のない abstract range に `default` を適用する。
|
||||
- concrete value と採用した `default` が constraint を満たすか検証する。
|
||||
- `default` のない `Unknown` や他の未解決 range を拒否する。
|
||||
- 未適用の function など、data に変換できない値を拒否する。
|
||||
|
||||
```dcdl
|
||||
Service = {
|
||||
host = String;
|
||||
port = Int default 8080;
|
||||
};
|
||||
```
|
||||
|
||||
`Service` をそのまま materialize すると、`host` に concrete value も `default` もないため失敗する。
|
||||
|
||||
```dcdl
|
||||
Config = {
|
||||
host = "localhost";
|
||||
} as Service;
|
||||
```
|
||||
|
||||
`Config` を materialize すると次の data になる。
|
||||
|
||||
```dcdl
|
||||
{
|
||||
host = "localhost";
|
||||
port = 8080;
|
||||
}
|
||||
```
|
||||
|
||||
明示値がある field では `default` は採用されない。
|
||||
|
||||
## Diagnostics
|
||||
|
||||
エラーは通常の値ではなく diagnostic として返される。
|
||||
式は diagnostic の種類や内容に基づいて分岐できない。
|
||||
|
||||
代表的な diagnostic は次の通りである。
|
||||
|
||||
- syntax error
|
||||
- unresolved identifier または field
|
||||
- type mismatch と constraint violation
|
||||
- `&` または `default` の conflict
|
||||
- cycle dependency
|
||||
- import failure
|
||||
- match failure
|
||||
- materialization failure
|
||||
|
||||
diagnostic は問題のある DCDL source span を示す。
|
||||
複数の式が conflict した場合は、関係する field、constraint、value、`default` の位置も示される。
|
||||
structured import の値に source span がない場合は、host が返した stable key と logical value path が示される。
|
||||
|
||||
## Fallback
|
||||
|
||||
`match` に fallback arm がなく、どの arm にも一致しない場合は diagnostic になる。
|
||||
|
||||
```dcdl
|
||||
match value {
|
||||
>= 10: "large";
|
||||
}
|
||||
```
|
||||
|
||||
Decodal は diagnostic を捕捉する汎用 `try / catch`、optional import、optional field access を提供しない。
|
||||
値がない場合の fallback は `default`、有限の値分岐は `match` で表現する。
|
||||
@@ -0,0 +1,88 @@
|
||||
# モジュールと import
|
||||
|
||||
`import` はホストが解決した DCDL module または structured value を返す。
|
||||
|
||||
## 構文
|
||||
|
||||
```dcdl
|
||||
import "./config.dcdl"
|
||||
```
|
||||
|
||||
import specifier は string literal である。
|
||||
specifier が path、URL、resource name のどれを表すかはホストが決める。
|
||||
|
||||
## モジュール
|
||||
|
||||
top-level に field 定義列を書いた module は recursive module scope を作る。
|
||||
top-level field は同じ module の他の top-level field から identifier として参照できる。
|
||||
|
||||
```dcdl
|
||||
schema = {
|
||||
name = String;
|
||||
};
|
||||
|
||||
result = schema;
|
||||
```
|
||||
|
||||
通常の object literal の field は sibling field を identifier として暗黙参照しない。
|
||||
object 内の値を参照する場合は、外側で束縛された値または明示的な path reference を使う。
|
||||
|
||||
module とその field は遅延評価される。
|
||||
import した module の未参照 field は評価されない。
|
||||
|
||||
## Host-defined resolution
|
||||
|
||||
Decodal は import specifier に対する filesystem や network の規則を定義しない。
|
||||
ホストが import 元の module と specifier を受け取り、次のどちらかを返す。
|
||||
|
||||
- DCDL source
|
||||
- ホストが構築した structured value
|
||||
|
||||
後者を使うと、Markdown、JSON、TOML などをホスト独自の規則で構造化し、通常の Decodal value として扱える。
|
||||
|
||||
```dcdl
|
||||
Post = {
|
||||
frontmatter = {
|
||||
title = String;
|
||||
draft = Bool default false;
|
||||
};
|
||||
body = String;
|
||||
};
|
||||
|
||||
post = (import "./hello.md") as Post;
|
||||
```
|
||||
|
||||
structured value も path reference、composition、constraint validation、materialization では DCDL source 由来の値と同じ規則に従う。
|
||||
loader API と diagnostic provenance は [Embedding](../embedding.md#imports) を参照する。
|
||||
|
||||
## 循環 import
|
||||
|
||||
module 間に循環参照があっても、実際に評価される field の依存関係が循環していなければ成功する。
|
||||
|
||||
```dcdl
|
||||
# main.dcdl
|
||||
schema = {
|
||||
name = String;
|
||||
};
|
||||
|
||||
result = (import "./func.dcdl")(schema);
|
||||
```
|
||||
|
||||
```dcdl
|
||||
# func.dcdl
|
||||
(input: (import "./main.dcdl").schema) => input
|
||||
```
|
||||
|
||||
この例で `func.dcdl` は `main.dcdl` を import するが、参照する `schema` は `result` に依存しないため評価できる。
|
||||
評価中の同じ field へ再び到達した場合は循環依存の diagnostic になる。
|
||||
|
||||
## import の失敗
|
||||
|
||||
次の状態は import failure になる。
|
||||
|
||||
- ホストが specifier を解決できない。
|
||||
- resource を読み込めない。
|
||||
- DCDL source の構文解析に失敗する。
|
||||
- 必要な import 先の値を評価できない。
|
||||
- structured value の読み込みまたは変換に失敗する。
|
||||
- 評価対象の依存関係が循環する。
|
||||
@@ -0,0 +1,17 @@
|
||||
# 命名規約
|
||||
|
||||
identifier の大文字・小文字に言語上の意味はない。
|
||||
値、constraint、schema、派生設定はいずれも同じ式として扱われる。
|
||||
|
||||
読みやすさのため、次の命名を推奨する。
|
||||
|
||||
- object value: `lower_snake`
|
||||
- function: `lowerCamel`
|
||||
- primitive、schema、抽象的な constraint: `UpperCamel`
|
||||
|
||||
```dcdl
|
||||
Port = Int & >= 1;
|
||||
Service = { port = Port; };
|
||||
service = { port = 8080; } as Service;
|
||||
mkService = (port: Port) => { port = port; };
|
||||
```
|
||||
@@ -58,7 +58,7 @@ host = "127.0.0.1"; # trailing comment
|
||||
## 識別子
|
||||
|
||||
識別子は ASCII 英字で始まり、ASCII 英数字または `_` を続けられる。
|
||||
慣習としては `lower_snake`、`lowerCamel`、`UpperCamel` を使える想定とする。
|
||||
命名規則には `lower_snake`、`lowerCamel`、`UpperCamel` を使用できる。
|
||||
|
||||
```dcdl
|
||||
my_config
|
||||
+1
-1
@@ -12,7 +12,7 @@ enable = Bool default true;
|
||||
tags = [...String];
|
||||
```
|
||||
|
||||
現在の primitive type は `String`、`Int`、`Float`、`Bool` である。
|
||||
primitive type は `String`、`Int`、`Float`、`Bool` である。
|
||||
`Unknown` はprimitive typeではなく、すべてのDecodal値を含む最上位の抽象rangeである。具体値またはdefaultがない `Unknown` はmaterializeできない。
|
||||
配列は primitive type ではなく、必須の要素制約を持つ `[...T]` で表現する。
|
||||
各型の個別仕様へのリンクは [Manual Index](../../index.md) に集約する。
|
||||
+9
@@ -2,6 +2,15 @@
|
||||
|
||||
`String` は文字列値を表す primitive type constraint である。
|
||||
|
||||
文字列リテラルは `"` で囲む。
|
||||
|
||||
```dcdl
|
||||
"hello"
|
||||
"line 1\nline 2"
|
||||
```
|
||||
|
||||
文字列内の `$` や `{}` に特別な意味はなく、文字列補間は行わない。
|
||||
|
||||
## 例
|
||||
|
||||
```dcdl
|
||||
@@ -0,0 +1,163 @@
|
||||
# Constraints and Defaults
|
||||
|
||||
A constraint describes a range that a value must satisfy.
|
||||
|
||||
```dcdl
|
||||
Int
|
||||
String
|
||||
>= 1
|
||||
<= 65535
|
||||
/Hello! .*/
|
||||
```
|
||||
|
||||
Constraints can be composed with `&`.
|
||||
|
||||
```dcdl
|
||||
Port = Int & >= 1 & <= 65535;
|
||||
NarrowedPort = Port & > 443;
|
||||
```
|
||||
|
||||
The result is the range that satisfies both sides.
|
||||
Incompatible constraints produce a conflict.
|
||||
|
||||
```dcdl
|
||||
Int & String # conflict
|
||||
> 10 & < 5 # conflict
|
||||
Int & > 10 & < 11 # conflict: integer candidate does not exist
|
||||
```
|
||||
|
||||
## Primitive and comparison constraints
|
||||
|
||||
The built-in primitive ranges are:
|
||||
|
||||
```dcdl
|
||||
String
|
||||
Int
|
||||
Float
|
||||
Bool
|
||||
```
|
||||
|
||||
Composing different primitive types with `&` produces a conflict.
|
||||
Numeric comparison constraints combine as bounds and conflict if they form an empty range.
|
||||
|
||||
```dcdl
|
||||
Int & >= 1 & <= 65535 & > 443
|
||||
```
|
||||
|
||||
Comparison constraints for `Int` use integer literals.
|
||||
Comparison constraints for `Float` may use either integer or float literals.
|
||||
|
||||
## Unknown
|
||||
|
||||
`Unknown` is the top range containing every Decodal value.
|
||||
It is not an `Any` that disables checking and erases concrete information; it means that the value's range has not yet been narrowed.
|
||||
|
||||
```dcdl
|
||||
Int & Unknown # Int
|
||||
42 as Unknown # 42
|
||||
Unknown as Int # conflict
|
||||
```
|
||||
|
||||
`Unknown` has no concrete value of its own and therefore cannot be materialized.
|
||||
It must be narrowed by a concrete value or given a `default`.
|
||||
|
||||
```dcdl
|
||||
Unknown default {}
|
||||
```
|
||||
|
||||
## Regex constraints
|
||||
|
||||
A regex literal is a string constraint.
|
||||
|
||||
```dcdl
|
||||
Host = /^api-[0-9]+$/;
|
||||
```
|
||||
|
||||
When several regex constraints are composed, a concrete string must match all of them.
|
||||
|
||||
```dcdl
|
||||
String & /^a/ & /z$/
|
||||
```
|
||||
|
||||
The intersection of regex constraints is not determined during composition.
|
||||
The following range therefore does not conflict when composed, but fails when a concrete value is validated.
|
||||
|
||||
```dcdl
|
||||
String & /^a$/ & /^b$/
|
||||
```
|
||||
|
||||
In the Rust runtime, enable the regex engine with the `regex` feature.
|
||||
Without that feature, validating a regex constraint against a concrete value produces an unsupported-feature diagnostic.
|
||||
|
||||
## Array constraints
|
||||
|
||||
Write an array constraint as `[...T]`; it applies `T` to every element.
|
||||
The element constraint is required.
|
||||
|
||||
```dcdl
|
||||
Names = [...String];
|
||||
PositiveInts = [...(Int & > 0)];
|
||||
```
|
||||
|
||||
When composed with a concrete array, each element is refined against `T` using the same rules as `as`.
|
||||
An empty array satisfies every array constraint.
|
||||
|
||||
If the element constraint is an object range, fields present only on the right remain abstract in every element.
|
||||
A field present only on the left is outside the right-hand field domain and produces a conflict.
|
||||
|
||||
## Associative-array and object rest constraints
|
||||
|
||||
Write an associative-array constraint as `{...T}`; it applies `T` to every field value in an object.
|
||||
|
||||
```dcdl
|
||||
Services = {...{
|
||||
port = Int;
|
||||
enabled = Bool default true;
|
||||
}};
|
||||
```
|
||||
|
||||
Keys are not enumerated, and an empty object is allowed.
|
||||
Writing `...T` at the end of an object with named fields applies `T` only to fields that were not named explicitly.
|
||||
|
||||
```dcdl
|
||||
{
|
||||
enabled = Bool default true;
|
||||
...Unknown
|
||||
}
|
||||
```
|
||||
|
||||
A rest constraint does not generate fields.
|
||||
It only validates additional fields that actually exist.
|
||||
|
||||
## default
|
||||
|
||||
`default` is not a constraint.
|
||||
It is a fallback used only when no concrete value exists during materialization.
|
||||
|
||||
```dcdl
|
||||
Port = Int & >= 1 & <= 65535;
|
||||
|
||||
Service = {
|
||||
port = Port default 8080;
|
||||
};
|
||||
```
|
||||
|
||||
When an explicit value has been composed into the range, that value is used and `default` is not evaluated.
|
||||
|
||||
```dcdl
|
||||
Config = {
|
||||
port = 9000;
|
||||
} as Service;
|
||||
```
|
||||
|
||||
Without an explicit value, materialization selects `8080` and verifies that it satisfies `Port`.
|
||||
|
||||
## Default composition
|
||||
|
||||
- If only one side of `&` has a `default`, that `default` is preserved.
|
||||
- If both sides of `&` have different defaults, they conflict.
|
||||
- If `&` between a constraint and a concrete value succeeds, the result is concrete and does not retain the `default`.
|
||||
- Because `//` is right-biased, the right-hand value or `default` replaces the left-hand one for the same field.
|
||||
- `as` does not inject a right-hand `default` into the left side. A field that exists only on the right, however, remains abstract in the result together with its `default`.
|
||||
|
||||
A `default` expression is evaluated when materialization requires it and must satisfy the constraints of the same range.
|
||||
@@ -0,0 +1,48 @@
|
||||
# Lazy Evaluation
|
||||
|
||||
Decodal evaluates values when they are needed.
|
||||
|
||||
## Lazily evaluated values
|
||||
|
||||
The following values are not evaluated until they are referenced or materialized:
|
||||
|
||||
- Module roots and top-level fields
|
||||
- Object fields
|
||||
- `let` bindings
|
||||
- Function arguments
|
||||
- `default` expressions
|
||||
- Imported values
|
||||
|
||||
When the same binding is referenced more than once, its evaluation result is reused.
|
||||
Consequently, a failure in an unreferenced field or function argument does not occur until that value is needed.
|
||||
|
||||
```dcdl
|
||||
let
|
||||
safe = 42;
|
||||
unused = missing_name;
|
||||
in
|
||||
safe
|
||||
```
|
||||
|
||||
This expression evaluates to `42` because it never references `unused`.
|
||||
|
||||
## Cycle detection
|
||||
|
||||
A cycle diagnostic is produced when a value being evaluated depends on itself again.
|
||||
|
||||
```dcdl
|
||||
{
|
||||
a = b + 1;
|
||||
b = a + 1;
|
||||
}
|
||||
```
|
||||
|
||||
Modules and imports may refer to one another cyclically as long as the dependency graph of the fields actually evaluated is not cyclic.
|
||||
|
||||
## Evaluation and materialization
|
||||
|
||||
A normal evaluation result may retain abstract values such as constraints, `Unknown`, `default`, and functions.
|
||||
Materialization converts that result to concrete data for use outside Decodal.
|
||||
|
||||
This separation allows schemas to be composed as values, only required fields to be evaluated, and selection of defaults to be deferred until output.
|
||||
See [Materialization and Errors](./materialization-and-errors.md) for details.
|
||||
@@ -0,0 +1,166 @@
|
||||
# Examples
|
||||
|
||||
This chapter combines Decodal's main constructs in complete examples.
|
||||
|
||||
## Basic configuration schema
|
||||
|
||||
```dcdl
|
||||
Host = String;
|
||||
|
||||
Port = Int & >= 1 & <= 65535;
|
||||
NarrowedPort = Port & > 443;
|
||||
|
||||
MyConfig = {
|
||||
host = Host;
|
||||
port = NarrowedPort default 8080;
|
||||
feature_hoge = {
|
||||
enable = Bool default true;
|
||||
fuga = Int default 10;
|
||||
};
|
||||
};
|
||||
|
||||
NewConfig = MyConfig & {
|
||||
host = "127.0.0.1";
|
||||
port = 8000;
|
||||
};
|
||||
|
||||
disabled_config = NewConfig & {
|
||||
feature_hoge.enable = false;
|
||||
};
|
||||
|
||||
enabled_config = NewConfig;
|
||||
```
|
||||
|
||||
## Array schema
|
||||
|
||||
```dcdl
|
||||
Services = [...{
|
||||
name = String;
|
||||
port = Int default 8080;
|
||||
}];
|
||||
|
||||
[
|
||||
{ name = "api"; },
|
||||
{ name = "worker"; port = 9000; },
|
||||
] as Services
|
||||
```
|
||||
|
||||
An abstract array requires an element constraint.
|
||||
In this example, `port` in the first element materializes to `8080`.
|
||||
|
||||
## Associative arrays and range refinement
|
||||
|
||||
```dcdl
|
||||
Service = {
|
||||
port = Int;
|
||||
enabled = Bool default true;
|
||||
};
|
||||
|
||||
services = {
|
||||
api = { port = 8080; };
|
||||
worker = { port = 8081; enabled = false; };
|
||||
} as {...Service};
|
||||
```
|
||||
|
||||
`api` and `worker` are arbitrary keys, and each value is refined to a range narrower than `Service`.
|
||||
In the result of `as`, `api.enabled` remains abstract. Its default of `true` is selected only when the complete result is materialized.
|
||||
|
||||
## Functions and constraints
|
||||
|
||||
```dcdl
|
||||
let
|
||||
Port = Int & >= 1 & <= 65535;
|
||||
add_offset = (base: Port, offset: Int) => base + offset;
|
||||
in
|
||||
add_offset(8000, 80)
|
||||
```
|
||||
|
||||
Evaluation result:
|
||||
|
||||
```text
|
||||
8080
|
||||
```
|
||||
|
||||
## match
|
||||
|
||||
```dcdl
|
||||
(
|
||||
input_a: {
|
||||
hoge = Int & >= 0;
|
||||
},
|
||||
input_b: {
|
||||
fuga = 20;
|
||||
}
|
||||
) =>
|
||||
let
|
||||
inputs = {
|
||||
a = input_a;
|
||||
b = input_b;
|
||||
};
|
||||
in
|
||||
{
|
||||
foo = match inputs.a.hoge {
|
||||
>= 20: {
|
||||
value = 200;
|
||||
};
|
||||
>= 10: {
|
||||
value = 100;
|
||||
};
|
||||
_: {
|
||||
value = 300;
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
`match` evaluates arms in order, so place narrower conditions before broader ones.
|
||||
|
||||
## Deep patch
|
||||
|
||||
```dcdl
|
||||
Base = {
|
||||
feature_hoge = {
|
||||
enable = Bool default true;
|
||||
fuga = Int default 10;
|
||||
};
|
||||
};
|
||||
|
||||
Patched = Base // {
|
||||
feature_hoge.enable = false;
|
||||
};
|
||||
```
|
||||
|
||||
`Patched` is equivalent to:
|
||||
|
||||
```dcdl
|
||||
{
|
||||
feature_hoge = {
|
||||
enable = false;
|
||||
fuga = Int default 10;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Cyclic imports
|
||||
|
||||
```dcdl
|
||||
# main.dcdl
|
||||
{
|
||||
schema = {
|
||||
hoge = String;
|
||||
};
|
||||
|
||||
result = (import "./func.dcdl")(schema);
|
||||
}
|
||||
```
|
||||
|
||||
```dcdl
|
||||
# func.dcdl
|
||||
(input: (import "./main.dcdl").schema) =>
|
||||
{
|
||||
# ...
|
||||
}
|
||||
```
|
||||
|
||||
`func.dcdl` imports `main.dcdl`, but it references only `main.schema`.
|
||||
The cyclic import is valid as long as `main.schema` does not depend on `main.result`.
|
||||
@@ -0,0 +1,71 @@
|
||||
# Array Expression
|
||||
|
||||
An array expression represents an ordered sequence of values.
|
||||
|
||||
```dcdl
|
||||
[1, 2, 3]
|
||||
["a", "b", "c"]
|
||||
```
|
||||
|
||||
An array literal is a concrete value and does not require all elements to have the same type.
|
||||
|
||||
```dcdl
|
||||
["api", 8080, true]
|
||||
```
|
||||
|
||||
## Array constraint
|
||||
|
||||
Write an array constraint by placing an ellipsis immediately after `[`, followed by an element constraint.
|
||||
|
||||
```dcdl
|
||||
Names = [...String];
|
||||
Ports = [...(Int & >= 1 & <= 65535)];
|
||||
```
|
||||
|
||||
`[...T]` represents an array of zero or more elements, all of which satisfy `T`.
|
||||
An empty array satisfies every array constraint.
|
||||
|
||||
```dcdl
|
||||
[...String] & []
|
||||
[...String] & ["api", "worker"]
|
||||
```
|
||||
|
||||
The element constraint can be an object range.
|
||||
A field with a default that exists only on the right remains abstract in every element when the array constraint is composed.
|
||||
Its default is selected only if the array is later materialized.
|
||||
|
||||
```dcdl
|
||||
Services = [...{
|
||||
name = String;
|
||||
enabled = Bool default true;
|
||||
}];
|
||||
|
||||
Services & [{ name = "api"; }]
|
||||
```
|
||||
|
||||
Applying an array constraint to a concrete array refines each element against the element range using the same rules as `as`.
|
||||
For an object element range, a field present only on the left produces an error, while a field present only on the right remains abstract.
|
||||
|
||||
Decodal does not provide an abstract array type without an element constraint.
|
||||
The former `Array` primitive type is unavailable, and `T` is required in `[...T]`.
|
||||
`[String]` is not an array constraint; it is a concrete one-element array containing the unresolved `String` constraint.
|
||||
An array constraint describes only the element range. It has no length, positional tuple, or uniqueness constraints.
|
||||
|
||||
## Array concatenation
|
||||
|
||||
`++` concatenates two concrete arrays.
|
||||
|
||||
```dcdl
|
||||
base = ["read", "write"];
|
||||
extra = ["admin"];
|
||||
roles = base ++ extra;
|
||||
```
|
||||
|
||||
`roles` has the same value as:
|
||||
|
||||
```dcdl
|
||||
["read", "write", "admin"]
|
||||
```
|
||||
|
||||
`++` does not transform array elements.
|
||||
Elements from the right side follow those from the left.
|
||||
@@ -0,0 +1,82 @@
|
||||
# Range Refinement
|
||||
|
||||
`narrower as wider` verifies that the left side is more concrete and narrower than the right side, then composes them asymmetrically.
|
||||
|
||||
```dcdl
|
||||
Int & > 10 as Int & > 0
|
||||
```
|
||||
|
||||
This succeeds and returns the narrower left-hand range, `Int & > 10`.
|
||||
The reverse, `Int as Int & > 0`, fails because the left side is not guaranteed to satisfy the right side.
|
||||
|
||||
## Object
|
||||
|
||||
For objects, the right side defines the field domain used to check the left:
|
||||
|
||||
- A field found only on the left lies outside the right-hand domain and produces an error.
|
||||
- A field found on both sides is checked recursively, and the narrower left-hand result is used.
|
||||
- A field found only on the right remains unevaluated in the result, whether or not it has a default.
|
||||
- Field order in the result follows the right-hand side.
|
||||
|
||||
```dcdl
|
||||
partial = {
|
||||
port = 8080;
|
||||
} as {
|
||||
host = String;
|
||||
port = Int;
|
||||
enabled = Bool default true;
|
||||
};
|
||||
```
|
||||
|
||||
`partial.port` becomes the concrete value `8080`.
|
||||
`partial.host` and `partial.enabled` remain abstract fields from the right; `as` neither selects nor forces the default for `enabled`.
|
||||
|
||||
If `partial` is later materialized, the normal materialization rules apply.
|
||||
In this example, unresolved `host` produces an error. If `host` is also made concrete, the default for `enabled` is selected during materialization.
|
||||
|
||||
## Default
|
||||
|
||||
A default is not evidence of range containment.
|
||||
When the same field exists on both sides, the result uses the left side and does not inject the right-hand default.
|
||||
|
||||
```dcdl
|
||||
Int & > 0 as (Int default 1)
|
||||
```
|
||||
|
||||
The result is `Int & > 0` without a default.
|
||||
A right-only object field, however, is retained as a whole and therefore retains any default its abstract value already had.
|
||||
|
||||
## Map and array ranges
|
||||
|
||||
`{...T}` allows arbitrary object keys and verifies that each value is narrower than `T`.
|
||||
|
||||
```dcdl
|
||||
ports = {
|
||||
http = 80;
|
||||
https = 443;
|
||||
} as {...(Int & >= 1 & <= 65535)};
|
||||
```
|
||||
|
||||
When `[...T]` is used on the right, every array element is refined against `T` in the same way.
|
||||
|
||||
## Concrete right-hand ranges
|
||||
|
||||
Primitive and composed constraints validate values in the usual way.
|
||||
If the right side is a concrete scalar or array literal, the left must have the same value or the same length and element structure.
|
||||
A function cannot be used as the right-hand range.
|
||||
|
||||
The left side does not have to be concrete.
|
||||
Abstract values can be used when their containment relation can be determined.
|
||||
Primitive types, numeric bounds, identical regexes or predicates, and element ranges of arrays and maps support containment checks.
|
||||
|
||||
A function parameter written as `name: range` uses the same refinement rules as `as` when its argument is forced.
|
||||
|
||||
## Precedence
|
||||
|
||||
`as` has the lowest precedence, below `default`, and is left-associative.
|
||||
|
||||
```dcdl
|
||||
narrow & overrides as Wider
|
||||
```
|
||||
|
||||
This is interpreted as `(narrow & overrides) as Wider`.
|
||||
@@ -0,0 +1,27 @@
|
||||
# Composition Expression
|
||||
|
||||
A composition expression combines multiple values, constraints, or structures.
|
||||
|
||||
## `&`
|
||||
|
||||
`&` performs constraint-preserving composition.
|
||||
|
||||
```dcdl
|
||||
Port = Int & >= 1 & <= 65535;
|
||||
Config = MyConfig & { port = 8000; };
|
||||
```
|
||||
|
||||
Because object fields present on only one side are retained, `&` has no direction.
|
||||
Use [`as`](./ascription.md) to verify that the left side is narrower than the right and treat the right side as the field domain.
|
||||
|
||||
## `//`
|
||||
|
||||
`//` performs a right-biased structural patch.
|
||||
|
||||
```dcdl
|
||||
Patched = Base // {
|
||||
feature_hoge.enable = false;
|
||||
};
|
||||
```
|
||||
|
||||
See [Operators](../operators.md) for details.
|
||||
@@ -0,0 +1,15 @@
|
||||
# Default Expression
|
||||
|
||||
A `default` expression specifies a fallback selected during materialization when no explicit value exists.
|
||||
|
||||
```dcdl
|
||||
port = Int default 8080;
|
||||
```
|
||||
|
||||
`default` is not a constraint.
|
||||
See [Constraints and Defaults](../constraints-and-defaults.md) for details.
|
||||
|
||||
## Evaluation
|
||||
|
||||
The fallback expression is evaluated only if materialization requires it.
|
||||
When an explicit value exists, the `default` is neither evaluated nor selected.
|
||||
@@ -0,0 +1,14 @@
|
||||
# Function Call Expression
|
||||
|
||||
A function call expression applies a function value to arguments.
|
||||
|
||||
```dcdl
|
||||
increment(41)
|
||||
```
|
||||
|
||||
## Evaluation
|
||||
|
||||
An argument is evaluated when the function body references it.
|
||||
If the argument is referenced several times, its evaluation result is reused.
|
||||
When a parameter has a range, the argument is refined using the same rules as `narrower as wider`.
|
||||
Fields present only in the parameter range remain abstract, and defaults are not selected at this point.
|
||||
@@ -0,0 +1,14 @@
|
||||
# Function Expression
|
||||
|
||||
A function expression is a value that accepts arguments and returns an expression.
|
||||
|
||||
```dcdl
|
||||
(value: Int) => value + 1
|
||||
```
|
||||
|
||||
See [Functions](../functions.md) for the complete function specification.
|
||||
|
||||
## Evaluation
|
||||
|
||||
A function can reference bindings from the lexical scope where it was defined.
|
||||
Its body is evaluated when the function is called, not when the function value is created.
|
||||
@@ -0,0 +1,14 @@
|
||||
# Identifier Expression
|
||||
|
||||
An identifier expression references a name bound in the lexical scope.
|
||||
|
||||
```dcdl
|
||||
Port
|
||||
MyConfig
|
||||
mkConfig
|
||||
```
|
||||
|
||||
## Evaluation
|
||||
|
||||
An identifier evaluates the corresponding binding when its value is needed.
|
||||
If no such binding exists, evaluation produces an unresolved-identifier error.
|
||||
@@ -0,0 +1,14 @@
|
||||
# Import Expression
|
||||
|
||||
An import expression returns a DCDL module or structured value resolved by the host.
|
||||
|
||||
```dcdl
|
||||
import "./config.dcdl"
|
||||
```
|
||||
|
||||
The specifier is a string literal. The host defines its interpretation as a path, resource name, or another identifier.
|
||||
See [Modules and Imports](../modules-and-imports.md) for details.
|
||||
|
||||
## Evaluation
|
||||
|
||||
Imported values are evaluated lazily, and unreferenced fields are not evaluated.
|
||||
@@ -0,0 +1,26 @@
|
||||
# Expression
|
||||
|
||||
This chapter classifies the expressions evaluated by the language.
|
||||
|
||||
An expression is the basic unit for representing a value, constraint, structure, or computation.
|
||||
|
||||
```text
|
||||
Expr
|
||||
├─ literal
|
||||
├─ identifier
|
||||
├─ path reference
|
||||
├─ object
|
||||
├─ map constraint
|
||||
├─ array
|
||||
├─ array constraint
|
||||
├─ function
|
||||
├─ function call
|
||||
├─ let
|
||||
├─ match
|
||||
├─ import
|
||||
├─ composition
|
||||
├─ range refinement (`as`)
|
||||
└─ default
|
||||
```
|
||||
|
||||
Links to each expression specification are collected in the [manual contents](../../index.md).
|
||||
@@ -0,0 +1,16 @@
|
||||
# Let Expression
|
||||
|
||||
A `let` expression creates local bindings.
|
||||
|
||||
```dcdl
|
||||
let
|
||||
base = 8000;
|
||||
offset = 80;
|
||||
in
|
||||
base + offset
|
||||
```
|
||||
|
||||
## Evaluation
|
||||
|
||||
A binding is evaluated when it is referenced.
|
||||
An unreferenced binding is not evaluated, and the result is reused if the same binding is referenced several times.
|
||||
@@ -0,0 +1,20 @@
|
||||
# Literal Expression
|
||||
|
||||
A literal expression is a concrete value written directly in source.
|
||||
|
||||
## Kinds
|
||||
|
||||
```dcdl
|
||||
"hello"
|
||||
123
|
||||
3.14
|
||||
true
|
||||
false
|
||||
```
|
||||
|
||||
## Corresponding primitive types
|
||||
|
||||
- A string literal satisfies `String`.
|
||||
- An integer literal satisfies `Int`.
|
||||
- A floating-point literal satisfies `Float`.
|
||||
- `true` and `false` satisfy `Bool`.
|
||||
@@ -0,0 +1,24 @@
|
||||
# Match Expression
|
||||
|
||||
A `match` expression compares a target value against patterns from top to bottom and selects the first matching arm.
|
||||
|
||||
```dcdl
|
||||
foo = match inputs.a.hoge {
|
||||
>= 20: {
|
||||
value = 200;
|
||||
};
|
||||
>= 10: {
|
||||
value = 100;
|
||||
};
|
||||
_: {
|
||||
value = 300;
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
`_` is the fallback pattern.
|
||||
|
||||
## Ordering
|
||||
|
||||
Arms are ordered; Decodal does not automatically select the most specific pattern.
|
||||
If a broad condition appears first, narrower conditions after it are unreachable.
|
||||
@@ -0,0 +1,90 @@
|
||||
# Object Expression
|
||||
|
||||
An object expression is a collection of named fields.
|
||||
|
||||
```dcdl
|
||||
{
|
||||
host = "127.0.0.1";
|
||||
port = 8000;
|
||||
}
|
||||
```
|
||||
|
||||
Objects can represent both configuration values and schemas.
|
||||
|
||||
```dcdl
|
||||
MyConfig = {
|
||||
host = String;
|
||||
port = Int default 8080;
|
||||
};
|
||||
```
|
||||
|
||||
## Dot-path fields
|
||||
|
||||
A nested field can be defined with a dot path.
|
||||
|
||||
```dcdl
|
||||
{
|
||||
feature_hoge.enable = false;
|
||||
}
|
||||
```
|
||||
|
||||
This represents the same structure as:
|
||||
|
||||
```dcdl
|
||||
{
|
||||
feature_hoge = {
|
||||
enable = false;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Map constraint
|
||||
|
||||
An associative-array constraint applies the same schema to every field value in an object.
|
||||
|
||||
```dcdl
|
||||
Services = {...{
|
||||
port = Int;
|
||||
enabled = Bool default true;
|
||||
}};
|
||||
```
|
||||
|
||||
`{...T}` represents an object whose keys are not fixed and whose values all satisfy `T`.
|
||||
An empty object is allowed. After materialization, it becomes the same object data as an object with named fields.
|
||||
|
||||
```dcdl
|
||||
services = {
|
||||
api = { port = 8080; };
|
||||
worker = { port = 8081; enabled = false; };
|
||||
} as Services;
|
||||
```
|
||||
|
||||
Each entry is refined by `as` into the right-hand field domain, so a field other than `port` or `enabled` on the left produces an error.
|
||||
A field found only on the right remains abstract whether or not it has a default.
|
||||
An object supplied by the host can preserve string keys that are not valid identifiers, although field names written in DCDL source are limited to ordinary identifiers.
|
||||
|
||||
## Object rest constraint
|
||||
|
||||
Fixed fields and a value range for arbitrary keys can coexist in one object.
|
||||
|
||||
```dcdl
|
||||
OpenConfig = {
|
||||
enabled = Bool default true;
|
||||
...Unknown
|
||||
};
|
||||
```
|
||||
|
||||
`...T` applies only to remaining fields that were not named explicitly.
|
||||
Each named field uses its own range; the rest range is not additionally applied to it.
|
||||
A rest constraint does not generate fields. Materialization outputs only additional fields that actually exist.
|
||||
|
||||
```dcdl
|
||||
{
|
||||
enabled = false;
|
||||
plugin = { name = "cache"; };
|
||||
} as OpenConfig
|
||||
```
|
||||
|
||||
An object can contain one `...T`, at its end.
|
||||
An object without it is closed, and an undeclared left-hand field under `as` produces an error.
|
||||
`{...T}` with no named fields is an abstract map constraint; materializing it by itself requires a default or a concrete value.
|
||||
@@ -0,0 +1,16 @@
|
||||
# Path Reference Expression
|
||||
|
||||
A path reference expression accesses a field of an object.
|
||||
|
||||
```dcdl
|
||||
config.host
|
||||
config.feature_hoge.enable
|
||||
```
|
||||
|
||||
## Evaluation
|
||||
|
||||
The expression on the left is evaluated as an object, then the named field is referenced.
|
||||
The referenced field is not evaluated until needed.
|
||||
|
||||
Referencing a field that does not exist produces a diagnostic.
|
||||
An explicit `Unknown` range does not represent a missing field, so it does not make field presence ambiguous.
|
||||
@@ -0,0 +1,41 @@
|
||||
# Functions
|
||||
|
||||
A function is a pure expression that accepts values and returns a value.
|
||||
|
||||
## Syntax
|
||||
|
||||
```dcdl
|
||||
(value: Int) => value + 1
|
||||
```
|
||||
|
||||
Use ordinary call syntax to invoke a function.
|
||||
|
||||
```dcdl
|
||||
let
|
||||
increment = (value: Int) => value + 1;
|
||||
in
|
||||
increment(41)
|
||||
```
|
||||
|
||||
A function can have multiple parameters.
|
||||
|
||||
```dcdl
|
||||
(input_a: { hoge = Int & >= 0; }, input_b: { fuga = Int; }) =>
|
||||
{
|
||||
hoge = input_a.hoge;
|
||||
fuga = input_b.fuga;
|
||||
}
|
||||
```
|
||||
|
||||
A parameter range is optional.
|
||||
When present, the argument is validated and refined using the same rules as `as` when the argument is referenced.
|
||||
|
||||
## Scope and evaluation
|
||||
|
||||
Functions have lexical scope and can reference bindings from where they were defined.
|
||||
Arguments are evaluated lazily, so an argument not referenced by the function body is not evaluated.
|
||||
Recursive field or argument dependencies produce a cycle diagnostic.
|
||||
|
||||
Functions can be referenced and called as intermediate values, but cannot be materialized as data.
|
||||
An unapplied function remaining in a materialization target produces a diagnostic.
|
||||
Function equality is not defined, and composing two function values with `&` produces a conflict.
|
||||
@@ -1,7 +1,6 @@
|
||||
# Grammar
|
||||
|
||||
This page is the canonical grammar reference for Decodal syntax.
|
||||
Parser implementations such as the Rust parser, Tree-sitter grammar, and Lezer grammar should follow this grammar and may add implementation-specific precedence annotations where needed.
|
||||
This page defines the grammar of Decodal source text.
|
||||
|
||||
## Lexical grammar
|
||||
|
||||
@@ -112,8 +111,3 @@ Precedence is highest first.
|
||||
12. `as`
|
||||
|
||||
Binary operators are left-associative except `default`, which is right-associative.
|
||||
|
||||
## Tooling mapping
|
||||
|
||||
Syntax tooling should derive token categories from this grammar rather than making a tool-specific grammar canonical.
|
||||
Tree-sitter and Lezer grammars are implementation artifacts that follow this page.
|
||||
@@ -0,0 +1,12 @@
|
||||
# Language Specification
|
||||
|
||||
This chapter defines the syntax of Decodal source and the rules that determine evaluation results.
|
||||
|
||||
- Syntax and grammar
|
||||
- Primitives, objects, arrays, and functions
|
||||
- Constraints, `Unknown`, and `default`
|
||||
- `&`, `//`, `as`, and other operators
|
||||
- Modules, imports, and lazy evaluation
|
||||
- Materialization and diagnostics
|
||||
|
||||
See the [manual contents](../index.md) for the complete chapter list.
|
||||
@@ -0,0 +1,73 @@
|
||||
# Materialization and Errors
|
||||
|
||||
A normal evaluation result may retain constraints, `default` values, functions, and other abstract values.
|
||||
Materialization converts an evaluation result into concrete data that can be passed outside Decodal.
|
||||
|
||||
## Materialization rules
|
||||
|
||||
Materialization performs the following work:
|
||||
|
||||
- Evaluate required fields.
|
||||
- Apply `default` to abstract ranges that have no explicit value.
|
||||
- Verify that concrete values and selected defaults satisfy their constraints.
|
||||
- Reject `Unknown` and other unresolved ranges without a `default`.
|
||||
- Reject unapplied functions and other values that cannot be represented as data.
|
||||
|
||||
```dcdl
|
||||
Service = {
|
||||
host = String;
|
||||
port = Int default 8080;
|
||||
};
|
||||
```
|
||||
|
||||
Materializing `Service` directly fails because `host` has neither a concrete value nor a `default`.
|
||||
|
||||
```dcdl
|
||||
Config = {
|
||||
host = "localhost";
|
||||
} as Service;
|
||||
```
|
||||
|
||||
Materializing `Config` produces the following data:
|
||||
|
||||
```dcdl
|
||||
{
|
||||
host = "localhost";
|
||||
port = 8080;
|
||||
}
|
||||
```
|
||||
|
||||
A `default` is not selected for a field that has an explicit value.
|
||||
|
||||
## Diagnostics
|
||||
|
||||
Errors are returned as diagnostics, not ordinary values.
|
||||
Expressions cannot branch on a diagnostic's kind or contents.
|
||||
|
||||
Representative diagnostics include:
|
||||
|
||||
- Syntax errors
|
||||
- Unresolved identifiers or fields
|
||||
- Type mismatches and constraint violations
|
||||
- Conflicts in `&` or `default`
|
||||
- Dependency cycles
|
||||
- Import failures
|
||||
- Match failures
|
||||
- Materialization failures
|
||||
|
||||
A diagnostic identifies the relevant DCDL source span.
|
||||
When several expressions conflict, it also points to the related fields, constraints, values, and defaults.
|
||||
If a structured import value has no source span, the diagnostic identifies the stable key supplied by the host and the logical value path.
|
||||
|
||||
## Fallback
|
||||
|
||||
A `match` with no matching arm and no fallback arm produces a diagnostic.
|
||||
|
||||
```dcdl
|
||||
match value {
|
||||
>= 10: "large";
|
||||
}
|
||||
```
|
||||
|
||||
Decodal does not provide general-purpose `try / catch`, optional imports, or optional field access for catching diagnostics.
|
||||
Use `default` for a missing-value fallback and `match` for branching over a finite set of values.
|
||||
@@ -0,0 +1,88 @@
|
||||
# Modules and Imports
|
||||
|
||||
`import` returns a DCDL module or structured value resolved by the host.
|
||||
|
||||
## Syntax
|
||||
|
||||
```dcdl
|
||||
import "./config.dcdl"
|
||||
```
|
||||
|
||||
An import specifier is a string literal.
|
||||
The host decides whether a specifier represents a path, URL, resource name, or another form of identifier.
|
||||
|
||||
## Modules
|
||||
|
||||
A sequence of field definitions at the top level creates a recursive module scope.
|
||||
A top-level field can refer to another top-level field in the same module by identifier.
|
||||
|
||||
```dcdl
|
||||
schema = {
|
||||
name = String;
|
||||
};
|
||||
|
||||
result = schema;
|
||||
```
|
||||
|
||||
Fields in an ordinary object literal do not implicitly refer to sibling fields by identifier.
|
||||
To reference a value from inside an object, use a binding in an outer scope or an explicit path reference.
|
||||
|
||||
Modules and their fields are evaluated lazily.
|
||||
An unreferenced field of an imported module is not evaluated.
|
||||
|
||||
## Host-defined resolution
|
||||
|
||||
Decodal does not define filesystem or network rules for import specifiers.
|
||||
The host receives the importing module and specifier, then returns one of:
|
||||
|
||||
- DCDL source
|
||||
- A structured value constructed by the host
|
||||
|
||||
The latter allows a host to parse Markdown, JSON, TOML, and other formats according to its own rules and expose the result as an ordinary Decodal value.
|
||||
|
||||
```dcdl
|
||||
Post = {
|
||||
frontmatter = {
|
||||
title = String;
|
||||
draft = Bool default false;
|
||||
};
|
||||
body = String;
|
||||
};
|
||||
|
||||
post = (import "./hello.md") as Post;
|
||||
```
|
||||
|
||||
Structured values follow the same rules as source-derived values for path references, composition, constraint validation, and materialization.
|
||||
See [Embedding](../embedding.md#imports) for the loader API and diagnostic provenance.
|
||||
|
||||
## Cyclic imports
|
||||
|
||||
Modules may import one another cyclically as long as the dependency graph of the fields actually evaluated is not cyclic.
|
||||
|
||||
```dcdl
|
||||
# main.dcdl
|
||||
schema = {
|
||||
name = String;
|
||||
};
|
||||
|
||||
result = (import "./func.dcdl")(schema);
|
||||
```
|
||||
|
||||
```dcdl
|
||||
# func.dcdl
|
||||
(input: (import "./main.dcdl").schema) => input
|
||||
```
|
||||
|
||||
Here, `func.dcdl` imports `main.dcdl`, but the referenced `schema` does not depend on `result`, so evaluation succeeds.
|
||||
Reaching the same field again while it is being evaluated produces a cycle-dependency diagnostic.
|
||||
|
||||
## Import failures
|
||||
|
||||
The following conditions produce an import failure:
|
||||
|
||||
- The host cannot resolve the specifier.
|
||||
- The resource cannot be read.
|
||||
- Parsing DCDL source fails.
|
||||
- A required value in the imported module cannot be evaluated.
|
||||
- Reading or converting a structured value fails.
|
||||
- The evaluated dependency graph is cyclic.
|
||||
@@ -0,0 +1,17 @@
|
||||
# Naming Conventions
|
||||
|
||||
Identifier capitalization has no language-defined meaning.
|
||||
Values, constraints, schemas, and derived configurations are all expressions of the same kind.
|
||||
|
||||
For readability, the following conventions are recommended:
|
||||
|
||||
- Object values: `lower_snake`
|
||||
- Functions: `lowerCamel`
|
||||
- Primitives, schemas, and abstract constraints: `UpperCamel`
|
||||
|
||||
```dcdl
|
||||
Port = Int & >= 1;
|
||||
Service = { port = Port; };
|
||||
service = { port = 8080; } as Service;
|
||||
mkService = (port: Port) => { port = port; };
|
||||
```
|
||||
@@ -0,0 +1,250 @@
|
||||
# Operators
|
||||
|
||||
This chapter defines the meaning of Decodal's operators.
|
||||
|
||||
## Operator reference
|
||||
|
||||
| Operator | Form | Kind | Operands | Result / meaning |
|
||||
|---|---|---|---|---|
|
||||
| `.` | `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 |
|
||||
| `as` | `narrower as wider` | range refinement | value / constraint / structure | narrower result plus untouched right-only ranges |
|
||||
|
||||
A concrete scalar is a `String`, `Bool`, `Int`, or `Float` value.
|
||||
|
||||
## Precedence
|
||||
|
||||
Precedence is highest first:
|
||||
|
||||
1. Function calls and field references
|
||||
2. Unary `!` and `-`
|
||||
3. `*` and `/`
|
||||
4. `+` and `-`
|
||||
5. `++`
|
||||
6. `==`, `!=`, `<`, `<=`, `>`, and `>=`
|
||||
7. `&&`
|
||||
8. `||`
|
||||
9. `&`
|
||||
10. `//`
|
||||
11. `default`
|
||||
12. `as`
|
||||
|
||||
Binary operators at the same precedence are left-associative.
|
||||
`default` is right-associative.
|
||||
|
||||
## Arithmetic operators
|
||||
|
||||
`+`, `-`, `*`, and `/` perform arithmetic on concrete `Int` and `Float` values.
|
||||
See [Arithmetic Expression](./expression/arithmetic.md) for details.
|
||||
|
||||
## Array concatenation operator
|
||||
|
||||
`++` concatenates two concrete arrays.
|
||||
It does not transform the elements; elements from the right side follow elements from the left.
|
||||
|
||||
```dcdl
|
||||
["read", "write"] ++ ["admin"]
|
||||
```
|
||||
|
||||
## Logical and comparison operators
|
||||
|
||||
`!`, `&&`, and `||` operate on concrete `Bool` values.
|
||||
`&&` and `||` short-circuit.
|
||||
|
||||
`==` and `!=` compare concrete scalar values.
|
||||
`<`, `<=`, `>`, and `>=` compare concrete numeric values.
|
||||
See [Logical and Comparison Expressions](./expression/logical-and-comparison.md) for details.
|
||||
|
||||
## `&`: constraint-preserving composition
|
||||
|
||||
`&` composes values, constraints, and structures.
|
||||
|
||||
```dcdl
|
||||
A & B
|
||||
```
|
||||
|
||||
The basic rules are:
|
||||
|
||||
- Two constraints become a constraint that satisfies both.
|
||||
- A constraint and a concrete value require the concrete value to satisfy the constraint.
|
||||
- Two identical concrete values produce that value.
|
||||
- Two different concrete values conflict.
|
||||
- Two objects are composed field by field.
|
||||
- A field present on both sides is composed with `&`.
|
||||
- A field present on only one side is retained unchanged.
|
||||
- Any contradiction produces an error.
|
||||
|
||||
For example:
|
||||
|
||||
```dcdl
|
||||
Port = Int & >= 1 & <= 65535;
|
||||
NarrowedPort = Port & > 443;
|
||||
|
||||
MyConfig = {
|
||||
port = NarrowedPort default 8080;
|
||||
};
|
||||
|
||||
Config = MyConfig & {
|
||||
port = 8000;
|
||||
};
|
||||
```
|
||||
|
||||
Conceptually, `Config.port` becomes:
|
||||
|
||||
```text
|
||||
NarrowedPort & 8000
|
||||
```
|
||||
|
||||
This succeeds because `8000` satisfies `NarrowedPort`.
|
||||
The following fails:
|
||||
|
||||
```dcdl
|
||||
BadConfig = MyConfig & {
|
||||
port = 80;
|
||||
};
|
||||
```
|
||||
|
||||
`80` does not satisfy `> 443`.
|
||||
|
||||
## `//`: patch composition
|
||||
|
||||
`//` is a right-biased structural patch operator.
|
||||
Where `&` preserves constraints, `//` changes or overrides configuration and schemas.
|
||||
|
||||
```dcdl
|
||||
A // B
|
||||
```
|
||||
|
||||
The basic rules are:
|
||||
|
||||
- Two objects are patched recursively field by field.
|
||||
- The same field containing object/object is patched recursively.
|
||||
- The same field containing anything other than object/object is replaced by the right side.
|
||||
- Fields present only on the left are retained.
|
||||
- Fields present only on the right are added.
|
||||
- Arrays are replaced by the right side by default.
|
||||
- Function values are replaced by the right side by default.
|
||||
|
||||
Thus, `//` is a deep patch rather than a shallow merge.
|
||||
|
||||
```dcdl
|
||||
Base = {
|
||||
feature_hoge = {
|
||||
enable = Bool default true;
|
||||
fuga = Int default 10;
|
||||
};
|
||||
};
|
||||
|
||||
Patched = Base // {
|
||||
feature_hoge = {
|
||||
enable = false;
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
`Patched` is equivalent to:
|
||||
|
||||
```dcdl
|
||||
{
|
||||
feature_hoge = {
|
||||
enable = false;
|
||||
fuga = Int default 10;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
The same patch can be written with a dot path:
|
||||
|
||||
```dcdl
|
||||
Patched = Base // {
|
||||
feature_hoge.enable = false;
|
||||
};
|
||||
```
|
||||
|
||||
## Replacing an entire object
|
||||
|
||||
Under `//`, object/object is always patched deeply.
|
||||
The core language does not include a special `replace(...)` syntax or built-in function for replacing an entire object field.
|
||||
|
||||
To use an entirely different object structure, construct the value outside the object being patched.
|
||||
|
||||
## `as`: range refinement
|
||||
|
||||
`as` is an asymmetric refinement operator that verifies the left side is more concrete and narrower than the right side.
|
||||
|
||||
```dcdl
|
||||
Refined = {
|
||||
port = 8000;
|
||||
} as {
|
||||
port = Int & >= 1 & <= 65535;
|
||||
host = String;
|
||||
enabled = Bool default true;
|
||||
};
|
||||
```
|
||||
|
||||
`port` becomes the concrete value `8000`.
|
||||
`host` and `enabled`, which exist only on the right, remain abstract in the result. Whether a field has a default does not affect preservation of a right-only field.
|
||||
`as` does not select defaults; normal default rules apply only if the result is later materialized.
|
||||
|
||||
A field present only in the left object lies outside the right-hand field domain and produces an error.
|
||||
Nested objects, array constraints, and map constraints present on both sides are refined recursively using the same rules.
|
||||
|
||||
Containment can also be checked between abstract ranges.
|
||||
|
||||
```dcdl
|
||||
Int & > 10 as Int & > 0 # succeeds and returns Int & > 10
|
||||
Int as Int & > 0 # fails
|
||||
```
|
||||
|
||||
`&` is symmetric and retains object fields found on only one side.
|
||||
Do not use it in place of directional refinement.
|
||||
See [Range Refinement](./expression/ascription.md) for details.
|
||||
|
||||
## Choosing between `&`, `//`, and `as`
|
||||
|
||||
Use `&` for symmetric composition that preserves constraints and partial structure.
|
||||
|
||||
```dcdl
|
||||
Combined = MyConfig & {
|
||||
port = 8000;
|
||||
};
|
||||
```
|
||||
|
||||
Use `//` to override or reshape an existing structure.
|
||||
|
||||
```dcdl
|
||||
ModifiedSchema = MyConfig // {
|
||||
port = Int default 9000;
|
||||
};
|
||||
```
|
||||
|
||||
Because `//` is a right-biased patch, it does not always preserve constraints from the left.
|
||||
Use `&` when those constraints must remain.
|
||||
|
||||
Use `as` to compose while verifying that the left side is narrower than the right.
|
||||
|
||||
```dcdl
|
||||
RefinedConfig = NarrowConfig as WideConfig;
|
||||
```
|
||||
@@ -0,0 +1,129 @@
|
||||
# Lexical Structure and Syntax
|
||||
|
||||
This chapter summarizes the surface syntax.
|
||||
See the EBNF in [Grammar](./grammar.md) for the precise grammar.
|
||||
|
||||
## Language name and extension
|
||||
|
||||
The project is named **Decodal**.
|
||||
Its full descriptive name is **Deferred Constraint Data Language**, abbreviated **DCDL**.
|
||||
Source files use the `.dcdl` extension.
|
||||
|
||||
```text
|
||||
config.dcdl
|
||||
schema.dcdl
|
||||
service.dcdl
|
||||
```
|
||||
|
||||
## Module source
|
||||
|
||||
An entire file can contain a single expression.
|
||||
A sequence of field definitions at the top level is treated as an implicit object.
|
||||
|
||||
```dcdl
|
||||
host = String;
|
||||
port = Int default 8080;
|
||||
```
|
||||
|
||||
The source above is equivalent to:
|
||||
|
||||
```dcdl
|
||||
{
|
||||
host = String;
|
||||
port = Int default 8080;
|
||||
}
|
||||
```
|
||||
|
||||
## Comments
|
||||
|
||||
A comment begins with `#` and continues to the end of the line.
|
||||
|
||||
```dcdl
|
||||
# comment
|
||||
host = "127.0.0.1"; # trailing comment
|
||||
```
|
||||
|
||||
## Semicolons
|
||||
|
||||
Semicolons separate object fields, `let` bindings, and `match` arms.
|
||||
A trailing semicolon is allowed.
|
||||
|
||||
```dcdl
|
||||
{
|
||||
host = "127.0.0.1";
|
||||
port = 8000;
|
||||
}
|
||||
```
|
||||
|
||||
## Identifiers
|
||||
|
||||
An identifier begins with an ASCII letter and may continue with ASCII letters, digits, or `_`.
|
||||
Naming conventions can use `lower_snake`, `lowerCamel`, or `UpperCamel`.
|
||||
|
||||
```dcdl
|
||||
my_config
|
||||
mkConfig
|
||||
IPv4Address
|
||||
```
|
||||
|
||||
## Path references
|
||||
|
||||
Use a dot to reference a field.
|
||||
|
||||
```dcdl
|
||||
config.host
|
||||
config.feature_hoge.enable
|
||||
```
|
||||
|
||||
Fields inside an object can also be defined with a dot path.
|
||||
|
||||
```dcdl
|
||||
{
|
||||
feature_hoge.enable = false;
|
||||
}
|
||||
```
|
||||
|
||||
This represents the same structure as:
|
||||
|
||||
```dcdl
|
||||
{
|
||||
feature_hoge = {
|
||||
enable = false;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Reserved words
|
||||
|
||||
The following words are reserved:
|
||||
|
||||
```text
|
||||
let
|
||||
in
|
||||
match
|
||||
import
|
||||
default
|
||||
as
|
||||
true
|
||||
false
|
||||
```
|
||||
|
||||
## Operators
|
||||
|
||||
The primary operators are:
|
||||
|
||||
```text
|
||||
+ - * / arithmetic
|
||||
++ array concatenation
|
||||
! && || logical operations
|
||||
== != < <= > >= comparisons
|
||||
& constraint-preserving composition
|
||||
// patch composition
|
||||
default fallback specification
|
||||
as range containment check and refinement from left to right
|
||||
=> function
|
||||
. field reference / dot-path definition
|
||||
... value constraint for an array or associative array
|
||||
```
|
||||
|
||||
Operator precedence is defined in [Operators](./operators.md).
|
||||
@@ -0,0 +1,19 @@
|
||||
# Bool
|
||||
|
||||
`Bool` is the primitive type constraint for Boolean values.
|
||||
|
||||
## Examples
|
||||
|
||||
```dcdl
|
||||
enable = Bool default true;
|
||||
disable = Bool default false;
|
||||
```
|
||||
|
||||
## Literals
|
||||
|
||||
`Bool` accepts the following literals:
|
||||
|
||||
```dcdl
|
||||
true
|
||||
false
|
||||
```
|
||||
@@ -0,0 +1,18 @@
|
||||
# Float
|
||||
|
||||
`Float` is the primitive type constraint for floating-point values.
|
||||
|
||||
## Examples
|
||||
|
||||
```dcdl
|
||||
ratio = Float;
|
||||
threshold = Float default 0.5;
|
||||
```
|
||||
|
||||
## Relationship to Int
|
||||
|
||||
`Int` and `Float` are distinct primitive type constraints.
|
||||
A `Float` constraint requires a concrete `Float`, and an `Int` constraint requires a concrete `Int`.
|
||||
|
||||
Arithmetic and comparison expressions can treat `Int` and `Float` as numeric values.
|
||||
Mixed arithmetic produces a `Float` result.
|
||||
@@ -0,0 +1,20 @@
|
||||
# Value
|
||||
|
||||
This chapter defines the kinds of values handled by the language.
|
||||
|
||||
Primitive types are built-in constraints that values must satisfy, rather than ordinary data values.
|
||||
|
||||
```dcdl
|
||||
name = String;
|
||||
retry = Int default 3;
|
||||
ratio = Float;
|
||||
enable = Bool default true;
|
||||
tags = [...String];
|
||||
```
|
||||
|
||||
The primitive types are `String`, `Int`, `Float`, and `Bool`.
|
||||
`Unknown` is not a primitive type; it is the top abstract range containing every Decodal value. An `Unknown` without a concrete value or default cannot be materialized.
|
||||
An array is not a primitive type and is described as `[...T]` with a required element constraint.
|
||||
Links to each type specification are collected in the [manual contents](../../index.md).
|
||||
|
||||
See [Constraints and Defaults](../constraints-and-defaults.md) for details about primitive types and constraint composition.
|
||||
@@ -0,0 +1,18 @@
|
||||
# Int
|
||||
|
||||
`Int` is the primitive type constraint for integer values.
|
||||
|
||||
## Examples
|
||||
|
||||
```dcdl
|
||||
retry = Int default 3;
|
||||
port = Int & >= 1 & <= 65535;
|
||||
```
|
||||
|
||||
## Constraint composition
|
||||
|
||||
`Int` can be composed with numeric comparison constraints.
|
||||
|
||||
```dcdl
|
||||
NarrowedPort = Int & >= 1 & <= 65535 & > 443;
|
||||
```
|
||||
@@ -0,0 +1,30 @@
|
||||
# String
|
||||
|
||||
`String` is the primitive type constraint for string values.
|
||||
|
||||
A string literal is enclosed in `"`.
|
||||
|
||||
```dcdl
|
||||
"hello"
|
||||
"line 1\nline 2"
|
||||
```
|
||||
|
||||
`$`, `{`, and `}` have no special meaning inside a string; Decodal does not perform string interpolation.
|
||||
|
||||
## Examples
|
||||
|
||||
```dcdl
|
||||
name = String;
|
||||
greeting = String default "hello";
|
||||
```
|
||||
|
||||
## Constraint composition
|
||||
|
||||
`String` can be composed with string constraints.
|
||||
|
||||
```dcdl
|
||||
message = String & /Hello! .*/;
|
||||
```
|
||||
|
||||
Enable regex-constraint validation with the Rust crate's `regex` feature.
|
||||
Without that feature, a regex constraint cannot be validated against a concrete string and produces a diagnostic.
|
||||
@@ -5,7 +5,7 @@ Lezer implementation of the Decodal grammar for CodeMirror 6.
|
||||
The canonical grammar is documented in:
|
||||
|
||||
```text
|
||||
../../doc/manual/souce/language/grammar.md
|
||||
../../doc/manual/source/language/grammar.md
|
||||
```
|
||||
|
||||
Regenerate the CodeMirror parser from the site directory:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user