initial commit
This commit is contained in:
commit
f3292d32a4
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
target/
|
||||||
14
example/Cargo.lock
generated
Normal file
14
example/Cargo.lock
generated
Normal file
@ -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"
|
||||||
9
example/Cargo.toml
Normal file
9
example/Cargo.toml
Normal file
@ -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" }
|
||||||
12
example/src/main.rs
Normal file
12
example/src/main.rs
Normal file
@ -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() {}
|
||||||
7
inline-sql/Cargo.lock
generated
Normal file
7
inline-sql/Cargo.lock
generated
Normal file
@ -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"
|
||||||
9
inline-sql/Cargo.toml
Normal file
9
inline-sql/Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "inline-sql"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
163
inline-sql/src/lib.rs
Normal file
163
inline-sql/src/lib.rs
Normal file
@ -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<usize, Range<usize>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Location> 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<Vec<char>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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::<Vec<(usize, Range<usize>)>>();
|
||||||
|
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()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user