--- /dev/null +++ b/cmds/jrsonnet-fmt/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "jrsonnet-fmt" +version = "0.1.0" +edition = "2021" + +[dependencies] +dprint-core = "0.47.1" +jrsonnet-parser = { path = "../../crates/jrsonnet-parser" } --- /dev/null +++ b/cmds/jrsonnet-fmt/src/main.rs @@ -0,0 +1,373 @@ +use std::path::PathBuf; + +use dprint_core::formatting::{PrintItems, PrintOptions, Signal}; +use jrsonnet_parser::{ + ArgsDesc, BinaryOpType, BindSpec, Expr, FieldName, LocExpr, Member, ObjBody, Param, ParamsDesc, + ParserSettings, Visibility, +}; + +pub trait Printable { + fn print(&self) -> PrintItems; +} + +macro_rules! pi { + (@i; $($t:tt)*) => {{ + let mut o = PrintItems::new(); + pi!(@s; o: $($t)*); + o + }}; + (@s; $o:ident: str($e:expr) $($t:tt)*) => {{ + $o.push_str($e); + pi!(@s; $o: $($t)*); + }}; + (@s; $o:ident: nl $($t:tt)*) => {{ + $o.push_signal(Signal::NewLine); + pi!(@s; $o: $($t)*); + }}; + (@s; $o:ident: >i $($t:tt)*) => {{ + $o.push_signal(Signal::StartIndent); + pi!(@s; $o: $($t)*); + }}; + (@s; $o:ident: {{ + $o.push_signal(Signal::FinishIndent); + pi!(@s; $o: $($t)*); + }}; + (@s; $o:ident: {$expr:expr} $($t:tt)*) => {{ + $o.extend($expr.print()); + pi!(@s; $o: $($t)*); + }}; + (@s; $o:ident: if ($e:expr)($($then:tt)*) $($t:tt)*) => {{ + if $e { + pi!(@s; $o: $($then)*); + } + pi!(@s; $o: $($t)*); + }}; + (@s; $o:ident: ifelse ($e:expr)($($then:tt)*)($($else:tt)*) $($t:tt)*) => {{ + if $e { + pi!(@s; $o: $($then)*); + } else { + pi!(@s; $o: $($else)*); + } + pi!(@s; $o: $($t)*); + }}; + (@s; $i:ident:) => {} +} +macro_rules! p { + (new: $($t:tt)*) => { + pi!(@i; $($t)*) + }; + ($o:ident: $($t:tt)*) => { + pi!(@s; $o: $($t)*) + }; +} + +impl Printable for FieldName { + fn print(&self) -> PrintItems { + match self { + FieldName::Fixed(f) => { + p!(new: str(&f)) + } + FieldName::Dyn(_) => todo!(), + } + } +} + +impl Printable for Visibility { + fn print(&self) -> PrintItems { + match self { + Visibility::Normal => p!(new: str(":")), + Visibility::Hidden => p!(new: str("::")), + Visibility::Unhide => p!(new: str(":::")), + } + } +} + +impl Printable for BinaryOpType { + fn print(&self) -> PrintItems { + let o = self.to_string(); + p!(new: str(&o)) + } +} + +impl Printable for Option { + fn print(&self) -> PrintItems { + if let Some(v) = self { + v.print() + } else { + PrintItems::new() + } + } +} + +impl Printable for Param { + fn print(&self) -> PrintItems { + p!(new: + str(&self.0) + if(self.1.is_some())(str(" = ") {self.1}) + ) + } +} + +impl Printable for ParamsDesc { + fn print(&self) -> PrintItems { + let mut out = PrintItems::new(); + for (i, item) in self.0.iter().enumerate() { + if i != 0 { + p!(out: str(", ")); + } + out.extend(item.print()); + } + out + } +} + +impl Printable for ArgsDesc { + fn print(&self) -> PrintItems { + let mut out = PrintItems::new(); + let mut first = Some(()); + for u in self.unnamed.iter() { + if first.take().is_none() { + p!(out: str(", ")); + } + p!(out: {u}) + } + for (n, u) in self.named.iter() { + if first.take().is_none() { + p!(out: str(", ")); + } + p!(out: str(&n) str(" = ") {u}) + } + + out + } +} + +impl Printable for BindSpec { + fn print(&self) -> PrintItems { + p!(new: str(&self.name) if(self.params.is_some())(str("(") {self.params} str(")")) str(" = ") {self.value}) + } +} + +struct StrExpr<'s>(&'s str); + +impl<'s> Printable for StrExpr<'s> { + fn print(&self) -> PrintItems { + todo!() + } +} + +impl Printable for ObjBody { + fn print(&self) -> PrintItems { + let mut pi = PrintItems::new(); + p!(pi: str("{")); + match self { + ObjBody::MemberList(m) => { + if !m.is_empty() { + p!(pi: nl > i); + for m in m { + match m { + Member::Field(f) => { + p!(pi: + {f.name} {f.params} + if(f.plus)(str("+")) + {f.visibility} str(" ") + {f.value} + str(",") nl + ); + } + Member::BindStmt(s) => { + p!(pi: str("local ") {s} str(",") nl) + } + Member::AssertStmt(a) => p!(pi: str("assert ") {a.0} if(a.1.is_some())( + str(" : ") {a.1} + ) str(",") nl), + } + } + p!(pi: todo!(), + } + p!(pi: str("}")); + pi + } +} + +impl Printable for Expr { + fn print(&self) -> PrintItems { + let mut pi = PrintItems::new(); + match self { + Expr::Literal(l) => match l { + jrsonnet_parser::LiteralType::This => p!(pi: str("self")), + jrsonnet_parser::LiteralType::Super => p!(pi: str("super")), + jrsonnet_parser::LiteralType::Dollar => p!(pi: str("$")), + jrsonnet_parser::LiteralType::Null => p!(pi: str("null")), + jrsonnet_parser::LiteralType::True => p!(pi: str("true")), + jrsonnet_parser::LiteralType::False => p!(pi: str("false")), + }, + Expr::Str(s) => { + p!(pi: str("\"") str(s) str("\"")) + } + Expr::Num(n) => { + let n = n.to_string(); + p!(pi: str(&n)); + } + Expr::Var(v) => p!(pi: str(&v)), + Expr::Arr(a) => { + p!(pi: str("[")); + for (i, v) in a.iter().enumerate() { + if i != 0 { + p!(pi: str(", ")); + } + p!(pi: {v}) + } + p!(pi: str("]")); + } + Expr::ArrComp(_, _) => todo!(), + Expr::Obj(o) => { + p!(pi: {o}); + } + Expr::ObjExtend(a, b) => p!(pi: {a} str(" ") {b}), + Expr::Parened(v) => { + if let Expr::Parened(_) = &v.0 as &Expr { + p!(pi: {v}) + } else { + p!(pi: str("(") {v} str(")")) + } + } + Expr::UnaryOp(_, _) => todo!(), + Expr::BinaryOp(a, o, b) => { + p!(pi: + {a} str(" ") if(!matches!(&b.0 as &Expr, Expr::Obj(_)))({o} str(" ")) {b} + ) + } + Expr::AssertExpr(_, _) => todo!(), + Expr::LocalExpr(s, v) => { + p!(pi: + str("local") nl >i + ); + for spec in s.iter() { + p!(pi: {spec} str(";") nl) + } + p!(pi: + { + let v = i.to_str().unwrap(); + p!(pi: str("import \"") str(&v) str("\"")); + } + Expr::ImportStr(_) => todo!(), + Expr::ErrorStmt(_) => todo!(), + Expr::Apply(f, a, t) => p!(pi: + {f} str("(") {a} str(")") if(*t)(str("tailstrict")) + ), + Expr::Index(a, b) => p!(pi: {a} str("[") {b} str("]")), + Expr::Function(_, _) => todo!(), + Expr::Intrinsic(_) => todo!(), + Expr::IfElse { + cond, + cond_then, + cond_else, + } => p!(pi: + str("if ") {cond.0} str(" then") ifelse(cond_else.is_some())( + nl >i + {cond_then} nl + i + {cond_else} + { + p!(pi: + {v} + str("[") {d.start} str(":") {d.end} + if(d.step.is_some())( + str(":") + {d.step} + ) + str("]") + ) + } + } + pi + } +} + +impl Printable for LocExpr { + fn print(&self) -> PrintItems { + self.0.print() + } +} + +fn main() { + let parsed = jrsonnet_parser::parse( + r#" + + + # Edit me! + local b = import "b.libsonnet"; # comment + local a = import "a.libsonnet"; + + local f(x,y)=x+y; + + + local Template = {z: "foo"}; + + Template + { + local + + h = 3, + assert self.a == 1 + + : "error", + "f": ((((((3)))))) , + "g g": + f(4,2), + arr: [[ + 1, 2, + ], + 3, + { + b: { + c: { + k: [16] + } + } + } + ], + m: a[1::], + m: b[::], + k: if a == b then + + + 2 + + else Template {} + } + + +"#, + &ParserSettings { + file_name: PathBuf::from("example").into(), + }, + ) + .unwrap(); + + let o = dprint_core::formatting::format( + || { + let print_items = parsed.print(); + print_items + }, + PrintOptions { + indent_width: 2, + max_width: 100, + use_tabs: false, + new_line_text: "\n", + }, + ); + println!("{}", o); +} --- a/crates/jrsonnet-evaluator/src/builtin/mod.rs +++ b/crates/jrsonnet-evaluator/src/builtin/mod.rs @@ -5,14 +5,13 @@ equals, error::{Error::*, Result}, operator::evaluate_mod_op, - parse_args, primitive_equals, push_frame, throw, with_state, ArrValue, Context, FuncVal, + primitive_equals, push_frame, throw, with_state, ArrValue, Context, FuncVal, IndexableVal, Val, }; use format::{format_arr, format_obj}; use gcmodule::Cc; use jrsonnet_interner::IStr; use jrsonnet_parser::{ArgsDesc, ExprLocation}; -use jrsonnet_types::ty; use serde::Deserialize; use serde_yaml::DeserializingQuirks; use std::{ @@ -466,19 +465,19 @@ Ok(format!("{:x}", md5::compute(&str.as_bytes()))) } -fn builtin_trace(context: Context, loc: &ExprLocation, args: &ArgsDesc) -> Result { - parse_args!(context, "trace", args, 2, [ - 0, str: ty!(string) => Val::Str; - 1, rest: ty!(any); - ], { - eprint!("TRACE:"); - with_state(|s|{ - let locs = s.map_source_locations(&loc.0, &[loc.1]); - eprint!(" {}:{}", loc.0.file_name().unwrap().to_str().unwrap(), locs[0].line); - }); - eprintln!(" {}", str); - Ok(rest) - }) +#[jrsonnet_macros::builtin] +fn builtin_trace(#[location] loc: &ExprLocation, str: IStr, rest: Any) -> Result { + eprint!("TRACE:"); + with_state(|s| { + let locs = s.map_source_locations(&loc.0, &[loc.1]); + eprint!( + " {}:{}", + loc.0.file_name().unwrap().to_str().unwrap(), + locs[0].line + ); + }); + eprintln!(" {}", str); + Ok(rest) as Result } #[jrsonnet_macros::builtin] --- a/crates/jrsonnet-evaluator/src/error.rs +++ b/crates/jrsonnet-evaluator/src/error.rs @@ -16,8 +16,6 @@ pub enum Error { #[error("intrinsic not found: {0}")] IntrinsicNotFound(IStr), - #[error("argument reordering in intrisics not supported yet")] - IntrinsicArgumentReorderingIsNotSupportedYet, #[error("operator {0} does not operate on type {1}")] UnaryOperatorDoesNotOperateOnType(UnaryOpType, ValType), --- a/crates/jrsonnet-evaluator/src/function.rs +++ b/crates/jrsonnet-evaluator/src/function.rs @@ -311,45 +311,3 @@ Ok(body_ctx.extend(out, None, None, None)) } - -#[macro_export] -macro_rules! parse_args { - ($ctx: expr, $fn_name: expr, $args: expr, $total_args: expr, [ - $($id: expr, $name: ident: $ty: expr $(=>$match: path)?);+ $(;)? - ], $handler:block) => {{ - use $crate::{error::Error::*, throw, evaluate, push_description_frame, typed::CheckType}; - - let args = $args; - if args.unnamed.len() + args.named.len() > $total_args { - throw!(TooManyArgsFunctionHas($total_args)); - } - $( - if args.unnamed.len() + args.named.len() <= $id { - throw!(FunctionParameterNotBoundInCall(stringify!($name).into())); - } - // Is named - let $name = if $id >= $args.unnamed.len() { - let named = &args.named[$id - $args.unnamed.len()]; - if &named.0 != stringify!($name) { - throw!(IntrinsicArgumentReorderingIsNotSupportedYet); - } - &named.1 - } else { - &$args.unnamed[$id] - }; - let $name = push_description_frame(|| format!("evaluating builtin argument {}", stringify!($name)), || { - let value = evaluate($ctx.clone(), &$name)?; - $ty.check(&value)?; - Ok(value) - })?; - $( - let $name = if let $match(v) = $name { - v - } else { - unreachable!(); - }; - )? - )+ - ($handler as crate::Result<_>) - }}; -} --- a/crates/jrsonnet-macros/src/lib.rs +++ b/crates/jrsonnet-macros/src/lib.rs @@ -1,6 +1,10 @@ use proc_macro2::Span; use quote::quote; -use syn::{parse_macro_input, FnArg, Ident, ItemFn, Pat}; +use syn::{parse_macro_input, FnArg, Ident, ItemFn, Pat, PatType}; + +fn is_location_arg(t: &PatType) -> bool { + t.attrs.iter().any(|a| a.path.is_ident("location")) +} #[proc_macro_attribute] pub fn builtin( @@ -8,14 +12,11 @@ item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { // syn::ItemFn::parse(input) - let fun: ItemFn = parse_macro_input!(item); + let mut fun: ItemFn = parse_macro_input!(item); - let inner_name = Ident::new("inner", Span::call_site()); - let mut inner_fun = fun.clone(); - inner_fun.sig.ident = inner_name.clone(); let result = match fun.sig.output { syn::ReturnType::Default => panic!("builtin should return something"), - syn::ReturnType::Type(_, ty) => ty, + syn::ReturnType::Type(_, ref ty) => ty.clone(), }; let params = fun @@ -26,6 +27,7 @@ FnArg::Receiver(_) => unreachable!(), FnArg::Typed(t) => t, }) + .filter(|a| !is_location_arg(a)) .map(|t| { let ident = match &t.pat as &Pat { Pat::Ident(i) => i.ident.to_string(), @@ -39,38 +41,53 @@ has_default: #optional, } } - }); + }) + .collect::>(); let args = fun .sig .inputs - .iter() + .iter_mut() .map(|i| match i { FnArg::Receiver(_) => unreachable!(), FnArg::Typed(t) => t, }) .map(|t| { - let ident = match &t.pat as &Pat { - Pat::Ident(i) => i.ident.to_string(), - _ => panic!("only idents supported yet"), - }; - let ty = &t.ty; - quote! {{ - let value = parsed.get(#ident).unwrap(); + let count_before = t.attrs.len(); + t.attrs.retain(|a| !a.path.is_ident("location")); + let count_after = t.attrs.len(); + let is_location = count_before != count_after; + if is_location { + quote! {{ + loc + }} + } else { + let ident = match &t.pat as &Pat { + Pat::Ident(i) => i.ident.to_string(), + _ => panic!("only idents supported yet"), + }; + let ty = &t.ty; + quote! {{ + let value = parsed.get(#ident).unwrap(); - jrsonnet_evaluator::push_description_frame( - || format!("argument <{}> evaluation", #ident), - || <#ty>::try_from(value.evaluate()?), - )? - }} - }); + jrsonnet_evaluator::push_description_frame( + || format!("argument <{}> evaluation", #ident), + || <#ty>::try_from(value.evaluate()?), + )? + }} + } + }).collect::>(); + + let inner_name = Ident::new("inner", Span::call_site()); + let mut inner_fun = fun.clone(); + inner_fun.sig.ident = inner_name.clone(); let attrs = &fun.attrs; let vis = &fun.vis; let name = &fun.sig.ident; (quote! { #(#attrs)* - #vis fn #name(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { + #vis fn #name(context: Context, loc: &ExprLocation, args: &ArgsDesc) -> Result { #inner_fun use jrsonnet_evaluator::function::BuiltinParam; const PARAMS: &'static [BuiltinParam] = &[ --- a/crates/jrsonnet-types/src/lib.rs +++ b/crates/jrsonnet-types/src/lib.rs @@ -191,9 +191,9 @@ write!(f, "}}")?; } ComplexValType::Union(v) => write_union(f, true, v.iter())?, - ComplexValType::UnionRef(v) => write_union(f, true, v.iter().map(|v| *v))?, + ComplexValType::UnionRef(v) => write_union(f, true, v.iter().copied())?, ComplexValType::Sum(v) => write_union(f, false, v.iter())?, - ComplexValType::SumRef(v) => write_union(f, false, v.iter().map(|v| *v))?, + ComplexValType::SumRef(v) => write_union(f, false, v.iter().copied())?, }; Ok(()) } --- a/flake.nix +++ b/flake.nix @@ -1,21 +1,59 @@ { - description = "Rust jsonnet implementation"; - - inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - inputs.flake-utils.url = "github:numtide/flake-utils"; - - outputs = { self, nixpkgs, flake-utils }: + description = "Dotfiles manager"; + inputs = { + nixpkgs.url = "github:nixos/nixpkgs"; + flake-utils.url = "github:numtide/flake-utils"; + naersk.url = "github:nix-community/naersk"; + rust-overlay.url = "github:oxalica/rust-overlay"; + pre-commit-hooks.url = "github:cachix/pre-commit-hooks.nix"; + }; + outputs = { self, nixpkgs, flake-utils, rust-overlay, pre-commit-hooks, naersk }: flake-utils.lib.eachDefaultSystem (system: let - pkgs = nixpkgs.legacyPackages.${system}; - jrsonnet = pkgs.rustPlatform.buildRustPackage rec { - pname = "jrsonnet"; - version = "0.1.0"; - src = self; - cargoSha256 = "sha256-cez8pJ/uwj+PHAPQwpSB4CKaxcP8Uvv8xguOrVXR2xE="; + pkgs = import nixpkgs + { + inherit system; + overlays = [ rust-overlay.overlay ]; + }; + rust = ((pkgs.rustChannelOf { date = "2021-11-11"; channel = "nightly"; }).default.override { + extensions = [ "rust-src" ]; + }); + naersk-lib = naersk.lib."${system}".override { + rustc = rust; + cargo = rust; + }; + in + rec { + checks = { + pre-commit-check = pre-commit-hooks.lib.${system}.run { + src = ./.; + hooks = { + nixpkgs-fmt.enable = true; + }; + }; + }; + defaultPackage = naersk-lib.buildPackage { + pname = "dotman"; + root = ./.; + buildInputs = with pkgs; [ + pkgs.sqlite + ]; }; - in { - defaultPackage = jrsonnet; - devShell = pkgs.mkShell {}; - }); + devShell = pkgs.mkShell { + inherit (checks.pre-commit-check) shellHook; + nativeBuildInputs = with pkgs;[ + pkgs.binutils + pkgs.pkgconfig + pkgs.clang + pkgs.x11 + pkgs.alsaLib + pkgs.libudev + pkgs.sqlite + rust + cargo-edit + go-jsonnet + ]; + }; + } + ); }