KFL
- Nominal Typing
- Trait-Based
- Two-Level
- Decode-Encode Duality
- Query System
KDL
- KDL as token tree (≠ AST, ≠ token stream)
- KDL is new string, new literal. Nodes are static just like string literals but have more structure than old literals
- Those raw literals (nodes) can be exposed to your final structures
- By that, it's not 'multi-staged'
- KDL as algebraic data type with exponential
TODO
Designing Basics
-
Delete
DecodeChildren flatten-
Implement
DecodeScalarfor struct as the replacement offlatten(properties)and supportflatten(arguments)equivalent as well? - flatten for enums
-
Implement
- Detect name conflicts between fields in the same struct
- property enum or union?
Documentation
Source Code Quality
Testing
-
Span
-
Should wrap Box
?
-
Should wrap Box
- Understand error categories
-
Rename
parse_todecode_andprint_toencode_in tests? - Use previous version of KFL itself when testing grammar, instead of serde/serde_json
Encoding
-
Encoding should have ways of recovering quatiented styling/formatting
Scalars
- Instad of predetermining strings or patterns of chars valid for scalar values, determine syntactical separation condition between meta structure and scalar representations. This would give us possible scope of scalar notations, more relaxed ones if successful, and ability for scalar objects, in AST, to hold raw strings or slices to delay parsing them.
- Eliminate
FromStr,ToString,Display(as primary interfaces, as least)#![no_std]
- Compare TokenStream with Scalar, TokenTree with Node
Specials
-
-
Introduction
|
|
|
| structs | ||
#[derive(Decode)] struct Node0; |
#[derive(Decode)]
struct Node1(
#[kfl(argument)] i32,
#[kfl(argument)] String
);
|
#[derive(Decode)]
struct Node2(
#[kfl(property(name = "a"))] i32,
#[kfl(property(name = "b"))] String
);
|
#[derive(Decode)]
struct Node1 {
#[kfl(argument)] a: i32,
#[kfl(argument)] b: String
}
|
#[derive(Decode)]
struct Node2 {
#[kfl(property)] a: i32,
#[kfl(property)] b: String
}
|
|
| enums | ||
#[derive(Decode)]
enum Node {
Node0,
Node1(
#[kfl(argument)] i32,
#[kfl(argument)] String
),
Node2(
#[kfl(property(name = "a"))] i32,
#[kfl(property(name = "b"))] String
)
}
|
||
#[derive(Decode)]
enum Node {
Node0,
Node1 {
#[kfl(argument)] a: i32,
#[kfl(argument)] b: String
},
Node2 {
#[kfl(property)] a: i32,
#[kfl(property)] b: String
}
}
|
||
Usage
Most common usage of this library is using derive and [parse] function:
use kfl::Decode; #[derive(Decode)] struct Config { #[kfl(children)] routes: Vec<Route>, #[kfl(children)] plugins: Vec<Plugin>, } #[derive(Decode)] struct Route { #[kfl(argument)] path: String, #[kfl(children)] subroutes: Vec<Route>, } #[derive(Decode)] struct Plugin { #[kfl(argument)] name: String, #[kfl(property)] url: String, } fn main() -> miette::Result<()> { let config = kfl::decode_children::<Config>("example.kdl", r#" route "/api" { route "/api/v1" } plugin "http" url="https://example.org/http" "#)?; Ok(()) }
This parses into a vector of nodes as enums Config, but you also use some node as a root of the document if there is no properties and arguments declared:
#[derive(Decode)]
struct Document {
#[kfl(child, unwrap(argument))]
version: Option<String>,
#[kfl(children)]
routes: Vec<Route>,
#[kfl(children)]
plugins: Vec<Plugin>,
}
let config = kfl::decode_children::<Document>("example.kdl", r#"
version "2.0"
route "/api" {
route "/api/v1"
}
plugin "http" url="https://example.org/http"
"#)?;
See description of Decode and DecodeScalar for the full reference on allowed attributes and parse modes.
Tutorial
Case 1: All Arguments
Case 1.1: With Different Types
node 1 "hoge"
Pattern 1.1.1: Tuple Struct
#![allow(unused)] fn main() { #[derive(Decode)] struct Node(#[kfl(argument)] i32, #[kfl(argument)] String); }
Pattern 1.1.2 Struct Struct
#![allow(unused)] fn main() { #[derive(Decode)] struct Node { #[kfl(argument)] a: i32, #[kfl(argument)] b: String } }
NOTE: a and b are redundant.
Case 1.2: With The Same Type
node 1 2
Pattern 1.2.1: Tuple Struct
#![allow(unused)] fn main() { #[derive(Decode)] struct Node(#[kfl(arguments)] Vec<i32>); }
Pattern 1.2.2 Struct Struct
#![allow(unused)] fn main() { #[derive(Decode)] struct Node { #[kfl(arguments)] a: Vec<i32> } }
NOTE: a is redundant.
Case 2: All Properties
Case 2.1: With Different Types
node a=1 b="hoge"
Pattern 2.1.1: Tuple Struct
#![allow(unused)] fn main() { #[derive(Decode)] struct Node(#[kfl(property(name = "a"))] i32, #[kfl(property(name = "b"))] String); }
NOTE: name = "a" and name = "b" are necessary.
Pattern 2.1.2: Struct Struct
#![allow(unused)] fn main() { #[derive(Decode)] struct Node { #[kfl(property)] a: i32, #[kfl(property)] b: String } }
Case 2.2: With The Same Type
node a=1 b=2
Pattern 2.2.1: Tuple Struct
#![allow(unused)] fn main() { #[derive(Decode)] struct Node(#[kfl(properties)] HashMap<String, i32>); }
Pattern 2.2.2: Struct Struct
#![allow(unused)] fn main() { #[derive(Decode)] struct Node { #[kfl(properties)] a: HashMap<String, i32> } }
NOTE: a is redundant.
Case 3: Mix of Singulars and A Plural
Case 3.1: Arguments
node 1 2 3
Pattern 3.1.1: Tuple Struct
#![allow(unused)] fn main() { #[derive(Decode)] struct Node(#[kfl(argument)] i32, #[kfl(arguments)] Vec<i32>); }
Pattern 3.1.2: Struct Struct
#![allow(unused)] fn main() { #[derive(Decode)] struct Node { #[kfl(argument)] a: i32, #[kfl(arguments)] b: Vec<i32> } }
Case 3.2: Properties
node a=1 b=2 c=3
Pattern 3.2.1: Tuple Struct
#![allow(unused)] fn main() { #[derive(Decode)] struct Node(#[kfl(property(name = "a")] i32, #[kfl(properties)] HashMap<String, i32>); }
NOTE: name = "a" is necessary.
Pattern 3.2.2: Struct Struct
#![allow(unused)] fn main() { #[derive(Decode)] struct Node { #[kfl(property)] a: i32, #[kfl(properties)] b: HashMap<String, i32> } }
Case 4: Mix of Arguments and Properties
Case 5: Child
node {
child 1
}
Pattern 5.: Tuple Struct
#![allow(unused)] fn main() { #[derive(Decode)] struct Node(#[kfl(child)] A); // Or Struct Struct #[derive(Decode)] struct A(#[kfl(argument)] i32); }
Pattern 5.: Struct Struct
#![allow(unused)] fn main() { #[derive(Decode)] struct Node { #[kfl(child)] child: A } // Or Struct Struct #[derive(Decode)] struct A(#[kfl(argument)] i32); }
NOTE: A is redundant.
Case ?: Children
Case ?: Alteration
node0 1
node1 "hoge"
#![allow(unused)] fn main() { #[derive(Decode)] enum Node { Node0(#[kfl(argument)] i32), Node1(#[kfl(argument)] String) } }
// #[derive(Debug, kfl::Decode)] // struct Document { // #[kfl(children(name = "plugin"))] // plugins: Vec<String>, // #[kfl(children(name = "file"))] // files: Vec<String>, // } // #[derive(Debug, kfl::Decode)] // struct Node { // #[kfl(property)] a: String, // #[kfl(property)] b: String, // // #[kfl(child)] c: Child // } // #[derive(Debug, kfl::Decode)] // struct Node { // #[kfl(argument)] a: String, // #[kfl(argument)] b: String, // #[kfl(child)] c: Child // } // #[derive(Debug, kfl::Decode)] // struct Child( // #[kfl(argument)] String, // #[kfl(argument)] String // ); fn main() { // let doc: Document = kfl::parse("test", &document).unwrap(); let doc: Vec<Node> = kfl::parse("test", &document).unwrap(); println!("{:?}", &doc); }
Rationale
| enums | struct | |
|---|---|---|
| error kinds | expected non-occurance | unexpected occurance |
Decode and Encode
Design scketch
| Decode | Encode |
|---|---|
check_type ('of course'-, !-modality) | declare_node ('why not'-, ?-modality) |
Appendix
knuffel
Misconceptions
- Children
- Not able to use enum as a single child
- Nominal
- Flatten properties (analogous to serde)
- NodeName
- TypeName
- type annotations for nodes and scalars are different
- bool child
DecodeChildren- Option vs Default
- form of type path doesn't matter
- unwrap
NewTypefor structs andNestedfor enums- Every data in KDL appears either as a scalar wrapped in a node or a node itself. Therefore every type can exsist only in an already
newtypeed form.
- Every data in KDL appears either as a scalar wrapped in a node or a node itself. Therefore every type can exsist only in an already
strandbytes→ simply implementDecodeScalarforSocketAddrandVec<u8>
Implementation
Change List
- Struct variants and tuple variants
DecodePartialcompatibility cannot automatically be detected (exceptUnituseless case)- Because we discard
optionparameter and also switch to manually implementing it forOption<T>andVec<T> - Beautiful fact is that default
Decodebehaviour anyway coerceTintoOption<T>whenChildMode::NormalorVec<T>whenChildMode::Multiat decoding (and then unwrap or into_iter.collect)
- Because we discard
- Removed
DecodeMode DecodeScalar- Separation of
check_typeandraw_decodeis inefficient when implementingbytesforVec<u8>- We need to return default values
- We can expect more that one kind of value, so DecodeError::ScalarKind cannot expect one fixed Kind
- Separation of
- Removed
Documentstruct as root of nodes-
Make
DecodeChildrensealed?
-
Make
- Removed
Spanned, nowContexthas spans map, prepared at parsing- To avoid spreading
DUMMY_SPkind everywhere when implementEncode
- To avoid spreading
Spannow our AST is span-free, spans are accomodated in contextSpanis to protect from being dereferencedAsRefto hold pointer identity- Instead of
Spanned<Box<str>, S>, justSpan(withContextincluding the input text) suffices. Another beautiful fact. - chumsky now operates on its own Input trait that has an associated type
Span, so we cannot be passive aboutSexpecting only some specific set of traits consisting of third party's ones (Into<miette::SourceSpan>).- In addition to that, each span type from different crates anyway resembles at all and conversion is direct as well. So let's be bold to carry around our own.
- Now that users can get spans from
Contextreturned fromdecode, removedspanandspan_typedirectives and correspoinding fields from ast. - Now
DecodePatialcompatibility andDecodeChildencompatibility coincide. Contextis not primarily for stacking erros, the need is partially met byDecodePartial