Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
644 changes: 361 additions & 283 deletions Cargo.lock

Large diffs are not rendered by default.

9 changes: 3 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,32 +49,29 @@ indexmap = { version = "2.7.1", features = ["serde"] }
# Error handling
thiserror = "2.0.12"

# Utilities
cfg-if = "1.0.0"

# WASM support (optional)
wasm-bindgen = { version = "0.2.100", optional = true }
serde-wasm-bindgen = { version = "0.6.5", optional = true }
console_error_panic_hook = { version = "0.1.7", optional = true }
wee_alloc = { version = "0.4.5", optional = true }

# Lua support (optional)
mlua = { version = "0.10.3", features = [
mlua = { version = "0.11.5", features = [
"module",
"macros",
"serialize",
], optional = true }

# Benchmarking (optional)
criterion = { version = "0.5.1", features = ["html_reports"], optional = true }
criterion = { version = "0.8.1", features = ["html_reports"], optional = true }

[dev-dependencies]
paste = "1.0.15"
wasm-bindgen-test = { version = "0.3.50" }
serde_json = "1.0.140"
serde_norway = "0.9.42"
serde_bytes = "0.11.16"
toml_edit = { version = "0.22.24", features = ["serde"] }
toml_edit = { version = "0.24.0", features = ["serde"] }

[profile.release]
lto = true
Expand Down
3 changes: 3 additions & 0 deletions assets/inputs/numeric_keys.corn
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
foo = { 0 = "low" 33 = "medium" 67 = "high" }
}
7 changes: 7 additions & 0 deletions assets/outputs/json/numeric_keys.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"foo": {
"0": "low",
"33": "medium",
"67": "high"
}
}
4 changes: 4 additions & 0 deletions assets/outputs/toml/numeric_keys.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[foo]
0 = "low"
33 = "medium"
67 = "high"
5 changes: 5 additions & 0 deletions assets/outputs/yaml/numeric_keys.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
foo:
0: low
33: medium
67: high

5 changes: 2 additions & 3 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description = "CLI for Corn. A simple and pain-free configuration language."
repository = "https://github.com/corn-config/corn"
categories = ["config", "command-line-utilities"]
keywords = ["configuration", "language", "pest", "peg", "cli"]
authors = ["Jake Stanger <mail@jakestanger.com>"]
authors = ["Jake Stanger <mail@jstanger.dev>"]
readme = "README.md"
homepage = "https://cornlang.dev/"
documentation = "https://docs.rs/corn-cli"
Expand All @@ -18,8 +18,7 @@ clap = { version = "4.5.31", features = ["derive", "cargo"] }
colored = "3.0.0"

# (De)serialization
serde = { version = "1.0.218", features = ["derive"] }
serde_json = "1.0.140"
serde_norway = "0.9.42"
libcorn = { version = "0.10.0", path = ".." }
toml_edit = { version = "0.22.24", features = ["serde"] }
toml_edit = { version = "0.24.0", features = ["serde"] }
4 changes: 2 additions & 2 deletions src/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ impl<'de> Map<'de> {
Value::Object(values) => Self {
values: values
.into_iter()
.flat_map(|(key, value)| vec![Value::String(key), value])
.flat_map(|(key, value)| vec![key.into(), value])
.collect(),
},
_ => unreachable!(),
Expand Down Expand Up @@ -474,7 +474,7 @@ impl<'de> EnumAccess<'de> for Enum<'de> {
Value::Object(obj) => {
let first_pair = obj.into_iter().next();
if let Some(first_pair) = first_pair {
let value = Value::String(first_pair.0);
let value = first_pair.0.into();
let tag = seed.deserialize(&mut Deserializer::from_value(value))?;
Ok((tag, Variant::new(Some(first_pair.1))))
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/grammar.pest
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ path = ${
}

path_seg = _{
quoted_path_seg | regular_path_seg
integer | quoted_path_seg | regular_path_seg
}

quoted_path_seg = ${ "'" ~ quoted_path_val ~ "'" }
Expand Down
31 changes: 30 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,37 @@ mod wasm;
/// The names include their `$` prefix.
pub type Inputs<'a> = HashMap<&'a str, Value<'a>>;

#[derive(Serialize, Debug, Clone, PartialEq, Eq, Hash)]
#[serde(untagged)]
pub enum Key<'a> {
String(Cow<'a, str>),
Integer(i64),
}

impl Display for Key<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Key::String(val) => val.to_string(),
Key::Integer(val) => val.to_string(),
}
)
}
}

impl<'a> From<Key<'a>> for Value<'a> {
fn from(value: Key<'a>) -> Self {
match value {
Key::String(val) => Value::String(val),
Key::Integer(val) => Value::Integer(val),
}
}
}

/// A map of keys to their values.
pub type Object<'a> = IndexMap<Cow<'a, str>, Value<'a>>;
pub type Object<'a> = IndexMap<Key<'a>, Value<'a>>;

#[derive(Serialize, Debug, Clone)]
#[serde(untagged)]
Expand Down
30 changes: 16 additions & 14 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use pest::iterators::Pair;
use pest::Parser;

use crate::error::{Error, Result};
use crate::{Inputs, Object, Value};
use crate::{Inputs, Key, Object, Value};

#[derive(pest_derive::Parser)]
#[grammar = "grammar.pest"]
Expand Down Expand Up @@ -121,7 +121,7 @@ impl<'a> CornParser<'a> {
}
}
_ => unreachable!(),
};
}
}

let full_string = if full_string.contains('\n') {
Expand Down Expand Up @@ -184,7 +184,7 @@ impl<'a> CornParser<'a> {
}
}
_ => arr.push(self.parse_value(pair)?),
};
}
}

Ok(arr)
Expand Down Expand Up @@ -240,17 +240,18 @@ impl<'a> CornParser<'a> {
Ok(obj)
}

fn parse_path(path: Pair<Rule>) -> Vec<Cow<str>> {
fn parse_path(path: Pair<Rule>) -> Vec<Key> {
path.into_inner()
.map(|pair| match pair.as_rule() {
Rule::regular_path_seg => Cow::Borrowed(pair.as_str()),
Rule::quoted_path_seg => Cow::Owned(
Rule::regular_path_seg => Key::String(Cow::Borrowed(pair.as_str())),
Rule::quoted_path_seg => Key::String(Cow::Owned(
pair.into_inner()
.next()
.expect("quoted paths should contain an inner value")
.as_str()
.replace('\\', ""),
),
)),
Rule::integer => Key::Integer(Self::parse_integer(pair)),
_ => unreachable!(),
})
.collect::<Vec<_>>()
Expand All @@ -262,11 +263,7 @@ impl<'a> CornParser<'a> {
/// for example `foo.bar` is represented as `["foo", "bar"]`.
///
/// Objects are created up to the required depth recursively.
fn add_at_path(
mut obj: Object<'a>,
path: &[Cow<'a, str>],
value: Value<'a>,
) -> Result<Object<'a>> {
fn add_at_path(mut obj: Object<'a>, path: &[Key<'a>], value: Value<'a>) -> Result<Object<'a>> {
let (part, path_rest) = path
.split_first()
.expect("paths should contain at least 1 segment");
Expand All @@ -289,7 +286,12 @@ impl<'a> CornParser<'a> {

Ok(obj)
}
_ => Err(Error::InvalidPathError(path.join("."))),
_ => Err(Error::InvalidPathError(
path.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join("."),
)),
}
}

Expand Down Expand Up @@ -400,7 +402,7 @@ fn trim_multiline_string(string: &str) -> String {
/// If the internal AST parser produces a tree in an invalid structure,
/// the function will panic.
/// This indicates a severe error in the library and should never occur.
pub fn parse(file: &str) -> Result<Value> {
pub fn parse(file: &str) -> Result<Value<'_>> {
let rules = AstParser::parse(Rule::config, file);

match rules {
Expand Down
7 changes: 7 additions & 0 deletions tests/de_tests.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use corn::from_str;
use paste::paste;
use serde::Deserialize;
use std::collections::HashMap;
use std::fs;

macro_rules! generate_eq_tests {
Expand Down Expand Up @@ -328,6 +329,11 @@ struct NullInArray {
foo: Vec<Option<u8>>,
}

#[derive(Deserialize, Debug, PartialEq)]
struct NumericKeys {
foo: HashMap<u32, String>,
}

#[derive(Deserialize, Debug, PartialEq)]
struct Object {
foo: SubObject,
Expand Down Expand Up @@ -463,6 +469,7 @@ generate_eq_tests!(
(mixed_array, MixedArray),
(null, Null),
(null_in_array, NullInArray),
(numeric_keys, NumericKeys),
(object, Object),
(object_in_array, ObjectInArray),
(readme_example, ReadmeExample),
Expand Down
2 changes: 2 additions & 0 deletions tests/parser_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ macro_rules! generate_eq_tests {
let valid = fs::read_to_string(format!("{root_dir}/assets/outputs/yaml/{test_name}.yml")).unwrap().replace("\r", "");

let config = parse(input.as_str()).unwrap();

let serialized = serde_norway::to_string(&config).unwrap().replace("\r", "");

assert_eq!(serialized.trim(), valid.trim());
Expand All @@ -45,6 +46,7 @@ macro_rules! generate_eq_tests {
let valid = fs::read_to_string(format!("{root_dir}/assets/outputs/toml/{test_name}.toml")).unwrap().replace("\r", "");

let config = parse(input.as_str()).unwrap();

// fall back to default as toml can fail due to no null
let serialized = toml_edit::ser::to_string_pretty(&config).unwrap_or_default().replace("\r", "");

Expand Down
Loading