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 DecodeScalar for struct as the replacement of flatten(properties) and support flatten(arguments) equivalent as well?
    • flatten for enums
  • Detect name conflicts between fields in the same struct
  • property enum or union?

Documentation

Source Code Quality

Testing

  • Span
    • Should wrap Box?
  • Understand error categories
  • Rename parse_ to decode_ and print_ to encode_ 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

node0
node1 1 "hoge"
node2 a=1 b="hoge"
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

enumsstruct
error kindsexpected non-occuranceunexpected occurance

Decode and Encode

Design scketch

DecodeEncode
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
  • NewType for structs and Nested for 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.
  • str and bytes → simply implement DecodeScalar for SocketAddr and Vec<u8>

Implementation

Change List

  • Struct variants and tuple variants
  • DecodePartial compatibility cannot automatically be detected (except Unit useless case)
    • Because we discard option parameter and also switch to manually implementing it for Option<T> and Vec<T>
    • Beautiful fact is that default Decode behaviour anyway coerce T into Option<T> when ChildMode::Normal or Vec<T> when ChildMode::Multi at decoding (and then unwrap or into_iter.collect)
  • Removed DecodeMode
  • DecodeScalar
    • Separation of check_type and raw_decode is inefficient when implementing bytes for Vec<u8>
      • We need to return default values
    • We can expect more that one kind of value, so DecodeError::ScalarKind cannot expect one fixed Kind
  • Removed Document struct as root of nodes
    • Make DecodeChildren sealed?
  • Removed Spanned, now Context has spans map, prepared at parsing
    • To avoid spreading DUMMY_SP kind everywhere when implementEncode
  • Span now our AST is span-free, spans are accomodated in context
    • Span is to protect from being dereferenced AsRef to hold pointer identity
    • Instead of Spanned<Box<str>, S>, just Span (with Context including 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 about S expecting 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 Context returned from decode, removed span and span_type directives and correspoinding fields from ast.
    • Now DecodePatial compatibility and DecodeChilden compatibility coincide.
    • Context is not primarily for stacking erros, the need is partially met by DecodePartial