Add bilingual manual and localized docs routes

This commit is contained in:
2026-08-14 11:16:37 +09:00
parent 3645a2cc2d
commit cda32cc260
85 changed files with 2316 additions and 71 deletions
+3 -2
View File
@@ -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.
+81
View File
@@ -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.
+193
View File
@@ -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.
+42
View File
@@ -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)
+54
View File
@@ -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;
```
+113
View File
@@ -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.
+48
View File
@@ -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.
+166
View File
@@ -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.
+41
View File
@@ -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.
+12
View File
@@ -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.
+17
View File
@@ -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; };
```
+250
View File
@@ -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;
```
+129
View File
@@ -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).
+19
View File
@@ -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
```
+18
View File
@@ -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.
+20
View File
@@ -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.
+18
View File
@@ -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.
+1 -1
View File
@@ -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:
+1 -1
View File
@@ -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 -1
View File
@@ -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>
+106 -22
View File
@@ -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 -9
View File
@@ -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>