Add bilingual manual and localized docs routes
This commit is contained in:
@@ -14,7 +14,8 @@ Development notes live here rather than in the public manual. The manual describ
|
|||||||
- `packages/decodal-codemirror`: CodeMirror language support and formatter WebAssembly.
|
- `packages/decodal-codemirror`: CodeMirror language support and formatter WebAssembly.
|
||||||
- `editors/lezer-decodal` and `editors/tree-sitter-decodal`: downstream editor grammars.
|
- `editors/lezer-decodal` and `editors/tree-sitter-decodal`: downstream editor grammars.
|
||||||
- `site/decodal-site`: documentation site and playground.
|
- `site/decodal-site`: documentation site and playground.
|
||||||
- `doc/manual/souce`: public manual consumed by the site.
|
- `doc/manual/source`: English public manual consumed by the site.
|
||||||
|
- `doc/manual/source/jp`: Japanese translation of the public manual.
|
||||||
|
|
||||||
## Runtime architecture
|
## Runtime architecture
|
||||||
|
|
||||||
@@ -43,7 +44,7 @@ Use `npm --prefix site/decodal-site run build:wasm` when Rust bindings or format
|
|||||||
|
|
||||||
When syntax changes:
|
When syntax changes:
|
||||||
|
|
||||||
1. Update the public EBNF in `doc/manual/souce/language/grammar.md` and the relevant language page.
|
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.
|
2. Update the Rust lexer/parser and tests.
|
||||||
3. Update the Lezer and Tree-sitter grammars and regenerate their committed outputs.
|
3. Update the Lezer and Tree-sitter grammars and regenerate their committed outputs.
|
||||||
4. Update formatter, playground, and language-service tests as applicable.
|
4. Update formatter, playground, and language-service tests as applicable.
|
||||||
|
|||||||
@@ -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,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;
|
||||||
|
```
|
||||||
@@ -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;
|
||||||
|
```
|
||||||
@@ -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,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.
|
||||||
@@ -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:
|
The canonical grammar is documented in:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
../../doc/manual/souce/language/grammar.md
|
../../doc/manual/source/language/grammar.md
|
||||||
```
|
```
|
||||||
|
|
||||||
Regenerate the CodeMirror parser from the site directory:
|
Regenerate the CodeMirror parser from the site directory:
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ Tree-sitter implementation of the Decodal grammar.
|
|||||||
The canonical grammar is documented in:
|
The canonical grammar is documented in:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
../../doc/manual/souce/language/grammar.md
|
../../doc/manual/source/language/grammar.md
|
||||||
```
|
```
|
||||||
|
|
||||||
This grammar is an editor/tooling implementation of that EBNF reference.
|
This grammar is an editor/tooling implementation of that EBNF reference.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Tree-sitter implementation of the canonical EBNF grammar in
|
// Tree-sitter implementation of the canonical EBNF grammar in
|
||||||
// doc/manual/souce/language/grammar.md.
|
// doc/manual/source/language/grammar.md.
|
||||||
|
|
||||||
const PREC = {
|
const PREC = {
|
||||||
AS: 0,
|
AS: 0,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import ManualLayout from '../layouts/ManualLayout.astro';
|
|||||||
import { highlightCode } from '../lib/highlight.js';
|
import { highlightCode } from '../lib/highlight.js';
|
||||||
|
|
||||||
const { locale = 'en' } = Astro.props;
|
const { locale = 'en' } = Astro.props;
|
||||||
|
const docsLocale = locale === 'ja' ? 'ja' : 'en';
|
||||||
|
|
||||||
const translations = {
|
const translations = {
|
||||||
en: {
|
en: {
|
||||||
@@ -121,7 +122,7 @@ const result = `{
|
|||||||
<p class="home-lead phrased" set:html={phrase(copy.heroLead)} />
|
<p class="home-lead phrased" set:html={phrase(copy.heroLead)} />
|
||||||
<div class="home-actions">
|
<div class="home-actions">
|
||||||
<a class="button primary" href="/playground/">{copy.playground}</a>
|
<a class="button primary" href="/playground/">{copy.playground}</a>
|
||||||
<a class="text-action" href="/docs/introduction/">{copy.introduction} <span aria-hidden="true">→</span></a>
|
<a class="text-action" href={`/docs/${docsLocale}/introduction/`}>{copy.introduction} <span aria-hidden="true">→</span></a>
|
||||||
</div>
|
</div>
|
||||||
<dl class="home-facts">
|
<dl class="home-facts">
|
||||||
{copy.facts.map(([label, value], index) => (
|
{copy.facts.map(([label, value], index) => (
|
||||||
@@ -177,7 +178,7 @@ const result = `{
|
|||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ol>
|
</ol>
|
||||||
<p class="section-link"><a href="/docs/embedding/">{copy.embedding} <span aria-hidden="true">→</span></a></p>
|
<p class="section-link"><a href={`/docs/${docsLocale}/embedding/`}>{copy.embedding} <span aria-hidden="true">→</span></a></p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="home-section package-section" aria-labelledby="packages-title">
|
<section class="home-section package-section" aria-labelledby="packages-title">
|
||||||
@@ -206,6 +207,6 @@ const result = `{
|
|||||||
<span class="package-links"><a href="https://jsr.io/@hare/decodal-codemirror@0.3.0">JSR</a><a href="https://www.npmjs.com/package/decodal-codemirror">npm</a></span>
|
<span class="package-links"><a href="https://jsr.io/@hare/decodal-codemirror@0.3.0">JSR</a><a href="https://www.npmjs.com/package/decodal-codemirror">npm</a></span>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
<p class="section-link"><a href="/docs/components/">{copy.packagesLink} <span aria-hidden="true">→</span></a></p>
|
<p class="section-link"><a href={`/docs/${docsLocale}/components/`}>{copy.packagesLink} <span aria-hidden="true">→</span></a></p>
|
||||||
</section>
|
</section>
|
||||||
</ManualLayout>
|
</ManualLayout>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
---
|
---
|
||||||
import { nav } from '../lib/docs.js';
|
import { getNav } from '../lib/docs.js';
|
||||||
import '@fontsource/ibm-plex-mono/latin-400.css';
|
import '@fontsource/ibm-plex-mono/latin-400.css';
|
||||||
import '@fontsource/ibm-plex-mono/latin-600.css';
|
import '@fontsource/ibm-plex-mono/latin-600.css';
|
||||||
import '../style.css';
|
import '../style.css';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
title = 'Decodal',
|
title = 'Decodal',
|
||||||
description = 'Decodal is a deterministic data language for configuration, constraints, and defaults.',
|
description,
|
||||||
active = '',
|
active = '',
|
||||||
playground = false,
|
playground = false,
|
||||||
frontpage = false,
|
frontpage = false,
|
||||||
@@ -14,11 +14,24 @@ const {
|
|||||||
} = Astro.props;
|
} = Astro.props;
|
||||||
|
|
||||||
const pathname = Astro.url.pathname;
|
const pathname = Astro.url.pathname;
|
||||||
const language = locale ?? (pathname.startsWith('/docs/') ? 'ja' : 'en');
|
const language = locale ?? 'en';
|
||||||
|
const pageDescription = description ?? (language === 'ja'
|
||||||
|
? 'Decodalは、設定、制約、デフォルトのための決定的なデータ言語です。'
|
||||||
|
: 'Decodal is a deterministic data language for configuration, constraints, and defaults.');
|
||||||
|
const docsLocale = language === 'ja' ? 'ja' : 'en';
|
||||||
|
const otherDocsLocale = docsLocale === 'ja' ? 'en' : 'ja';
|
||||||
|
const isDocsPage = pathname.startsWith('/docs/');
|
||||||
|
const localizedDocPath = active && active !== 'index' ? `${active}/` : '';
|
||||||
const homeHref = language === 'ja' ? '/ja/' : '/';
|
const homeHref = language === 'ja' ? '/ja/' : '/';
|
||||||
const alternateHref = language === 'ja' ? '/' : '/ja/';
|
const alternateHref = frontpage
|
||||||
|
? language === 'ja' ? '/' : '/ja/'
|
||||||
|
: `/docs/${otherDocsLocale}/${localizedDocPath}`;
|
||||||
const alternateLabel = language === 'ja' ? 'English' : '日本語';
|
const alternateLabel = language === 'ja' ? 'English' : '日本語';
|
||||||
const alternateAriaLabel = language === 'ja' ? 'Switch to English' : '日本語に切り替える';
|
const alternateAriaLabel = language === 'ja' ? 'Switch to English' : '日本語に切り替える';
|
||||||
|
const manualLabel = language === 'ja' ? 'マニュアル' : 'Manual';
|
||||||
|
const manualHref = `/docs/${docsLocale}/introduction/`;
|
||||||
|
const manualIndexHref = `/docs/${docsLocale}/`;
|
||||||
|
const nav = getNav(docsLocale);
|
||||||
|
|
||||||
function isCurrent(path) {
|
function isCurrent(path) {
|
||||||
return pathname === path || (path !== '/' && pathname.startsWith(path));
|
return pathname === path || (path !== '/' && pathname.startsWith(path));
|
||||||
@@ -28,7 +41,7 @@ function renderNav(items, prefix = []) {
|
|||||||
return `<ul class="nav-tree">${items
|
return `<ul class="nav-tree">${items
|
||||||
.map((item, index) => {
|
.map((item, index) => {
|
||||||
const number = [...prefix, index + 1];
|
const number = [...prefix, index + 1];
|
||||||
const href = `/docs/${item.slug}/`;
|
const href = `/docs/${docsLocale}/${item.slug}/`;
|
||||||
const activeClass = active === item.slug ? ' class="active"' : '';
|
const activeClass = active === item.slug ? ' class="active"' : '';
|
||||||
const children = item.children ? renderNav(item.children, number) : '';
|
const children = item.children ? renderNav(item.children, number) : '';
|
||||||
return `<li><a${activeClass} href="${href}"><span class="nav-number">${number.join('.')}.</span>${item.title}</a>${children}</li>`;
|
return `<li><a${activeClass} href="${href}"><span class="nav-number">${number.join('.')}.</span>${item.title}</a>${children}</li>`;
|
||||||
@@ -41,10 +54,10 @@ function renderNav(items, prefix = []) {
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="description" content={description} />
|
<meta name="description" content={pageDescription} />
|
||||||
{frontpage && <link rel="alternate" hreflang="en" href="/" />}
|
{(frontpage || isDocsPage) && <link rel="alternate" hreflang="en" href={frontpage ? '/' : `/docs/en/${localizedDocPath}`} />}
|
||||||
{frontpage && <link rel="alternate" hreflang="ja" href="/ja/" />}
|
{(frontpage || isDocsPage) && <link rel="alternate" hreflang="ja" href={frontpage ? '/ja/' : `/docs/ja/${localizedDocPath}`} />}
|
||||||
{frontpage && <link rel="alternate" hreflang="x-default" href="/" />}
|
{(frontpage || isDocsPage) && <link rel="alternate" hreflang="x-default" href={frontpage ? '/' : `/docs/en/${localizedDocPath}`} />}
|
||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -54,17 +67,17 @@ function renderNav(items, prefix = []) {
|
|||||||
<span>Deferred Constraint Data Language</span>
|
<span>Deferred Constraint Data Language</span>
|
||||||
</a>
|
</a>
|
||||||
<nav class="topnav" aria-label="Primary navigation">
|
<nav class="topnav" aria-label="Primary navigation">
|
||||||
<a class:list={{ current: isCurrent('/docs/') }} aria-current={isCurrent('/docs/') ? 'page' : undefined} href="/docs/introduction/">Manual</a>
|
<a class:list={{ current: isCurrent('/docs/') }} aria-current={isCurrent('/docs/') ? 'page' : undefined} href={manualHref}>{manualLabel}</a>
|
||||||
<a class:list={{ current: isCurrent('/playground/') }} aria-current={isCurrent('/playground/') ? 'page' : undefined} href="/playground/">Playground</a>
|
<a class:list={{ current: isCurrent('/playground/') }} aria-current={isCurrent('/playground/') ? 'page' : undefined} href="/playground/">Playground</a>
|
||||||
<a class="source-link" href="https://gitea.hareworks.net/Hare/Decodal">Source</a>
|
<a class="source-link" href="https://gitea.hareworks.net/Hare/Decodal">Source</a>
|
||||||
{frontpage && <a class="language-switch" href={alternateHref} hreflang={language === 'ja' ? 'en' : 'ja'} aria-label={alternateAriaLabel}>{alternateLabel}</a>}
|
{(frontpage || isDocsPage) && <a class="language-switch" href={alternateHref} hreflang={otherDocsLocale} aria-label={alternateAriaLabel}>{alternateLabel}</a>}
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
<div class:list={{ layout: true, 'wide-layout': playground || frontpage, 'playground-layout': playground }}>
|
<div class:list={{ layout: true, 'wide-layout': playground || frontpage, 'playground-layout': playground }}>
|
||||||
{!playground && !frontpage && (
|
{!playground && !frontpage && (
|
||||||
<aside class="sidebar">
|
<aside class="sidebar">
|
||||||
<div class="sidebar-content">
|
<div class="sidebar-content">
|
||||||
<a class="sidebar-title" href="/docs/">Manual</a>
|
<a class="sidebar-title" href={manualIndexHref}>{manualLabel}</a>
|
||||||
<nav set:html={renderNav(nav)} />
|
<nav set:html={renderNav(nav)} />
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|||||||
@@ -1,31 +1,48 @@
|
|||||||
import { marked } from 'marked';
|
import { marked } from 'marked';
|
||||||
import { escapeAttribute, highlightCode } from './highlight.js';
|
import { escapeAttribute, highlightCode } from './highlight.js';
|
||||||
|
|
||||||
const modules = import.meta.glob('../../../../doc/manual/souce/**/*.md', {
|
const modules = import.meta.glob('../../../../doc/manual/source/**/*.md', {
|
||||||
query: '?raw',
|
query: '?raw',
|
||||||
import: 'default',
|
import: 'default',
|
||||||
eager: true,
|
eager: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const documentEntries = Object.entries(modules).map(([path, content]) => {
|
const documentEntries = Object.entries(modules).map(([path, content]) => {
|
||||||
const sourcePath = path
|
const relativePath = path
|
||||||
.replace(/^\.\.\/\.\.\/\.\.\/\.\.\/doc\/manual\/souce\//, '')
|
.replace(/^\.\.\/\.\.\/\.\.\/\.\.\/doc\/manual\/source\//, '')
|
||||||
.replace(/\.md$/, '');
|
.replace(/\.md$/, '');
|
||||||
const parts = sourcePath.split('/');
|
const pathParts = relativePath.split('/');
|
||||||
parts.pop();
|
const locale = pathParts[0] === 'jp' ? 'ja' : 'en';
|
||||||
|
const localizedParts = locale === 'ja' ? pathParts.slice(1) : pathParts;
|
||||||
|
const sourcePath = localizedParts.join('/');
|
||||||
|
const base = localizedParts.slice(0, -1);
|
||||||
const slug = sourcePath.replace(/\/index$/, '') || 'index';
|
const slug = sourcePath.replace(/\/index$/, '') || 'index';
|
||||||
return { slug, content, base: parts };
|
return { locale, slug, content, base };
|
||||||
});
|
});
|
||||||
|
|
||||||
export const docs = Object.fromEntries(
|
const docsByLocale = Object.fromEntries(
|
||||||
documentEntries.map(({ slug, content }) => [slug, content]),
|
['en', 'ja'].map((locale) => [
|
||||||
|
locale,
|
||||||
|
Object.fromEntries(
|
||||||
|
documentEntries
|
||||||
|
.filter((entry) => entry.locale === locale)
|
||||||
|
.map(({ slug, content }) => [slug, content]),
|
||||||
|
),
|
||||||
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
const docBases = Object.fromEntries(
|
const docBasesByLocale = Object.fromEntries(
|
||||||
documentEntries.map(({ slug, base }) => [slug, base]),
|
['en', 'ja'].map((locale) => [
|
||||||
|
locale,
|
||||||
|
Object.fromEntries(
|
||||||
|
documentEntries
|
||||||
|
.filter((entry) => entry.locale === locale)
|
||||||
|
.map(({ slug, base }) => [slug, base]),
|
||||||
|
),
|
||||||
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const nav = [
|
const englishNav = [
|
||||||
{ title: 'Introduction', slug: 'introduction' },
|
{ title: 'Introduction', slug: 'introduction' },
|
||||||
{
|
{
|
||||||
title: 'Language Specification',
|
title: 'Language Specification',
|
||||||
@@ -78,6 +95,64 @@ export const nav = [
|
|||||||
{ title: 'Packages and Integrations', slug: 'components' },
|
{ title: 'Packages and Integrations', slug: 'components' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const japaneseNav = [
|
||||||
|
{ title: 'はじめに', slug: 'introduction' },
|
||||||
|
{
|
||||||
|
title: '言語仕様',
|
||||||
|
slug: 'language',
|
||||||
|
children: [
|
||||||
|
{ title: '構文と字句', slug: 'language/syntax' },
|
||||||
|
{ title: '文法', slug: 'language/grammar' },
|
||||||
|
{
|
||||||
|
title: '値',
|
||||||
|
slug: 'language/value',
|
||||||
|
children: [
|
||||||
|
{ title: 'String', slug: 'language/value/string' },
|
||||||
|
{ title: 'Int', slug: 'language/value/int' },
|
||||||
|
{ title: 'Float', slug: 'language/value/float' },
|
||||||
|
{ title: 'Bool', slug: 'language/value/bool' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '式',
|
||||||
|
slug: 'language/expression',
|
||||||
|
children: [
|
||||||
|
{ title: 'リテラル', slug: 'language/expression/literal' },
|
||||||
|
{ title: '識別子', slug: 'language/expression/identifier' },
|
||||||
|
{ title: 'パス参照', slug: 'language/expression/path-reference' },
|
||||||
|
{ title: 'オブジェクト', slug: 'language/expression/object' },
|
||||||
|
{ title: '配列', slug: 'language/expression/array' },
|
||||||
|
{ title: '関数', slug: 'language/expression/function' },
|
||||||
|
{ title: '関数呼び出し', slug: 'language/expression/function-call' },
|
||||||
|
{ title: 'let', slug: 'language/expression/let' },
|
||||||
|
{ title: 'match', slug: 'language/expression/match' },
|
||||||
|
{ title: 'import', slug: 'language/expression/import' },
|
||||||
|
{ title: '合成', slug: 'language/expression/composition' },
|
||||||
|
{ title: '範囲の絞り込み', slug: 'language/expression/ascription' },
|
||||||
|
{ title: 'default', slug: 'language/expression/default' },
|
||||||
|
{ title: '算術式', slug: 'language/expression/arithmetic' },
|
||||||
|
{ title: '論理式と比較式', slug: 'language/expression/logical-and-comparison' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ title: '制約とdefault', slug: 'language/constraints-and-defaults' },
|
||||||
|
{ title: '演算子', slug: 'language/operators' },
|
||||||
|
{ title: '関数', slug: 'language/functions' },
|
||||||
|
{ title: 'モジュールとimport', slug: 'language/modules-and-imports' },
|
||||||
|
{ title: '遅延評価', slug: 'language/evaluation' },
|
||||||
|
{ title: '具体化とエラー', slug: 'language/materialization-and-errors' },
|
||||||
|
{ title: '命名規約', slug: 'language/naming' },
|
||||||
|
{ title: '例', slug: 'language/examples' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ title: '組み込み', slug: 'embedding' },
|
||||||
|
{ title: 'パッケージと統合', slug: 'components' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const navByLocale = {
|
||||||
|
en: englishNav,
|
||||||
|
ja: japaneseNav,
|
||||||
|
};
|
||||||
|
|
||||||
const renderer = new marked.Renderer();
|
const renderer = new marked.Renderer();
|
||||||
|
|
||||||
renderer.code = (code, language = '') => {
|
renderer.code = (code, language = '') => {
|
||||||
@@ -90,33 +165,42 @@ renderer.codespan = (code) => `<code>${code}</code>`;
|
|||||||
|
|
||||||
marked.setOptions({ gfm: true, renderer });
|
marked.setOptions({ gfm: true, renderer });
|
||||||
|
|
||||||
export function allDocSlugs() {
|
export function getNav(locale = 'en') {
|
||||||
return Object.keys(docs).filter((slug) => slug !== 'index');
|
return navByLocale[locale] ?? englishNav;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function docTitle(slug, items = nav) {
|
export function allDocSlugs(locale = 'en') {
|
||||||
|
return Object.keys(docsByLocale[locale] ?? {}).filter((slug) => slug !== 'index');
|
||||||
|
}
|
||||||
|
|
||||||
|
function findDocTitle(slug, items) {
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
if (item.slug === slug) return item.title;
|
if (item.slug === slug) return item.title;
|
||||||
if (item.children) {
|
if (item.children) {
|
||||||
const childTitle = docTitle(slug, item.children);
|
const childTitle = findDocTitle(slug, item.children);
|
||||||
if (childTitle) return childTitle;
|
if (childTitle) return childTitle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return slug;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderMarkdown(slug) {
|
export function docTitle(slug, locale = 'en') {
|
||||||
const source = docs[slug] ?? docs.index;
|
return findDocTitle(slug, getNav(locale)) ?? slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function renderMarkdown(slug, locale = 'en') {
|
||||||
|
const localizedDocs = docsByLocale[locale] ?? docsByLocale.en;
|
||||||
|
const source = localizedDocs[slug] ?? localizedDocs.index;
|
||||||
const html = marked.parse(source ?? '# Not found\n');
|
const html = marked.parse(source ?? '# Not found\n');
|
||||||
return html.replace(/href="([^"#][^"]*)\.md(#[^"]*)?"/g, (_all, href, hash = '') => {
|
return html.replace(/href="([^"#][^"]*)\.md(#[^"]*)?"/g, (_all, href, hash = '') => {
|
||||||
const target = normalizeDocLink(slug, href);
|
const target = normalizeDocLink(locale, slug, href);
|
||||||
const pathname = target === 'index' ? '/docs/' : `/docs/${target}/`;
|
const pathname = target === 'index' ? `/docs/${locale}/` : `/docs/${locale}/${target}/`;
|
||||||
return `href="${pathname}${hash}"`;
|
return `href="${pathname}${hash}"`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeDocLink(currentSlug, href) {
|
function normalizeDocLink(locale, currentSlug, href) {
|
||||||
const base = docBases[currentSlug] ?? [];
|
const base = docBasesByLocale[locale]?.[currentSlug] ?? [];
|
||||||
const parts = [...base, ...href.split('/')];
|
const parts = [...base, ...href.split('/')];
|
||||||
const out = [];
|
const out = [];
|
||||||
for (const part of parts) {
|
for (const part of parts) {
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
---
|
|
||||||
import ManualLayout from '../../layouts/ManualLayout.astro';
|
|
||||||
import { allDocSlugs, docTitle, renderMarkdown } from '../../lib/docs.js';
|
|
||||||
import '@fontsource/ibm-plex-sans-jp/400.css';
|
|
||||||
import '@fontsource/ibm-plex-sans-jp/600.css';
|
|
||||||
|
|
||||||
export function getStaticPaths() {
|
|
||||||
return allDocSlugs().map((slug) => ({
|
|
||||||
params: { slug },
|
|
||||||
props: { slug },
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
const { slug } = Astro.props;
|
|
||||||
const html = renderMarkdown(slug);
|
|
||||||
const title = docTitle(slug);
|
|
||||||
---
|
|
||||||
<ManualLayout title={`${title} — Decodal Manual`} active={slug}>
|
|
||||||
<article class="markdown" set:html={html} />
|
|
||||||
</ManualLayout>
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
import ManualLayout from '../../../layouts/ManualLayout.astro';
|
||||||
|
import { allDocSlugs, docTitle, renderMarkdown } from '../../../lib/docs.js';
|
||||||
|
import '@fontsource/ibm-plex-sans-jp/latin-400.css';
|
||||||
|
import '@fontsource/ibm-plex-sans-jp/latin-600.css';
|
||||||
|
|
||||||
|
export function getStaticPaths() {
|
||||||
|
return allDocSlugs('en').map((slug) => ({
|
||||||
|
params: { slug },
|
||||||
|
props: { slug },
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const { slug } = Astro.props;
|
||||||
|
const html = renderMarkdown(slug, 'en');
|
||||||
|
const title = docTitle(slug, 'en');
|
||||||
|
---
|
||||||
|
<ManualLayout title={`${title} — Decodal Manual`} active={slug} locale="en">
|
||||||
|
<article class="markdown" set:html={html} />
|
||||||
|
</ManualLayout>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
import ManualLayout from '../../../layouts/ManualLayout.astro';
|
||||||
|
import { renderMarkdown } from '../../../lib/docs.js';
|
||||||
|
import '@fontsource/ibm-plex-sans-jp/latin-400.css';
|
||||||
|
import '@fontsource/ibm-plex-sans-jp/latin-600.css';
|
||||||
|
|
||||||
|
const html = renderMarkdown('index', 'en');
|
||||||
|
---
|
||||||
|
<ManualLayout
|
||||||
|
title="Decodal Manual"
|
||||||
|
description="The Decodal language specification and public APIs for embedding it in applications."
|
||||||
|
active="index"
|
||||||
|
locale="en"
|
||||||
|
>
|
||||||
|
<article class="markdown" set:html={html} />
|
||||||
|
</ManualLayout>
|
||||||
@@ -1,11 +1,3 @@
|
|||||||
---
|
---
|
||||||
import ManualLayout from '../../layouts/ManualLayout.astro';
|
return Astro.redirect('/docs/en/');
|
||||||
import { renderMarkdown } from '../../lib/docs.js';
|
|
||||||
import '@fontsource/ibm-plex-sans-jp/400.css';
|
|
||||||
import '@fontsource/ibm-plex-sans-jp/600.css';
|
|
||||||
|
|
||||||
const html = renderMarkdown('index');
|
|
||||||
---
|
---
|
||||||
<ManualLayout title="Decodal Manual" description="Decodalの言語仕様と、アプリケーションへ組み込むための公開API。" active="index">
|
|
||||||
<article class="markdown" set:html={html} />
|
|
||||||
</ManualLayout>
|
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
import ManualLayout from '../../../layouts/ManualLayout.astro';
|
||||||
|
import { allDocSlugs, docTitle, renderMarkdown } from '../../../lib/docs.js';
|
||||||
|
import '@fontsource/ibm-plex-sans-jp/400.css';
|
||||||
|
import '@fontsource/ibm-plex-sans-jp/600.css';
|
||||||
|
|
||||||
|
export function getStaticPaths() {
|
||||||
|
return allDocSlugs('ja').map((slug) => ({
|
||||||
|
params: { slug },
|
||||||
|
props: { slug },
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const { slug } = Astro.props;
|
||||||
|
const html = renderMarkdown(slug, 'ja');
|
||||||
|
const title = docTitle(slug, 'ja');
|
||||||
|
---
|
||||||
|
<ManualLayout title={`${title} — Decodalマニュアル`} active={slug} locale="ja">
|
||||||
|
<article class="markdown" set:html={html} />
|
||||||
|
</ManualLayout>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
import ManualLayout from '../../../layouts/ManualLayout.astro';
|
||||||
|
import { renderMarkdown } from '../../../lib/docs.js';
|
||||||
|
import '@fontsource/ibm-plex-sans-jp/400.css';
|
||||||
|
import '@fontsource/ibm-plex-sans-jp/600.css';
|
||||||
|
|
||||||
|
const html = renderMarkdown('index', 'ja');
|
||||||
|
---
|
||||||
|
<ManualLayout
|
||||||
|
title="Decodalマニュアル"
|
||||||
|
description="Decodalの言語仕様と、アプリケーションへ組み込むための公開API。"
|
||||||
|
active="index"
|
||||||
|
locale="ja"
|
||||||
|
>
|
||||||
|
<article class="markdown" set:html={html} />
|
||||||
|
</ManualLayout>
|
||||||
Reference in New Issue
Block a user