From bce268cceb0f15aa4e2f63ab183e8e2298998337 Mon Sep 17 00:00:00 2001 From: Nilstrieb <48135649+Nilstrieb@users.noreply.github.com> Date: Sat, 6 Jul 2024 22:35:11 +0200 Subject: [PATCH] stuff --- .gitignore | 1 + Cargo.lock | 150 ++++++++++++ Cargo.toml | 9 + example-configs/kubernetes/deployment-dry.nix | 45 ++++ example-configs/kubernetes/deployment-raw.nix | 33 +++ example-configs/kubernetes/deployment.yml | 33 +++ example-configs/rust-cargo/Cargo.json | 34 +++ example-configs/rust-cargo/Cargo.toml | 37 +++ example-configs/rust-cargo/Cargo.yml | 38 +++ example.nc | 12 + src/lib.rs | 45 ++++ src/main.rs | 7 + src/parser.rs | 228 ++++++++++++++++++ 13 files changed, 672 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 example-configs/kubernetes/deployment-dry.nix create mode 100644 example-configs/kubernetes/deployment-raw.nix create mode 100644 example-configs/kubernetes/deployment.yml create mode 100644 example-configs/rust-cargo/Cargo.json create mode 100644 example-configs/rust-cargo/Cargo.toml create mode 100644 example-configs/rust-cargo/Cargo.yml create mode 100644 example.nc create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/parser.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..40d6fe5 --- /dev/null +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a4bee24 --- /dev/null +++ b/Cargo.toml @@ -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" diff --git a/example-configs/kubernetes/deployment-dry.nix b/example-configs/kubernetes/deployment-dry.nix new file mode 100644 index 0000000..30e5d8d --- /dev/null +++ b/example-configs/kubernetes/deployment-dry.nix @@ -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"; }; +} diff --git a/example-configs/kubernetes/deployment-raw.nix b/example-configs/kubernetes/deployment-raw.nix new file mode 100644 index 0000000..37710e6 --- /dev/null +++ b/example-configs/kubernetes/deployment-raw.nix @@ -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"; }; + }; + } +] diff --git a/example-configs/kubernetes/deployment.yml b/example-configs/kubernetes/deployment.yml new file mode 100644 index 0000000..b375e2d --- /dev/null +++ b/example-configs/kubernetes/deployment.yml @@ -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 diff --git a/example-configs/rust-cargo/Cargo.json b/example-configs/rust-cargo/Cargo.json new file mode 100644 index 0000000..55b9ff8 --- /dev/null +++ b/example-configs/rust-cargo/Cargo.json @@ -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 }] +} diff --git a/example-configs/rust-cargo/Cargo.toml b/example-configs/rust-cargo/Cargo.toml new file mode 100644 index 0000000..d0b3f37 --- /dev/null +++ b/example-configs/rust-cargo/Cargo.toml @@ -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 diff --git a/example-configs/rust-cargo/Cargo.yml b/example-configs/rust-cargo/Cargo.yml new file mode 100644 index 0000000..93970b6 --- /dev/null +++ b/example-configs/rust-cargo/Cargo.yml @@ -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 diff --git a/example.nc b/example.nc new file mode 100644 index 0000000..0cf32ab --- /dev/null +++ b/example.nc @@ -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", +] diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..ca15ec9 --- /dev/null +++ b/src/lib.rs @@ -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(); +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..58cece0 --- /dev/null +++ b/src/main.rs @@ -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), + } +} diff --git a/src/parser.rs b/src/parser.rs new file mode 100644 index 0000000..b099c0c --- /dev/null +++ b/src/parser.rs @@ -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 = std::result::Result; + +#[derive(Debug)] +pub enum Expr { + Array(Vec, Span), + Object(IndexMap, Span), + String(String, Span), + Ident(String, Span), +} + +struct TokenStream { + tokens: Peekable>, + nesting: usize, +} + +impl TokenStream { + fn peek(&mut self) -> Result<&Token> { + self.tokens.peek().ok_or_else(|| Error::UnexpectedEndOfFile) + } + fn next(&mut self) -> Result { + self.tokens.next().ok_or_else(|| Error::UnexpectedEndOfFile) + } + fn eat(&mut self, kind: TokenKind) -> Option { + let next = self.peek().ok()?; + if next.kind != kind { + return None; + } + Some(self.next().unwrap()) + } + fn expect(&mut self, kind: TokenKind) -> Result { + let next = self.next()?; + if next.kind != kind { + return Err(Error::UnexpectedToken(format!("expected {kind:?}"), next)); + } + Ok(next) + } +} + +pub fn parse(input: &str) -> Result { + 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 { + 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> { + 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()), + } + } +}