diff --git a/doc/develop/README.md b/doc/develop/README.md index 7512d70..6d93cbd 100644 --- a/doc/develop/README.md +++ b/doc/develop/README.md @@ -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. - `editors/lezer-decodal` and `editors/tree-sitter-decodal`: downstream editor grammars. - `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 @@ -43,7 +44,7 @@ Use `npm --prefix site/decodal-site run build:wasm` when Rust bindings or format 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. 3. Update the Lezer and Tree-sitter grammars and regenerate their committed outputs. 4. Update formatter, playground, and language-service tests as applicable. diff --git a/doc/manual/source/components.md b/doc/manual/source/components.md new file mode 100644 index 0000000..a1a8f88 --- /dev/null +++ b/doc/manual/source/components.md @@ -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. diff --git a/doc/manual/source/embedding.md b/doc/manual/source/embedding.md new file mode 100644 index 0000000..42330fa --- /dev/null +++ b/doc/manual/source/embedding.md @@ -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, +} +``` + +`BTreeMap` corresponds to `...Unknown`, while `BTreeMap` 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, + ) -> 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. diff --git a/doc/manual/source/index.md b/doc/manual/source/index.md new file mode 100644 index 0000000..bf8ece9 --- /dev/null +++ b/doc/manual/source/index.md @@ -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) diff --git a/doc/manual/source/introduction.md b/doc/manual/source/introduction.md new file mode 100644 index 0000000..1130889 --- /dev/null +++ b/doc/manual/source/introduction.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. diff --git a/doc/manual/souce/components.md b/doc/manual/source/jp/components.md similarity index 100% rename from doc/manual/souce/components.md rename to doc/manual/source/jp/components.md diff --git a/doc/manual/souce/embedding.md b/doc/manual/source/jp/embedding.md similarity index 100% rename from doc/manual/souce/embedding.md rename to doc/manual/source/jp/embedding.md diff --git a/doc/manual/souce/index.md b/doc/manual/source/jp/index.md similarity index 100% rename from doc/manual/souce/index.md rename to doc/manual/source/jp/index.md diff --git a/doc/manual/souce/introduction.md b/doc/manual/source/jp/introduction.md similarity index 100% rename from doc/manual/souce/introduction.md rename to doc/manual/source/jp/introduction.md diff --git a/doc/manual/souce/language/constraints-and-defaults.md b/doc/manual/source/jp/language/constraints-and-defaults.md similarity index 100% rename from doc/manual/souce/language/constraints-and-defaults.md rename to doc/manual/source/jp/language/constraints-and-defaults.md diff --git a/doc/manual/souce/language/evaluation.md b/doc/manual/source/jp/language/evaluation.md similarity index 100% rename from doc/manual/souce/language/evaluation.md rename to doc/manual/source/jp/language/evaluation.md diff --git a/doc/manual/souce/language/examples.md b/doc/manual/source/jp/language/examples.md similarity index 100% rename from doc/manual/souce/language/examples.md rename to doc/manual/source/jp/language/examples.md diff --git a/doc/manual/source/jp/language/expression/arithmetic.md b/doc/manual/source/jp/language/expression/arithmetic.md new file mode 100644 index 0000000..8154a81 --- /dev/null +++ b/doc/manual/source/jp/language/expression/arithmetic.md @@ -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; +``` diff --git a/doc/manual/souce/language/expression/array.md b/doc/manual/source/jp/language/expression/array.md similarity index 100% rename from doc/manual/souce/language/expression/array.md rename to doc/manual/source/jp/language/expression/array.md diff --git a/doc/manual/souce/language/expression/ascription.md b/doc/manual/source/jp/language/expression/ascription.md similarity index 100% rename from doc/manual/souce/language/expression/ascription.md rename to doc/manual/source/jp/language/expression/ascription.md diff --git a/doc/manual/souce/language/expression/composition.md b/doc/manual/source/jp/language/expression/composition.md similarity index 100% rename from doc/manual/souce/language/expression/composition.md rename to doc/manual/source/jp/language/expression/composition.md diff --git a/doc/manual/souce/language/expression/default.md b/doc/manual/source/jp/language/expression/default.md similarity index 100% rename from doc/manual/souce/language/expression/default.md rename to doc/manual/source/jp/language/expression/default.md diff --git a/doc/manual/souce/language/expression/function-call.md b/doc/manual/source/jp/language/expression/function-call.md similarity index 100% rename from doc/manual/souce/language/expression/function-call.md rename to doc/manual/source/jp/language/expression/function-call.md diff --git a/doc/manual/souce/language/expression/function.md b/doc/manual/source/jp/language/expression/function.md similarity index 100% rename from doc/manual/souce/language/expression/function.md rename to doc/manual/source/jp/language/expression/function.md diff --git a/doc/manual/souce/language/expression/identifier.md b/doc/manual/source/jp/language/expression/identifier.md similarity index 100% rename from doc/manual/souce/language/expression/identifier.md rename to doc/manual/source/jp/language/expression/identifier.md diff --git a/doc/manual/souce/language/expression/import.md b/doc/manual/source/jp/language/expression/import.md similarity index 100% rename from doc/manual/souce/language/expression/import.md rename to doc/manual/source/jp/language/expression/import.md diff --git a/doc/manual/souce/language/expression/index.md b/doc/manual/source/jp/language/expression/index.md similarity index 100% rename from doc/manual/souce/language/expression/index.md rename to doc/manual/source/jp/language/expression/index.md diff --git a/doc/manual/souce/language/expression/let.md b/doc/manual/source/jp/language/expression/let.md similarity index 100% rename from doc/manual/souce/language/expression/let.md rename to doc/manual/source/jp/language/expression/let.md diff --git a/doc/manual/souce/language/expression/literal.md b/doc/manual/source/jp/language/expression/literal.md similarity index 100% rename from doc/manual/souce/language/expression/literal.md rename to doc/manual/source/jp/language/expression/literal.md diff --git a/doc/manual/source/jp/language/expression/logical-and-comparison.md b/doc/manual/source/jp/language/expression/logical-and-comparison.md new file mode 100644 index 0000000..6eafc55 --- /dev/null +++ b/doc/manual/source/jp/language/expression/logical-and-comparison.md @@ -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; +``` diff --git a/doc/manual/souce/language/expression/match.md b/doc/manual/source/jp/language/expression/match.md similarity index 100% rename from doc/manual/souce/language/expression/match.md rename to doc/manual/source/jp/language/expression/match.md diff --git a/doc/manual/souce/language/expression/object.md b/doc/manual/source/jp/language/expression/object.md similarity index 100% rename from doc/manual/souce/language/expression/object.md rename to doc/manual/source/jp/language/expression/object.md diff --git a/doc/manual/souce/language/expression/path-reference.md b/doc/manual/source/jp/language/expression/path-reference.md similarity index 100% rename from doc/manual/souce/language/expression/path-reference.md rename to doc/manual/source/jp/language/expression/path-reference.md diff --git a/doc/manual/souce/language/functions.md b/doc/manual/source/jp/language/functions.md similarity index 100% rename from doc/manual/souce/language/functions.md rename to doc/manual/source/jp/language/functions.md diff --git a/doc/manual/source/jp/language/grammar.md b/doc/manual/source/jp/language/grammar.md new file mode 100644 index 0000000..34a8bb0 --- /dev/null +++ b/doc/manual/source/jp/language/grammar.md @@ -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` だけは右結合である。 diff --git a/doc/manual/souce/language/index.md b/doc/manual/source/jp/language/index.md similarity index 100% rename from doc/manual/souce/language/index.md rename to doc/manual/source/jp/language/index.md diff --git a/doc/manual/souce/language/materialization-and-errors.md b/doc/manual/source/jp/language/materialization-and-errors.md similarity index 100% rename from doc/manual/souce/language/materialization-and-errors.md rename to doc/manual/source/jp/language/materialization-and-errors.md diff --git a/doc/manual/souce/language/modules-and-imports.md b/doc/manual/source/jp/language/modules-and-imports.md similarity index 100% rename from doc/manual/souce/language/modules-and-imports.md rename to doc/manual/source/jp/language/modules-and-imports.md diff --git a/doc/manual/souce/language/naming.md b/doc/manual/source/jp/language/naming.md similarity index 100% rename from doc/manual/souce/language/naming.md rename to doc/manual/source/jp/language/naming.md diff --git a/doc/manual/souce/language/operators.md b/doc/manual/source/jp/language/operators.md similarity index 100% rename from doc/manual/souce/language/operators.md rename to doc/manual/source/jp/language/operators.md diff --git a/doc/manual/souce/language/syntax.md b/doc/manual/source/jp/language/syntax.md similarity index 100% rename from doc/manual/souce/language/syntax.md rename to doc/manual/source/jp/language/syntax.md diff --git a/doc/manual/souce/language/value/bool.md b/doc/manual/source/jp/language/value/bool.md similarity index 100% rename from doc/manual/souce/language/value/bool.md rename to doc/manual/source/jp/language/value/bool.md diff --git a/doc/manual/souce/language/value/float.md b/doc/manual/source/jp/language/value/float.md similarity index 100% rename from doc/manual/souce/language/value/float.md rename to doc/manual/source/jp/language/value/float.md diff --git a/doc/manual/souce/language/value/index.md b/doc/manual/source/jp/language/value/index.md similarity index 100% rename from doc/manual/souce/language/value/index.md rename to doc/manual/source/jp/language/value/index.md diff --git a/doc/manual/souce/language/value/int.md b/doc/manual/source/jp/language/value/int.md similarity index 100% rename from doc/manual/souce/language/value/int.md rename to doc/manual/source/jp/language/value/int.md diff --git a/doc/manual/souce/language/value/string.md b/doc/manual/source/jp/language/value/string.md similarity index 100% rename from doc/manual/souce/language/value/string.md rename to doc/manual/source/jp/language/value/string.md diff --git a/doc/manual/source/language/constraints-and-defaults.md b/doc/manual/source/language/constraints-and-defaults.md new file mode 100644 index 0000000..0074edf --- /dev/null +++ b/doc/manual/source/language/constraints-and-defaults.md @@ -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. diff --git a/doc/manual/source/language/evaluation.md b/doc/manual/source/language/evaluation.md new file mode 100644 index 0000000..df1a14a --- /dev/null +++ b/doc/manual/source/language/evaluation.md @@ -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. diff --git a/doc/manual/source/language/examples.md b/doc/manual/source/language/examples.md new file mode 100644 index 0000000..84292aa --- /dev/null +++ b/doc/manual/source/language/examples.md @@ -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`. diff --git a/doc/manual/souce/language/expression/arithmetic.md b/doc/manual/source/language/expression/arithmetic.md similarity index 100% rename from doc/manual/souce/language/expression/arithmetic.md rename to doc/manual/source/language/expression/arithmetic.md diff --git a/doc/manual/source/language/expression/array.md b/doc/manual/source/language/expression/array.md new file mode 100644 index 0000000..b05729e --- /dev/null +++ b/doc/manual/source/language/expression/array.md @@ -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. diff --git a/doc/manual/source/language/expression/ascription.md b/doc/manual/source/language/expression/ascription.md new file mode 100644 index 0000000..5165683 --- /dev/null +++ b/doc/manual/source/language/expression/ascription.md @@ -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`. diff --git a/doc/manual/source/language/expression/composition.md b/doc/manual/source/language/expression/composition.md new file mode 100644 index 0000000..fb7d830 --- /dev/null +++ b/doc/manual/source/language/expression/composition.md @@ -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. diff --git a/doc/manual/source/language/expression/default.md b/doc/manual/source/language/expression/default.md new file mode 100644 index 0000000..92d02e5 --- /dev/null +++ b/doc/manual/source/language/expression/default.md @@ -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. diff --git a/doc/manual/source/language/expression/function-call.md b/doc/manual/source/language/expression/function-call.md new file mode 100644 index 0000000..ddb736c --- /dev/null +++ b/doc/manual/source/language/expression/function-call.md @@ -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. diff --git a/doc/manual/source/language/expression/function.md b/doc/manual/source/language/expression/function.md new file mode 100644 index 0000000..a6381dd --- /dev/null +++ b/doc/manual/source/language/expression/function.md @@ -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. diff --git a/doc/manual/source/language/expression/identifier.md b/doc/manual/source/language/expression/identifier.md new file mode 100644 index 0000000..166563f --- /dev/null +++ b/doc/manual/source/language/expression/identifier.md @@ -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. diff --git a/doc/manual/source/language/expression/import.md b/doc/manual/source/language/expression/import.md new file mode 100644 index 0000000..afb49eb --- /dev/null +++ b/doc/manual/source/language/expression/import.md @@ -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. diff --git a/doc/manual/source/language/expression/index.md b/doc/manual/source/language/expression/index.md new file mode 100644 index 0000000..d8b91bc --- /dev/null +++ b/doc/manual/source/language/expression/index.md @@ -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). diff --git a/doc/manual/source/language/expression/let.md b/doc/manual/source/language/expression/let.md new file mode 100644 index 0000000..24760da --- /dev/null +++ b/doc/manual/source/language/expression/let.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. diff --git a/doc/manual/source/language/expression/literal.md b/doc/manual/source/language/expression/literal.md new file mode 100644 index 0000000..c6dd96e --- /dev/null +++ b/doc/manual/source/language/expression/literal.md @@ -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`. diff --git a/doc/manual/souce/language/expression/logical-and-comparison.md b/doc/manual/source/language/expression/logical-and-comparison.md similarity index 100% rename from doc/manual/souce/language/expression/logical-and-comparison.md rename to doc/manual/source/language/expression/logical-and-comparison.md diff --git a/doc/manual/source/language/expression/match.md b/doc/manual/source/language/expression/match.md new file mode 100644 index 0000000..b2f5dfc --- /dev/null +++ b/doc/manual/source/language/expression/match.md @@ -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. diff --git a/doc/manual/source/language/expression/object.md b/doc/manual/source/language/expression/object.md new file mode 100644 index 0000000..08af9af --- /dev/null +++ b/doc/manual/source/language/expression/object.md @@ -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. diff --git a/doc/manual/source/language/expression/path-reference.md b/doc/manual/source/language/expression/path-reference.md new file mode 100644 index 0000000..188676b --- /dev/null +++ b/doc/manual/source/language/expression/path-reference.md @@ -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. diff --git a/doc/manual/source/language/functions.md b/doc/manual/source/language/functions.md new file mode 100644 index 0000000..b262bb1 --- /dev/null +++ b/doc/manual/source/language/functions.md @@ -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. diff --git a/doc/manual/souce/language/grammar.md b/doc/manual/source/language/grammar.md similarity index 100% rename from doc/manual/souce/language/grammar.md rename to doc/manual/source/language/grammar.md diff --git a/doc/manual/source/language/index.md b/doc/manual/source/language/index.md new file mode 100644 index 0000000..f74a442 --- /dev/null +++ b/doc/manual/source/language/index.md @@ -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. diff --git a/doc/manual/source/language/materialization-and-errors.md b/doc/manual/source/language/materialization-and-errors.md new file mode 100644 index 0000000..b988761 --- /dev/null +++ b/doc/manual/source/language/materialization-and-errors.md @@ -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. diff --git a/doc/manual/source/language/modules-and-imports.md b/doc/manual/source/language/modules-and-imports.md new file mode 100644 index 0000000..50ad7a1 --- /dev/null +++ b/doc/manual/source/language/modules-and-imports.md @@ -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. diff --git a/doc/manual/source/language/naming.md b/doc/manual/source/language/naming.md new file mode 100644 index 0000000..56826ec --- /dev/null +++ b/doc/manual/source/language/naming.md @@ -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; }; +``` diff --git a/doc/manual/source/language/operators.md b/doc/manual/source/language/operators.md new file mode 100644 index 0000000..9b26df2 --- /dev/null +++ b/doc/manual/source/language/operators.md @@ -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; +``` diff --git a/doc/manual/source/language/syntax.md b/doc/manual/source/language/syntax.md new file mode 100644 index 0000000..2adf69f --- /dev/null +++ b/doc/manual/source/language/syntax.md @@ -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). diff --git a/doc/manual/source/language/value/bool.md b/doc/manual/source/language/value/bool.md new file mode 100644 index 0000000..9d4e45e --- /dev/null +++ b/doc/manual/source/language/value/bool.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 +``` diff --git a/doc/manual/source/language/value/float.md b/doc/manual/source/language/value/float.md new file mode 100644 index 0000000..73f8e65 --- /dev/null +++ b/doc/manual/source/language/value/float.md @@ -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. diff --git a/doc/manual/source/language/value/index.md b/doc/manual/source/language/value/index.md new file mode 100644 index 0000000..f9b79dc --- /dev/null +++ b/doc/manual/source/language/value/index.md @@ -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. diff --git a/doc/manual/source/language/value/int.md b/doc/manual/source/language/value/int.md new file mode 100644 index 0000000..91c16f4 --- /dev/null +++ b/doc/manual/source/language/value/int.md @@ -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; +``` diff --git a/doc/manual/source/language/value/string.md b/doc/manual/source/language/value/string.md new file mode 100644 index 0000000..437967a --- /dev/null +++ b/doc/manual/source/language/value/string.md @@ -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. diff --git a/editors/lezer-decodal/README.md b/editors/lezer-decodal/README.md index a8ce2fe..cef97cc 100644 --- a/editors/lezer-decodal/README.md +++ b/editors/lezer-decodal/README.md @@ -5,7 +5,7 @@ Lezer implementation of the Decodal grammar for CodeMirror 6. The canonical grammar is documented in: ```text -../../doc/manual/souce/language/grammar.md +../../doc/manual/source/language/grammar.md ``` Regenerate the CodeMirror parser from the site directory: diff --git a/editors/tree-sitter-decodal/README.md b/editors/tree-sitter-decodal/README.md index ee39790..13f38ea 100644 --- a/editors/tree-sitter-decodal/README.md +++ b/editors/tree-sitter-decodal/README.md @@ -5,7 +5,7 @@ Tree-sitter implementation of the Decodal grammar. The canonical grammar is documented in: ```text -../../doc/manual/souce/language/grammar.md +../../doc/manual/source/language/grammar.md ``` This grammar is an editor/tooling implementation of that EBNF reference. diff --git a/editors/tree-sitter-decodal/grammar.js b/editors/tree-sitter-decodal/grammar.js index 54d27aa..587b255 100644 --- a/editors/tree-sitter-decodal/grammar.js +++ b/editors/tree-sitter-decodal/grammar.js @@ -1,5 +1,5 @@ // Tree-sitter implementation of the canonical EBNF grammar in -// doc/manual/souce/language/grammar.md. +// doc/manual/source/language/grammar.md. const PREC = { AS: 0, diff --git a/site/decodal-site/src/components/HomePage.astro b/site/decodal-site/src/components/HomePage.astro index 2b48aeb..11c56d6 100644 --- a/site/decodal-site/src/components/HomePage.astro +++ b/site/decodal-site/src/components/HomePage.astro @@ -3,6 +3,7 @@ import ManualLayout from '../layouts/ManualLayout.astro'; import { highlightCode } from '../lib/highlight.js'; const { locale = 'en' } = Astro.props; +const docsLocale = locale === 'ja' ? 'ja' : 'en'; const translations = { en: { @@ -121,7 +122,7 @@ const result = `{

{copy.facts.map(([label, value], index) => ( @@ -177,7 +178,7 @@ const result = `{ ))} - +
@@ -206,6 +207,6 @@ const result = `{ JSRnpm - +
diff --git a/site/decodal-site/src/layouts/ManualLayout.astro b/site/decodal-site/src/layouts/ManualLayout.astro index 70f5522..d232ff6 100644 --- a/site/decodal-site/src/layouts/ManualLayout.astro +++ b/site/decodal-site/src/layouts/ManualLayout.astro @@ -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-600.css'; import '../style.css'; const { title = 'Decodal', - description = 'Decodal is a deterministic data language for configuration, constraints, and defaults.', + description, active = '', playground = false, frontpage = false, @@ -14,11 +14,24 @@ const { } = Astro.props; 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 alternateHref = language === 'ja' ? '/' : '/ja/'; +const alternateHref = frontpage + ? language === 'ja' ? '/' : '/ja/' + : `/docs/${otherDocsLocale}/${localizedDocPath}`; const alternateLabel = language === 'ja' ? '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) { return pathname === path || (path !== '/' && pathname.startsWith(path)); @@ -28,7 +41,7 @@ function renderNav(items, prefix = []) { return `