commit f3292d32a4846c4112ba8ab3fecfb1a0345e9aaf Author: Jonas Maier <> Date: Fri Feb 17 20:29:30 2023 +0100 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f7896d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/example/Cargo.lock b/example/Cargo.lock new file mode 100644 index 0000000..b412ae4 --- /dev/null +++ b/example/Cargo.lock @@ -0,0 +1,14 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "example" +version = "0.1.0" +dependencies = [ + "inline-sql", +] + +[[package]] +name = "inline-sql" +version = "0.1.0" diff --git a/example/Cargo.toml b/example/Cargo.toml new file mode 100644 index 0000000..2fc4c6a --- /dev/null +++ b/example/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "example" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +inline-sql = { path = "../inline-sql" } diff --git a/example/src/main.rs b/example/src/main.rs new file mode 100644 index 0000000..942ba40 --- /dev/null +++ b/example/src/main.rs @@ -0,0 +1,12 @@ +extern crate inline_sql; +use inline_sql::sql; + +sql! { + CREATE TABLE MY_TABLE ( + X INTEGER, + Y INTEGER, + PRIMARY KEY (Y) + ) +} + +fn main() {} diff --git a/inline-sql/Cargo.lock b/inline-sql/Cargo.lock new file mode 100644 index 0000000..414a513 --- /dev/null +++ b/inline-sql/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "inline-sql" +version = "0.1.0" diff --git a/inline-sql/Cargo.toml b/inline-sql/Cargo.toml new file mode 100644 index 0000000..d2cb045 --- /dev/null +++ b/inline-sql/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "inline-sql" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] diff --git a/inline-sql/src/lib.rs b/inline-sql/src/lib.rs new file mode 100644 index 0000000..55e5768 --- /dev/null +++ b/inline-sql/src/lib.rs @@ -0,0 +1,163 @@ +#![feature(proc_macro_span, proc_macro_span_shrink)] + +extern crate proc_macro; +use std::{collections::HashMap, ops::Range}; + +use proc_macro::{Delimiter, Span, TokenStream, TokenTree}; + +#[derive(Debug, Default, Clone)] +struct Spacing { + sizes: HashMap>, +} + +struct Location { + indent: usize, + line_start: usize, + line_end: usize, +} + +impl Location { + fn new(line: usize, start: usize) -> Self { + Self { + indent: start, + line_start: line, + line_end: line, + } + } + fn combine(self, other: Self) -> Self { + Self { + indent: self.indent.min(other.indent), + line_start: self.line_start.min(other.line_start), + line_end: self.line_end.max(other.line_end), + } + } + fn lines(&self) -> usize { + self.line_end - self.line_start + 1 + } + fn indent(&self) -> usize { + self.indent + } +} +impl Into for Spacing { + fn into(self) -> Location { + let loc = self.sizes.iter().next().unwrap(); + let mut loc = Location::new(*loc.0, loc.1.start); + for s in self.sizes.iter() { + loc = loc.combine(Location::new(*s.0, s.1.start)); + } + loc + } +} + +struct Canvas { + location: Location, + buffers: Vec>, +} + +impl Canvas { + fn new(location: Location, spacing: Spacing) -> Self { + let mut buffers = vec![vec![]; location.lines()]; + for &line in spacing.sizes.keys() { + let line_length = spacing.sizes[&line].end - location.indent(); + let idx = line - location.line_start; + buffers[idx] = vec![' '; line_length]; + } + Self { location, buffers } + } + fn render_at(&mut self, span: Span, value: &str) { + let idx = span.start().line - self.location.line_start; + let offset = span.start().column - self.location.indent(); + for (i, c) in value.chars().enumerate() { + self.buffers[idx][offset + i] = c; + } + } + fn render_tk(&mut self, token: TokenTree) { + self.render_at(token.span(), &token.to_string()); + } + fn render(&mut self, tokens: TokenStream) { + for token in tokens { + if let proc_macro::TokenTree::Group(group) = token { + self.render(group.stream()); + let (open, close) = match group.delimiter() { + Delimiter::Parenthesis => ("(", ")"), + Delimiter::Brace => ("{", "}"), + Delimiter::Bracket => ("[", "]"), + Delimiter::None => todo!(), + }; + self.render_at(group.span_open(), open); + self.render_at(group.span_close(), close); + } else { + self.render_tk(token); + } + } + } +} + +impl ToString for Canvas { + fn to_string(&self) -> String { + let mut buf = String::new(); + for line in self.buffers.iter() { + for &ch in line.iter() { + buf.push(ch); + } + buf += "\n"; + } + buf + } +} + +impl Spacing { + /// begin is inclusive, end is exclusive + fn register(&mut self, line: usize, start: usize, end: usize) { + let range = self.sizes.entry(line).or_insert(start..end); + range.start = range.start.min(start); + range.end = range.end.max(end); + } + fn register_span(&mut self, span: Span) { + for line in span.start().line..=span.end().line { + let start = span.start().column.min(span.end().column); + let end = span.start().column.max(span.end().column); + self.register(line, start, end); + } + } + fn visit(&mut self, tokens: TokenStream) { + for token in tokens { + match token { + TokenTree::Group(e) => { + self.register_span(e.span_open()); + self.register_span(e.span_close()); + self.visit(e.stream()); + } + _ => self.register_span(token.span()), + } + } + } + fn print(&self) { + let mut lines = self + .sizes + .iter() + .map(|e| (*e.0, e.1.clone())) + .collect::)>>(); + lines.sort_by(|a, b| a.0.cmp(&b.0)); + for (_, range) in lines { + for _ in 1..(range.start) { + print!("-"); + } + for _ in range { + print!("X"); + } + println!(); + } + } +} + +#[proc_macro] +pub fn sql(tokens: TokenStream) -> TokenStream { + let mut spacing = Spacing::default(); + spacing.visit(tokens.clone()); + let loc = spacing.clone().into(); + let mut canvas = Canvas::new(loc, spacing.clone()); + canvas.render(tokens); + print!("{}", canvas.to_string()); + "".parse().unwrap() +}