This commit is contained in:
nora 2024-07-06 22:35:11 +02:00
commit bce268cceb
13 changed files with 672 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

150
Cargo.lock generated Normal file
View file

@ -0,0 +1,150 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "codespan"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3362992a0d9f1dd7c3d0e89e0ab2bb540b7a95fea8cd798090e758fda2899b5e"
dependencies = [
"codespan-reporting",
]
[[package]]
name = "codespan-reporting"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
dependencies = [
"termcolor",
"unicode-width",
]
[[package]]
name = "config-language"
version = "0.1.0"
dependencies = [
"codespan",
"codespan-reporting",
"indexmap",
]
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "indexmap"
version = "2.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
[[package]]
name = "unicode-width"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
[[package]]
name = "winapi-util"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
dependencies = [
"windows-sys",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"

9
Cargo.toml Normal file
View file

@ -0,0 +1,9 @@
[package]
name = "config-language"
version = "0.1.0"
edition = "2021"
[dependencies]
codespan = "0.11.1"
codespan-reporting = "0.11.1"
indexmap = "2.2.6"

View file

@ -0,0 +1,45 @@
let
trivialWebService = { name, selectorLabel, port, extraPodLabels ? { } }:
[
{
apiVersion = "apps/v1";
kind = "Deployment";
metadata = {
inherit name;
};
spec = {
selector.matchLabels = selectorLabel;
replicas = 2;
template = {
metadata.labels = selectorLabel // extraPodLabels;
spec.containters = [
{
inherit name;
image = "nginx";
ports = [{ containerPort = port; }];
}
];
};
};
}
{
apiVersion = "apps/v1";
kind = "Service";
metadata = {
inherit name;
labels = selectorLabel;
};
spec = {
ports = { port = 80; protocol = "TCP"; };
selector = selectorLabel;
};
}
]
;
in
trivialWebService {
name = "my-nginx";
selectorLabel = { run = "my-nginx"; };
port = 80;
extraPodLabels = { someOther = "label"; };
}

View file

@ -0,0 +1,33 @@
[
{
apiVersion = "apps/v1";
kind = "Deployment";
metadata.name = "my-nginx";
spec = {
selector.matchLabels.run = "my-nginx";
replicas = 2;
template = {
metadata.labels.run = "my-nginx";
spec.containters = [
{
name = "my-nginx";
image = "nginx";
ports = [{ containerPort = 80; }];
}
];
};
};
}
{
apiVersion = "apps/v1";
kind = "Service";
metadata = {
name = "my-nginx";
labels.run = "my-nginx";
};
spec = {
ports = { port = 80; protocol = "TCP"; };
selector = { run = "my-nginx"; };
};
}
]

View file

@ -0,0 +1,33 @@
# real k8s configs use --- and not a top-level array
- apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
selector:
matchLabels:
run: my-nginx
replicas: 2
template:
metadata:
labels:
run: my-nginx
someOther: label
spec:
containers:
- name: my-nginx
image: nginx
ports:
- containerPort: 80
- apiVersion: v1
kind: Service
metadata:
name: my-nginx
labels:
run: my-nginx
spec:
ports:
- port: 80
protocol: TCP
selector:
run: my-nginx

View file

@ -0,0 +1,34 @@
{
"cargo-features": ["public-dependency"],
"package": {
"name": "std",
"version": "0.0.0",
"metadata": { "fortanix-sgx": { "threads": 125, "heap_size": 134217728 } }
},
"lib": { "crate-type": ["dylib", "rlib"] },
"dependencies": {
"alloc": { "path": "../alloc", "public": true },
"something": "1.2.0",
"std_detect": {
"path": "../stdarch/crates/std_detect",
"default-features": false,
"features": ["rustc-dep-of-std"]
},
"target": {
"cfg(not(all(windows, target_env = \"msvc\", not(target_vendor = \"uwp\"))))": {
"dependencies": {
"miniz_oxide": {
"version": "0.7.0",
"optional": true,
"default-features": false
}
}
}
}
},
"features": {
"backtrace": ["gimli-symbolize", "miniz_oxide/rustc-dep-of-std"],
"gimli-symbolize": []
},
"bench": [{ "name": "stdbenches", "path": "benches/lib.rs", "test": true }]
}

View file

@ -0,0 +1,37 @@
# github.com/rust-lang/rust:library/std/Cargo.toml
cargo-features = ["public-dependency"]
[package]
name = "std"
version = "0.0.0"
[lib]
crate-type = ["dylib", "rlib"]
[dependencies]
alloc = { path = "../alloc", public = true }
something = "1.2.0"
std_detect = { path = "../stdarch/crates/std_detect", default-features = false, features = ['rustc-dep-of-std'] }
[target.'cfg(not(all(windows, target_env = "msvc", not(target_vendor = "uwp"))))'.dependencies]
miniz_oxide = { version = "0.7.0", optional = true, default-features = false }
[features]
backtrace = [
"gimli-symbolize",
'miniz_oxide/rustc-dep-of-std',
]
gimli-symbolize = []
[package.metadata.fortanix-sgx]
# Maximum possible number of threads when testing
threads = 125
# Maximum heap size
heap_size = 0x8000000
[[bench]]
name = "stdbenches"
path = "benches/lib.rs"
test = true

View file

@ -0,0 +1,38 @@
cargo-features: [public-dependency]
package:
name: std
version: "0.0.0"
metadata:
fortanix-sgx:
threads: 125
heap_size: 0x8000000
lib:
crate-type:
- dylib
- rlib
dependencies:
alloc:
path: ../alloc
public: true
something: "1.2.0"
std_detect:
path: ../stdarch/crates/std_detect
default-features: false
features: [rustc-dep-of-std]
target:
'cfg(not(all(windows, target_env = "msvc", not(target_vendor = "uwp"))))':
dependencies:
miniz_oxide:
version: "0.7.0"
optional: true
default-features: false
features:
backtrace:
- gimli-symbolize
- miniz_oxide/rustc-dep-of-std
gimli-symbolize: []
bench:
- name: stdbenches
path: benches/lib.rs
test: true

12
example.nc Normal file
View file

@ -0,0 +1,12 @@
#type X = jsonSchema("https://kubernetesjsonschema.dev/v1.14.0/deployment-apps-v1.json")
[
{
apiVersion = "apps/v1",
kind = "Deployment",
metadata = {
name = "my-nginx",
}
},
"x",
]

45
src/lib.rs Normal file
View file

@ -0,0 +1,45 @@
use codespan::Span;
use codespan_reporting::{
diagnostic::{Diagnostic, Label},
files::SimpleFiles,
};
pub mod parser;
#[derive(Debug)]
pub enum Error {
Parse(parser::Error),
}
impl Error {
fn into_msg_and_span(self) -> (String, Span) {
match self {
Error::Parse(e) => e.into_msg_and_span(),
}
}
}
pub fn evaluate(input: &str) -> Result<(), Error> {
let expr = parser::parse(input).map_err(Error::Parse)?;
dbg!(expr);
todo!()
}
pub fn render_error(input: &str, error: Error) {
let mut files = SimpleFiles::new();
let file_id = files.add("example.nc", input);
let (msg, span) = error.into_msg_and_span();
let diagnostic = Diagnostic::error()
.with_message(msg)
.with_labels(vec![Label::primary(
file_id,
(span.start().to_usize())..span.end().to_usize(),
)]);
let writer = codespan_reporting::term::termcolor::StandardStream::stderr(
codespan_reporting::term::termcolor::ColorChoice::Always,
);
let config = codespan_reporting::term::Config::default();
codespan_reporting::term::emit(&mut writer.lock(), &config, &files, &diagnostic).unwrap();
}

7
src/main.rs Normal file
View file

@ -0,0 +1,7 @@
fn main() {
let input = std::fs::read_to_string("example.nc").unwrap();
match config_language::evaluate(&input) {
Ok(()) => {}
Err(e) => config_language::render_error(&input, e),
}
}

228
src/parser.rs Normal file
View file

@ -0,0 +1,228 @@
use std::{iter::Peekable, vec};
use codespan::Span;
use indexmap::IndexMap;
#[derive(Debug)]
pub enum Error {
SourceFileTooBig(usize),
InvalidCharacter(char, Span),
UnterminatedStringLiteral(Span),
UnexpectedToken(String, Token),
TooNested(Span),
UnexpectedEndOfFile,
}
pub type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Debug)]
pub enum Expr {
Array(Vec<Expr>, Span),
Object(IndexMap<String, Expr>, Span),
String(String, Span),
Ident(String, Span),
}
struct TokenStream {
tokens: Peekable<vec::IntoIter<Token>>,
nesting: usize,
}
impl TokenStream {
fn peek(&mut self) -> Result<&Token> {
self.tokens.peek().ok_or_else(|| Error::UnexpectedEndOfFile)
}
fn next(&mut self) -> Result<Token> {
self.tokens.next().ok_or_else(|| Error::UnexpectedEndOfFile)
}
fn eat(&mut self, kind: TokenKind) -> Option<Token> {
let next = self.peek().ok()?;
if next.kind != kind {
return None;
}
Some(self.next().unwrap())
}
fn expect(&mut self, kind: TokenKind) -> Result<Token> {
let next = self.next()?;
if next.kind != kind {
return Err(Error::UnexpectedToken(format!("expected {kind:?}"), next));
}
Ok(next)
}
}
pub fn parse(input: &str) -> Result<Expr> {
let tokens = lexer(input)?;
dbg!(&tokens);
let expr = parse_expr(&mut TokenStream {
tokens: tokens.into_iter().peekable(),
nesting: 0,
})?;
dbg!(&expr);
Ok(expr)
}
fn parse_expr(t: &mut TokenStream) -> Result<Expr> {
let token = t.next()?;
if t.nesting > 50 {
return Err(Error::TooNested(token.span));
}
let expr = match token.kind {
TokenKind::BracketOpen => {
let mut elems = vec![];
let mut had_comma = true;
loop {
if let Ok(Token {
kind: TokenKind::BracketClose,
..
}) = t.peek()
{
break;
}
if !had_comma {
return Err(Error::UnexpectedToken("comma".to_owned(), t.next()?));
}
t.nesting += 1;
elems.push(parse_expr(t)?);
t.nesting -= 1;
had_comma = t.eat(TokenKind::Comma).is_some();
}
t.next()?;
Expr::Array(elems, token.span)
}
TokenKind::BraceOpen => {
let mut elems = IndexMap::new();
let mut had_comma = true;
loop {
if let Ok(Token {
kind: TokenKind::BraceClose,
..
}) = t.peek()
{
break;
}
if !had_comma {
return Err(Error::UnexpectedToken("comma".to_owned(), t.next()?));
}
let name_tok = t.next()?;
let Token {
kind: TokenKind::Ident(name),
..
} = name_tok
else {
return Err(Error::UnexpectedToken("expected name".into(), name_tok));
};
t.expect(TokenKind::Equal)?;
t.nesting += 1;
elems.insert(name, parse_expr(t)?);
t.nesting -= 1;
had_comma = t.eat(TokenKind::Comma).is_some();
}
t.next()?;
Expr::Object(elems, token.span)
}
TokenKind::String(s) => Expr::String(s, token.span),
TokenKind::Ident(s) => Expr::Ident(s, token.span),
_ => return Err(Error::UnexpectedToken("expected expression".into(), token)),
};
Ok(expr)
}
#[derive(Debug, Clone)]
pub struct Token {
span: Span,
kind: TokenKind,
}
#[derive(Debug, Clone, PartialEq)]
enum TokenKind {
BracketOpen,
BracketClose,
BraceOpen,
BraceClose,
Equal,
Comma,
Ident(String),
String(String),
}
fn lexer(input_str: &str) -> Result<Vec<Token>> {
if input_str.len() >= (u32::MAX as usize) {
return Err(Error::SourceFileTooBig(input_str.len()));
}
let mut input = input_str.char_indices().peekable();
let mut tokens = vec![];
while let Some((idx, c)) = input.next() {
let span = Span::new(idx as u32, (idx as u32) + 1);
let mut simple = |kind| tokens.push(Token { span, kind });
match c {
c if c.is_whitespace() => {}
'#' => loop {
if let Some((_, '\n')) = input.next() {
break;
}
},
'[' => simple(TokenKind::BracketOpen),
']' => simple(TokenKind::BracketClose),
'{' => simple(TokenKind::BraceOpen),
'}' => simple(TokenKind::BraceClose),
'=' => simple(TokenKind::Equal),
',' => simple(TokenKind::Comma),
'"' => loop {
match input.next() {
None => return Err(Error::UnterminatedStringLiteral(span)),
Some((next_idx, '"')) => {
let s = &input_str[(idx + 1)..next_idx];
tokens.push(Token {
span: Span::new(idx as u32, next_idx as u32),
kind: TokenKind::String(s.to_owned()),
});
break;
}
Some(_) => {}
}
},
c if c.is_alphabetic() => {
while let Some((next_idx, c)) = input.peek() {
if !c.is_alphanumeric() {
let s = &input_str[idx..*next_idx];
tokens.push(Token {
span: Span::new(idx as u32, *next_idx as u32),
kind: TokenKind::Ident(s.to_owned()),
});
break;
}
input.next();
}
}
c => return Err(Error::InvalidCharacter(c, span)),
}
}
Ok(tokens)
}
impl Error {
pub(crate) fn into_msg_and_span(self) -> (String, Span) {
match self {
Error::SourceFileTooBig(_) => ("source file bigger than 4GB".into(), Span::default()),
Error::InvalidCharacter(c, sp) => (format!("invalid character: {c}"), sp),
Error::UnterminatedStringLiteral(sp) => (format!("unterminated string literal"), sp),
Error::UnexpectedToken(expected, tok) => {
(format!("unexpected token, expected {expected}"), tok.span)
}
Error::TooNested(sp) => ("too nested".to_owned(), sp),
Error::UnexpectedEndOfFile => ("unexpected end of file".to_owned(), Span::default()),
}
}
}