├── .gitignore ├── LICENSE ├── dune ├── dune-project ├── main.ml ├── nix ├── dune ├── lexer.mll ├── nix.ml ├── parser.mly ├── tokens.ml └── types.ml ├── nixformat.opam ├── pprinter.ml ├── pretty_printer.ml └── simple_printer.ml /.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | .merlin 3 | *.install 4 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) 2018 Denis Korzunov 3 | 4 | Permission to use, copy, modify, and/or distribute this software for any purpose 5 | with or without fee is hereby granted, provided that the above copyright notice 6 | and this permission notice appear in all copies. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 9 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 10 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 11 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 12 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 13 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 14 | THIS SOFTWARE. 15 | -------------------------------------------------------------------------------- /dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name main) 3 | (public_name nixformat) 4 | (libraries nix pprint)) 5 | -------------------------------------------------------------------------------- /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 3.0) 2 | (name nixformat) 3 | (using menhir 2.0) 4 | (generate_opam_files true) 5 | (license MIT) 6 | (authors "Denis Korzunov ") 7 | (source (github d2km/nixformat)) 8 | 9 | (package 10 | (name nixformat) 11 | (depends menhir pprint) 12 | (synopsis "Nix code formatter") 13 | (description "A code formatter written in ocaml using menhir and ocamllex")) 14 | -------------------------------------------------------------------------------- /main.ml: -------------------------------------------------------------------------------- 1 | let file_names = ref ([]: string list) 2 | let out = ref stdout 3 | 4 | let main () = 5 | Arg.parse [] (fun x -> file_names := x :: !file_names) ""; 6 | let files = match !file_names with 7 | | [] -> [stdin, ""] 8 | | names -> List.map (fun n -> open_in n, n) names 9 | in 10 | files 11 | |> List.rev 12 | |> List.iter (fun (file, name) -> 13 | try 14 | Nix.parse file name |> Pretty_printer.print !out; 15 | output_char !out '\n' 16 | with 17 | | Nix.ParseError msg -> 18 | Printf.eprintf "%s" msg 19 | ) 20 | 21 | let () = main () 22 | -------------------------------------------------------------------------------- /nix/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name nix)) 3 | 4 | (menhir 5 | (modules parser) 6 | (flags "--explain" "--dump" "--strict" "--external-tokens" "Tokens") 7 | (infer true)) 8 | 9 | (ocamllex 10 | (modules lexer)) 11 | -------------------------------------------------------------------------------- /nix/lexer.mll: -------------------------------------------------------------------------------- 1 | { 2 | open Tokens 3 | 4 | exception Error of string 5 | 6 | (* Types of curly braces. 7 | AQUOTE corresponds to the braces for antiquotation, i.e. '${...}' 8 | and SET to an attribute set '{...}'. 9 | *) 10 | type braces = 11 | | AQUOTE 12 | | SET 13 | 14 | let print_stack s = 15 | let b = Buffer.create 100 in 16 | Buffer.add_string b "[ "; 17 | List.iter (function 18 | | AQUOTE -> Buffer.add_string b "AQUOTE; " 19 | | SET -> Buffer.add_string b "SET; " 20 | ) s; 21 | Buffer.add_string b "]"; 22 | Buffer.contents b 23 | 24 | let token_of_str state buf = 25 | match state with 26 | | `Start -> STR_START (Buffer.contents buf) 27 | | `Mid -> STR_MID (Buffer.contents buf) 28 | 29 | let token_of_istr state buf = 30 | match state with 31 | | `Start -> ISTR_START (Buffer.contents buf) 32 | | `Mid -> ISTR_MID (Buffer.contents buf) 33 | 34 | (* lookup table for one-character tokens *) 35 | let char_table = Array.make 93 EOF 36 | let _ = 37 | List.iter (fun (k, v) -> Array.set char_table ((int_of_char k) - 1) v) 38 | [ 39 | '.', SELECT; 40 | '?', QMARK; 41 | '!', NOT; 42 | '=', ASSIGN; 43 | '<', LT; 44 | '>', GT; 45 | '[', LBRACK; 46 | ']', RBRACK; 47 | '+', PLUS; 48 | '-', MINUS; 49 | '*', TIMES; 50 | '/', SLASH; 51 | '(', LPAREN; 52 | ')', RPAREN; 53 | ':', COLON; 54 | ';', SEMICOLON; 55 | ',', COMMA; 56 | '@', AS 57 | ] 58 | 59 | (* lookup table for two- and three-character tokens *) 60 | let str_table = Hashtbl.create 10 61 | let _ = 62 | List.iter (fun (kwd, tok) -> Hashtbl.add str_table kwd tok) 63 | [ 64 | "//", MERGE; 65 | "++", CONCAT; 66 | "<=", LTE; 67 | ">=", GTE; 68 | "==", EQ; 69 | "!=", NEQ; 70 | "&&", AND; 71 | "||", OR; 72 | "->", IMPL; 73 | "...", ELLIPSIS 74 | ] 75 | 76 | (* lookup table for keywords *) 77 | let keyword_table = Hashtbl.create 10 78 | let _ = 79 | List.iter (fun (kwd, tok) -> Hashtbl.add keyword_table kwd tok) 80 | [ "with", WITH; 81 | "rec", REC; 82 | "let", LET; 83 | "in", IN; 84 | "inherit", INHERIT; 85 | "null", NULL; 86 | "if" , IF; 87 | "then", THEN; 88 | "else", ELSE; 89 | "assert", ASSERT; 90 | "or", ORDEF ] 91 | 92 | (* replace an escape sequence by the corresponding character(s) *) 93 | let unescape = function 94 | | "\\n" -> "\n" 95 | | "\\r" -> "\r" 96 | | "\\t" -> "\t" 97 | | "\\\\" -> "\\" 98 | | "\\${" -> "${" 99 | | "''$" -> "$" 100 | | "$$" -> "$" 101 | | "'''" -> "''" 102 | | "''\\t" -> "\t" 103 | | "''\\r" -> "\r" 104 | | x -> 105 | failwith (Printf.sprintf "unescape unexpected arg %s" x) 106 | 107 | let collect_tokens lexer q lexbuf = 108 | let stack = ref [] in 109 | let queue = Queue.create () in 110 | let rec go () = 111 | match (try Some (Queue.take queue) with Queue.Empty -> None) with 112 | | Some token -> 113 | ( 114 | match token, !stack with 115 | | AQUOTE_CLOSE, [] -> 116 | Queue.add AQUOTE_CLOSE q 117 | | EOF, _ -> 118 | Queue.add EOF q; 119 | | _, _ -> 120 | Queue.add token q; 121 | go () 122 | ) 123 | | None -> 124 | lexer queue stack lexbuf; 125 | go () 126 | in 127 | Queue.add AQUOTE_OPEN q; 128 | stack := [AQUOTE]; 129 | lexer queue stack lexbuf; 130 | go () 131 | 132 | (* utility functions *) 133 | let print_position lexbuf = 134 | let pos = Lexing.lexeme_start_p lexbuf in 135 | Printf.sprintf "%s:%d:%d" pos.pos_fname 136 | pos.pos_lnum (pos.pos_cnum - pos.pos_bol + 1) 137 | 138 | 139 | let set_filename fname (lexbuf: Lexing.lexbuf) = 140 | let pos = lexbuf.lex_curr_p in 141 | lexbuf.lex_curr_p <- { pos with pos_fname = fname }; lexbuf 142 | 143 | } 144 | 145 | let digit = ['0'-'9'] 146 | let float = digit* '.' digit+ ('e' '-'? digit+)? 147 | let alpha = ['a'-'z' 'A'-'Z'] 148 | let alpha_digit = alpha | digit 149 | let path_chr = alpha_digit | ['.' '_' '-' '+'] 150 | let path = path_chr* ('/' path_chr+)+ 151 | let spath = alpha_digit path_chr* ('/' path_chr+)* 152 | let uri_chr = ['%' '/' '?' ':' '@' '&' '=' '+' '$' ',' '-' '_' '.' '!' '~' '*' '\''] 153 | let scheme = "http" 's'? | "ftp" | "ssh" | "git" | "mirror" | "svn" 154 | let uri = scheme ':' (alpha_digit | uri_chr)+ 155 | (* let uri = alpha (alpha_digit | ['+' '-' '.'])* ':' (alpha_digit | uri_chr)+ *) 156 | let char_tokens = ['.' '?' '!' '=' '<' '>' '[' ']' '+' '-' '*' '/' '(' ')' ':' ';' ',' '@'] 157 | 158 | rule get_tokens q s = parse 159 | (* skip whitespeces *) 160 | | [' ' '\t' '\r'] 161 | { get_tokens q s lexbuf } 162 | (* increase line count for new lines *) 163 | | '\n' 164 | { Lexing.new_line lexbuf; get_tokens q s lexbuf } 165 | | char_tokens as c 166 | { Queue.add (Array.get char_table ((int_of_char c) - 1)) q } 167 | | ("//" | "++" | "<=" | ">=" | "==" | "!=" | "&&" | "||" | "->" | "...") as s 168 | { Queue.add (Hashtbl.find str_table s) q} 169 | | digit+ as i 170 | { Queue.add (INT i) q } 171 | | float 172 | { Queue.add (FLOAT (Lexing.lexeme lexbuf)) q } 173 | | path 174 | { Queue.add (PATH (Lexing.lexeme lexbuf)) q } 175 | | '<' (spath as p) '>' 176 | { Queue.add (SPATH p) q } 177 | | '~' path as p 178 | { Queue.add (HPATH p) q } 179 | | uri 180 | { Queue.add(URI (Lexing.lexeme lexbuf)) q } 181 | | ("true" | "false") as b 182 | { Queue.add (BOOL b) q } 183 | (* keywords or identifies *) 184 | | ((alpha | '_')+ (alpha_digit | ['_' '\'' '-'])*) as id 185 | { Queue.add (try Hashtbl.find keyword_table id with Not_found -> ID id) q} 186 | (* comments *) 187 | | '#' ([^ '\n']* as c) 188 | { (* Queue.add (SCOMMENT c) q *) ignore c; get_tokens q s lexbuf} 189 | | "/*" 190 | { (* Queue.add (comment (Buffer.create 64) lexbuf) q *) 191 | comment (Buffer.create 64) lexbuf; 192 | get_tokens q s lexbuf 193 | } 194 | (* the following three tokens change the braces stack *) 195 | | "${" 196 | { Queue.add AQUOTE_OPEN q; s := AQUOTE :: !s } 197 | | '{' 198 | { Queue.add LBRACE q; s := SET :: !s } 199 | | '}' 200 | { 201 | match !s with 202 | | AQUOTE :: rest -> 203 | Queue.add AQUOTE_CLOSE q; s := rest 204 | | SET :: rest -> 205 | Queue.add RBRACE q; s := rest 206 | | _ -> 207 | let pos = print_position lexbuf in 208 | let err = Printf.sprintf "Unbalanced '}' at %s\n" pos in 209 | raise (Error err) 210 | } 211 | (* a special token to avoid parser conflicts on param sets and attr sets *) 212 | | '{' [' ' '\r' '\t' '\n']* as ws '}' 213 | { 214 | (* change the line number *) 215 | String.iter (fun c -> 216 | if c == '\n' then Lexing.new_line lexbuf else () 217 | ) ws; 218 | Queue.add EMPTY_CURLY q 219 | } 220 | (* a double-quoted string *) 221 | | '"' 222 | { string `Start (Buffer.create 64) q lexbuf } 223 | (* an indented string *) 224 | | "''" 225 | { istring `Start None (Buffer.create 64) q lexbuf } 226 | (* End of input *) 227 | | eof 228 | { Queue.add EOF q } 229 | (* any other character raises an exception *) 230 | | _ 231 | { 232 | let pos = print_position lexbuf in 233 | let tok = Lexing.lexeme lexbuf in 234 | let err = Printf.sprintf "Unexpected character '%s' at %s\n" tok pos in 235 | raise (Error err) 236 | } 237 | 238 | (* Nix does not allow nested comments, but it is still handy to lex it 239 | separately because we can properly increase line count. *) 240 | and comment buf = parse 241 | | '\n' 242 | {Lexing.new_line lexbuf; Buffer.add_char buf '\n'; comment buf lexbuf} 243 | | "*/" 244 | { (* MCOMMENT (Buffer.contents buf) *) ()} 245 | | _ as c 246 | { Buffer.add_char buf c; comment buf lexbuf } 247 | 248 | and string state buf q = parse 249 | | '"' (* terminate when we hit '"' *) 250 | { Queue.add (token_of_str state buf) q; Queue.add STR_END q } 251 | | '\n' 252 | { Lexing.new_line lexbuf; Buffer.add_char buf '\n'; string state buf q lexbuf } 253 | | ("\\n" | "\\r" | "\\t" | "\\\\" | "\\${") as s 254 | { Buffer.add_string buf (unescape s); string state buf q lexbuf } 255 | | "\\" (_ as c) (* add the character verbatim *) 256 | { Buffer.add_char buf c; string state buf q lexbuf } 257 | | "${" (* collect all the tokens till we hit the matching '}' *) 258 | { 259 | Queue.add (token_of_str state buf) q; 260 | collect_tokens get_tokens q lexbuf; 261 | string `Mid (Buffer.create 64) q lexbuf 262 | } 263 | | _ as c (* otherwise just add the character to the buffer *) 264 | { Buffer.add_char buf c; string state buf q lexbuf } 265 | 266 | and istring state imin buf q = parse 267 | | "''" 268 | { 269 | let indent = match imin with | None -> 0 | Some i -> i in 270 | Queue.add (token_of_istr state buf) q; 271 | Queue.add (ISTR_END indent) q 272 | } 273 | | ('\n' (' '* as ws)) as s 274 | { 275 | Lexing.new_line lexbuf; 276 | Buffer.add_string buf s; 277 | let ws_count = String.length ws in 278 | match imin with 279 | | None -> 280 | istring state (Some ws_count) buf q lexbuf 281 | | Some i -> 282 | istring state (Some (min i ws_count)) buf q lexbuf 283 | } 284 | | ("''$" | "'''" | "''\\t" | "''\\r") as s 285 | { Buffer.add_string buf (unescape s); istring state imin buf q lexbuf } 286 | | "''\\" (_ as c) 287 | { Buffer.add_char buf c; istring state imin buf q lexbuf } 288 | | "${" 289 | { 290 | Queue.add (token_of_istr state buf) q; 291 | collect_tokens get_tokens q lexbuf; 292 | istring `Mid imin (Buffer.create 64) q lexbuf 293 | } 294 | | _ as c 295 | { Buffer.add_char buf c; istring state imin buf q lexbuf } 296 | { 297 | 298 | let rec next_token 299 | (q: token Queue.t) 300 | (s: braces list ref) 301 | (lexbuf: Lexing.lexbuf) 302 | : token = 303 | match (try Some (Queue.take q) with | Queue.Empty -> None) with 304 | | Some token -> 305 | token 306 | | None -> 307 | get_tokens q s lexbuf; 308 | next_token q s lexbuf 309 | } 310 | -------------------------------------------------------------------------------- /nix/nix.ml: -------------------------------------------------------------------------------- 1 | module Nix = struct 2 | module Ast = Types 3 | 4 | exception ParseError of string 5 | 6 | let parse (chan:in_channel) (file_name:string) = 7 | let lexbuf = Lexer.set_filename file_name (Lexing.from_channel chan) in 8 | let q, s = Queue.create (), ref [] in 9 | try 10 | Parser.main (Lexer.next_token q s) lexbuf 11 | with 12 | | Lexer.Error msg -> 13 | raise (ParseError (Printf.sprintf "lexing error: %s\n" msg)) 14 | | Parser.Error -> 15 | let msg = Printf.sprintf 16 | "parse error at: %s\n" (Lexer.print_position lexbuf) 17 | in raise (ParseError msg) 18 | end 19 | 20 | include Nix 21 | -------------------------------------------------------------------------------- /nix/parser.mly: -------------------------------------------------------------------------------- 1 | /* Tokens with data */ 2 | %token INT 3 | %token FLOAT 4 | /* a path */ 5 | %token PATH 6 | /* search path, enclosed in <> */ 7 | %token SPATH 8 | /* home path, starts with ~ */ 9 | %token HPATH 10 | %token URI 11 | %token BOOL 12 | %token STR_START 13 | %token STR_MID 14 | %token STR_END 15 | %token ISTR_START 16 | %token ISTR_MID 17 | %token ISTR_END 18 | %token ID 19 | /* %token SCOMMENT */ 20 | /* %token MCOMMENT */ 21 | /* Tokens that stand for themselves */ 22 | %token SELECT "." 23 | %token QMARK "?" 24 | %token CONCAT "++" 25 | %token NOT "!" 26 | %token MERGE "//" 27 | %token ASSIGN "=" 28 | %token LT "<" 29 | %token LTE "<=" 30 | %token GT ">" 31 | %token GTE ">=" 32 | %token EQ "==" 33 | %token NEQ "!=" 34 | %token AND "&&" 35 | %token OR "||" 36 | %token IMPL "->" 37 | %token AQUOTE_OPEN "${" 38 | %token AQUOTE_CLOSE "}$" 39 | %token LBRACE "{" 40 | %token RBRACE "}" 41 | %token LBRACK "[" 42 | %token RBRACK "]" 43 | %token PLUS "+" 44 | %token MINUS "-" 45 | %token TIMES "*" 46 | %token SLASH "/" 47 | %token LPAREN "(" 48 | %token RPAREN ")" 49 | %token COLON ":" 50 | %token SEMICOLON ";" 51 | %token COMMA "," 52 | %token ELLIPSIS "..." 53 | %token AS "@" 54 | /* Keywords */ 55 | %token WITH "with" 56 | %token REC "rec" 57 | %token LET "let" 58 | %token IN "in" 59 | %token INHERIT "inherit" 60 | %token NULL "null" 61 | %token IF "if" 62 | %token THEN "then" 63 | %token ELSE "else" 64 | %token ASSERT "assert" 65 | %token ORDEF "or" 66 | /* A special token to denote {} */ 67 | %token EMPTY_CURLY "{}" 68 | 69 | /* end of input */ 70 | %token EOF 71 | 72 | %{ 73 | open Types 74 | %} 75 | 76 | %start main 77 | 78 | %% 79 | 80 | main: 81 | | e = expr0 EOF 82 | { e } 83 | 84 | expr0: 85 | | "if"; e1 = expr0; "then"; e2 = expr0; "else"; e3 = expr0 86 | { Cond(e1, e2, e3) } 87 | | "with"; e1 = expr0; ";"; e2 = expr0 88 | { With(e1, e2) } 89 | | "assert"; e1 = expr0; ";"; e2 = expr0 90 | { Assert(e1, e2) } 91 | | "let"; xs = nonempty_list(binding); "in"; e = expr0 92 | { Let(xs, e) } 93 | | l = lambda 94 | { Val l } 95 | | e = expr1 96 | { e } 97 | 98 | /* 99 | rules expr1-expr14 are almost direct translation of the operator 100 | precedence table: 101 | https://nixos.org/nix/manual/#sec-language-operators 102 | */ 103 | 104 | %inline binary_expr(Lhs, Op, Rhs): 105 | lhs = Lhs; op = Op; rhs = Rhs 106 | { BinaryOp(op, lhs, rhs) } 107 | 108 | expr1: 109 | | e = binary_expr(expr2, "->" {Impl}, expr1) 110 | | e = expr2 111 | { e } 112 | 113 | expr2: 114 | | e = binary_expr(expr2, "||" {Or}, expr3) 115 | | e = expr3 116 | { e } 117 | 118 | expr3: 119 | | e = binary_expr(expr3, "&&" {And}, expr4) 120 | | e = expr4 121 | { e } 122 | 123 | %inline expr4_ops: 124 | | "==" {Eq} 125 | | "!=" {Neq} 126 | 127 | expr4: 128 | | e = binary_expr(expr5, expr4_ops, expr5) 129 | | e = expr5 130 | { e } 131 | 132 | %inline expr5_ops: 133 | | "<" {Lt} 134 | | ">" {Gt} 135 | | "<=" {Lte} 136 | | ">=" {Gte} 137 | 138 | expr5: 139 | | e = binary_expr(expr6, expr5_ops, expr6) 140 | | e = expr6 141 | { e } 142 | 143 | expr6: 144 | | e = binary_expr(expr7, "//" {Merge}, expr6) 145 | | e = expr7 146 | { e } 147 | 148 | expr7: 149 | | e = preceded("!", expr7) 150 | { UnaryOp(Not, e) } 151 | | e = expr8 152 | { e } 153 | 154 | %inline expr8_ops: 155 | | "+" {Plus} 156 | | "-" {Minus} 157 | 158 | expr8: 159 | | e = binary_expr(expr8, expr8_ops, expr9) 160 | | e = expr9 161 | { e } 162 | 163 | %inline expr9_ops: 164 | | "*" {Mult} 165 | | "/" {Div} 166 | 167 | expr9: 168 | | e = binary_expr(expr9, expr9_ops, expr10) 169 | | e = expr10 170 | { e } 171 | 172 | expr10: 173 | | e = binary_expr(expr11, "++" {Concat}, expr10) 174 | | e = expr11 175 | { e } 176 | 177 | expr11: 178 | | e = expr12 "?" p = attr_path 179 | { Test(e, p) } 180 | | e = expr12 181 | { e } 182 | 183 | expr12: 184 | | e = preceded("-", expr13) 185 | { UnaryOp(Negate, e) } 186 | | e = expr13 187 | { e } 188 | 189 | expr13: 190 | | f = expr13; arg = expr14 191 | { Apply(f, arg) } 192 | | e = expr14 193 | { e } 194 | 195 | %inline selectable: 196 | | s = set 197 | { Val s } 198 | | id = ID 199 | { Id id } 200 | | e = delimited("(", expr0, ")") 201 | { e } 202 | 203 | expr14: 204 | | e = selectable; "."; p = attr_path; o = option(preceded("or", expr14)) 205 | { Select(e, p, o) } 206 | | e = atomic_expr 207 | { e } 208 | 209 | atomic_expr: 210 | | id = ID 211 | { Id id } 212 | | v = value 213 | { Val v } 214 | | e = delimited("(", expr0, ")") 215 | { e } 216 | 217 | attr_path: 218 | | p = separated_nonempty_list(".", attr_path_component) 219 | { p } 220 | 221 | attr_path_component: 222 | | id = ID 223 | {Id id} 224 | | e = delimited("${", expr0, "}$") 225 | { Aquote e } 226 | | s = str 227 | { Val s } 228 | 229 | value: 230 | | s = str 231 | { s } 232 | | s = istr 233 | { s } 234 | | i = INT 235 | {Int i} 236 | | f = FLOAT 237 | { Float f } 238 | | p = PATH 239 | { Path p } 240 | | sp = SPATH 241 | { SPath sp } 242 | | hp = HPATH 243 | { HPath hp } 244 | | uri = URI 245 | { Uri uri } 246 | | b = BOOL 247 | { Bool b } 248 | | "null" 249 | { Null } 250 | | l = nixlist 251 | { l } 252 | | s = set 253 | { s } 254 | 255 | %inline str_mid(X): 256 | xs = list(pair(delimited("${", expr0, "}$"), X)) { xs } 257 | 258 | /* double-quoted string */ 259 | str: 260 | start = STR_START; mids = str_mid(STR_MID); STR_END 261 | { Str(start, mids) } 262 | 263 | /* indented string */ 264 | istr: 265 | start = ISTR_START; mids = str_mid(ISTR_MID); i = ISTR_END 266 | { IStr(i, start, mids) } 267 | 268 | /* lists and sets */ 269 | nixlist: 270 | xs = delimited("[", list(expr14), "]") 271 | { List xs } 272 | 273 | set: 274 | | "{}" 275 | { AttSet [] } 276 | | "rec"; "{}" 277 | { RecAttSet [] } 278 | | xs = delimited("{", list(binding), "}") 279 | { AttSet xs } 280 | | xs = preceded("rec", delimited("{", list(binding), "}")) 281 | { RecAttSet xs } 282 | 283 | binding: 284 | | kv = terminated(separated_pair(attr_path, "=", expr0), ";") 285 | { let (k, v) = kv in AttrPath(k, v) } 286 | | xs = delimited("inherit", pair(option(delimited("(", expr0, ")")), list(ID)), ";") 287 | { let (prefix, ids) = xs in Inherit(prefix, ids) } 288 | 289 | lambda: 290 | | id = ID; "@"; p = param_set; ":"; e = expr0 291 | { Lambda(AliasedSet(id, p), e) } 292 | | p = param_set; "@"; id = ID; ":"; e = expr0 293 | { Lambda(AliasedSet(id, p), e) } 294 | | p = param_set; ":"; e = expr0 295 | { Lambda(ParamSet p, e) } 296 | | id = ID; ":"; e = expr0 297 | { Lambda(Alias id, e) } 298 | 299 | 300 | %inline param_set: 301 | | "{}" 302 | { ([], None) } 303 | | ps = delimited("{", params, "}") 304 | { ps } 305 | 306 | params: 307 | | "..." 308 | { ([], Some ()) } 309 | | p = param 310 | { ([p], None) } 311 | | p = param; ","; ps = params 312 | { let (prev, ellipsis) = ps in (p :: prev, ellipsis) } 313 | 314 | param: 315 | p = pair(ID, option(preceded("?", expr0))) 316 | { p } 317 | -------------------------------------------------------------------------------- /nix/tokens.ml: -------------------------------------------------------------------------------- 1 | type token = 2 | (* Tokens with data *) 3 | | INT of string 4 | | FLOAT of string 5 | (* a path *) 6 | | PATH of string 7 | (* search path, enclosed in <> *) 8 | | SPATH of string 9 | (* home path, starts with ~ *) 10 | | HPATH of string 11 | | URI of string 12 | | BOOL of string 13 | | STR_START of string 14 | | STR_MID of string 15 | | STR_END 16 | | ISTR_START of string 17 | | ISTR_MID of string 18 | | ISTR_END of int 19 | | ID of string 20 | (* | SCOMMENT *) 21 | (* | MCOMMENT *) 22 | (* Tokens that stand for themselves *) 23 | | SELECT 24 | | QMARK 25 | | CONCAT 26 | | NOT 27 | | MERGE 28 | | ASSIGN 29 | | LT 30 | | LTE 31 | | GT 32 | | GTE 33 | | EQ 34 | | NEQ 35 | | AND 36 | | OR 37 | | IMPL 38 | | AQUOTE_OPEN 39 | | AQUOTE_CLOSE 40 | | LBRACE 41 | | RBRACE 42 | | LBRACK 43 | | RBRACK 44 | | PLUS 45 | | MINUS 46 | | TIMES 47 | | SLASH 48 | | LPAREN 49 | | RPAREN 50 | | COLON 51 | | SEMICOLON 52 | | COMMA 53 | | ELLIPSIS 54 | | AS 55 | (* Keywords *) 56 | | WITH 57 | | REC 58 | | LET 59 | | IN 60 | | INHERIT 61 | | NULL 62 | | IF 63 | | THEN 64 | | ELSE 65 | | ASSERT 66 | | ORDEF 67 | (* A special token to denote {} *) 68 | | EMPTY_CURLY 69 | (* end of input *) 70 | | EOF 71 | -------------------------------------------------------------------------------- /nix/types.ml: -------------------------------------------------------------------------------- 1 | (* Binary operators *) 2 | type binary_op = 3 | | Plus 4 | | Minus 5 | | Mult 6 | | Div 7 | | Gt 8 | | Lt 9 | | Lte 10 | | Gte 11 | | Eq 12 | | Neq 13 | | Or 14 | | And 15 | | Impl 16 | | Merge 17 | | Concat 18 | 19 | (* Unary operators *) 20 | type unary_op = 21 | | Negate 22 | | Not 23 | 24 | (* The top-level expression type *) 25 | type expr = 26 | | BinaryOp of binary_op * expr * expr 27 | | UnaryOp of unary_op * expr 28 | | Cond of expr * expr * expr 29 | | With of expr * expr 30 | | Assert of expr * expr 31 | | Test of expr * expr list 32 | | Let of binding list * expr 33 | | Val of value 34 | | Id of id 35 | | Select of expr * expr list * expr option 36 | | Apply of expr * expr 37 | | Aquote of expr 38 | 39 | (* Possible values *) 40 | and value = 41 | (* Str is a string start, followed by arbitrary number of antiquotations and 42 | strings that separate them *) 43 | | Str of string * (expr * string) list 44 | (* IStr is an indented string, so it has the extra integer component which 45 | indicates the indentation *) 46 | | IStr of int * string * (expr * string) list 47 | | Int of string 48 | | Float of string 49 | | Path of string 50 | | SPath of string 51 | | HPath of string 52 | | Uri of string 53 | | Bool of string 54 | | Lambda of pattern * expr 55 | | List of expr list 56 | | AttSet of binding list 57 | | RecAttSet of binding list 58 | | Null 59 | 60 | (* Patterns in lambdas definitions *) 61 | and pattern = 62 | | Alias of id 63 | | ParamSet of param_set 64 | | AliasedSet of id * param_set 65 | 66 | and param_set = param list * unit option 67 | 68 | and param = id * expr option 69 | 70 | (* Bindings in attribute sets and let expressions *) 71 | and binding = 72 | (* The first expr should be attrpath, which is the same as in Select *) 73 | | AttrPath of expr list * expr 74 | | Inherit of expr option * id list 75 | 76 | (* Identifiers *) 77 | and id = string 78 | 79 | (* precedence levels of binary operators *) 80 | let prec_of_bop = function 81 | | Concat -> 5 82 | | Mult | Div -> 6 83 | | Plus | Minus -> 7 84 | | Merge -> 9 85 | | Gt | Lt | Lte | Gte -> 10 86 | | Eq | Neq -> 11 87 | | And -> 12 88 | | Or -> 13 89 | | Impl -> 14 90 | 91 | (* precedence levels of unary operatots *) 92 | let prec_of_uop = function 93 | | Negate -> 3 94 | | Not -> 8 95 | 96 | (* precedence level of expressions 97 | (assuming that the constituents have higher levels) 98 | *) 99 | let prec_of_expr = function 100 | | Val (Lambda _) -> 15 101 | | Val _ | Id _ | Aquote _ -> 0 102 | | BinaryOp(op, _, _) -> prec_of_bop op 103 | | UnaryOp(op, _) -> prec_of_uop op 104 | | Cond _ | With _ | Assert _ | Let _ -> 15 105 | | Test _ -> 4 106 | | Select _ -> 1 107 | | Apply _ -> 2 108 | -------------------------------------------------------------------------------- /nixformat.opam: -------------------------------------------------------------------------------- 1 | # This file is generated by dune, edit dune-project instead 2 | opam-version: "2.0" 3 | synopsis: "Nix code formatter" 4 | description: "A code formatter written in ocaml using menhir and ocamllex" 5 | authors: ["Denis Korzunov "] 6 | license: "MIT" 7 | homepage: "https://github.com/d2km/nixformat" 8 | bug-reports: "https://github.com/d2km/nixformat/issues" 9 | depends: [ 10 | "dune" {>= "3.0"} 11 | "menhir" 12 | "pprint" 13 | "odoc" {with-doc} 14 | ] 15 | build: [ 16 | ["dune" "subst"] {dev} 17 | [ 18 | "dune" 19 | "build" 20 | "-p" 21 | name 22 | "-j" 23 | jobs 24 | "@install" 25 | "@runtest" {with-test} 26 | "@doc" {with-doc} 27 | ] 28 | ] 29 | dev-repo: "git+https://github.com/d2km/nixformat.git" 30 | -------------------------------------------------------------------------------- /pprinter.ml: -------------------------------------------------------------------------------- 1 | (* Interface for pretty-printing modules *) 2 | module type PPRINTER = sig 3 | val print: out_channel -> Nix.Ast.expr -> unit 4 | end 5 | -------------------------------------------------------------------------------- /pretty_printer.ml: -------------------------------------------------------------------------------- 1 | module PrettyPrinter : sig 2 | include Pprinter.PPRINTER 3 | val set_width: int -> unit 4 | val set_indent: int -> unit 5 | end = struct 6 | open Nix.Ast 7 | open PPrint 8 | 9 | let out_width = ref 80 10 | let set_width i = out_width := i 11 | 12 | let indent = ref 2 13 | let set_indent i = indent := i 14 | 15 | let rec doc_of_expr = function 16 | | BinaryOp(op, lhs, rhs) -> 17 | let lvl = prec_of_bop op in 18 | let lhs_doc = maybe_parens lvl lhs in 19 | let rhs_doc = maybe_parens lvl rhs in 20 | (* TODO: don't use parens if it's a chain of ops, e.g. 1+1+1 *) 21 | infix !indent 1 (doc_of_bop op) lhs_doc rhs_doc 22 | 23 | | UnaryOp(op, e) -> 24 | precede (doc_of_uop op) (maybe_parens (prec_of_uop op) e) 25 | 26 | | Cond(e1, e2, e3) -> 27 | surround !indent 1 28 | (soft_surround !indent 1 29 | (string "if") (doc_of_expr e1) (string "then")) 30 | (doc_of_expr e2) 31 | (string "else" ^^ 32 | (nest !indent (break 1 ^^ doc_of_expr e3))) 33 | 34 | | With(e1, e2) -> 35 | flow (break 1) [ 36 | string "with"; 37 | doc_of_expr e1 ^^ semi; 38 | doc_of_expr e2 39 | ] 40 | 41 | | Assert(e1, e2) -> 42 | flow (break 1) [ 43 | string "assert"; 44 | doc_of_expr e1 ^^ semi; 45 | doc_of_expr e2 46 | ] 47 | 48 | | Test(e, path) -> 49 | maybe_parens 4 e ^^ string "?" ^^ 50 | group (break 1 ^^ separate_map dot doc_of_expr path) 51 | 52 | | Let(bs, e) -> 53 | surround !indent 1 54 | (string "let") 55 | (separate_map (break 1) doc_of_binding bs) 56 | (prefix !indent 1 (string "in") (doc_of_expr e)) 57 | 58 | | Val v -> 59 | doc_of_val v 60 | 61 | | Id id -> 62 | string id 63 | 64 | | Select(e, path, oe) -> 65 | maybe_parens 1 e ^^ dot ^^ 66 | doc_of_attpath path ^^ 67 | optional (fun e -> 68 | space ^^ string "or" ^^ 69 | nest !indent ( break 1 ^^ maybe_parens 1 e) 70 | ) oe 71 | 72 | | Apply(e1, e2) -> 73 | prefix !indent 1 (maybe_parens 2 e1) (maybe_parens 2 e2) 74 | 75 | | Aquote e -> 76 | surround !indent 0 (string "${") (doc_of_expr e) (string "}") 77 | 78 | and maybe_parens lvl e = 79 | if prec_of_expr e >= lvl then 80 | surround !indent 0 lparen (doc_of_expr e) rparen 81 | else 82 | doc_of_expr e 83 | 84 | and doc_of_attpath path = 85 | separate_map dot doc_of_expr path 86 | 87 | and doc_of_paramset(params, ellipsis) = 88 | let ps = List.map doc_of_param params @ match ellipsis with 89 | | Some _ -> [string "..."] 90 | | None -> [] 91 | in 92 | surround !indent 0 93 | lbrace 94 | (separate (comma ^^ break 1) ps) 95 | rbrace 96 | 97 | and doc_of_param(id, oe) = 98 | string id ^^ optional (fun e -> 99 | qmark ^^ space ^^ doc_of_expr e 100 | ) oe 101 | 102 | and doc_of_binding = function 103 | | AttrPath(path, e) -> 104 | (doc_of_attpath path) ^^ 105 | space ^^ equals ^^ space ^^ 106 | doc_of_expr e ^^ semi 107 | 108 | | Inherit(oe, ids) -> 109 | let id_docs = List.map string ids in 110 | let xs = flow (break 1) ( 111 | match oe with 112 | | Some e -> (parens (doc_of_expr e)) :: id_docs 113 | | None -> id_docs 114 | ) 115 | in 116 | soft_surround !indent 0 (string "inherit" ^^ space) xs semi 117 | 118 | and doc_of_bop = function 119 | | Plus -> plus 120 | | Minus -> minus 121 | | Mult -> star 122 | | Div -> slash 123 | | Gt -> rangle 124 | | Lt -> langle 125 | | Lte -> string "<=" 126 | | Gte -> string ">=" 127 | | Eq -> string "==" 128 | | Neq -> string "!=" 129 | | Or -> string "||" 130 | | And -> string "&&" 131 | | Impl -> string "->" 132 | | Merge -> string "//" 133 | | Concat -> string "++" 134 | 135 | and doc_of_uop = function 136 | | Negate -> minus 137 | | Not -> bang 138 | 139 | and doc_of_val = function 140 | | Str(start, xs) -> 141 | dquotes ( 142 | string start ^^ 143 | concat (List.map (fun (e, s) -> 144 | surround !indent 0 145 | (string "${") 146 | (doc_of_expr e) 147 | (string "}" ^^ string s) 148 | ) xs )) 149 | 150 | | IStr(i, start, xs) -> 151 | let qq = string "''" in 152 | let str s = 153 | String.split_on_char '\n' s 154 | |> List.map (fun s -> 155 | let len = String.length s in 156 | let s' = if len >= i then String.sub s i (len - i) else s in 157 | string s' 158 | ) 159 | |> separate hardline 160 | in 161 | enclose qq qq ( 162 | str start ^^ 163 | concat (List.map (fun (e, s) -> 164 | enclose (string "${") rbrace (doc_of_expr e) ^^ str s 165 | ) xs )) 166 | 167 | | Int x | Float x | Path x | SPath x | HPath x | Uri x | Bool x -> 168 | string x 169 | 170 | | Lambda(pattern, body) -> 171 | let pat = match pattern with 172 | | Alias id -> 173 | string id 174 | | ParamSet ps -> 175 | doc_of_paramset ps 176 | | AliasedSet(id, ps) -> 177 | doc_of_paramset ps ^^ 178 | group (break 1 ^^ at ^^ break 1 ^^ string id) 179 | in 180 | flow (break 1) [ 181 | pat ^^ colon; 182 | doc_of_expr body 183 | ] 184 | 185 | | List es -> 186 | surround !indent 1 187 | lbracket 188 | (separate_map (break 1) doc_of_expr es) 189 | rbracket 190 | 191 | | AttSet bs -> 192 | surround !indent 1 193 | lbrace 194 | (group (separate_map (break 1) doc_of_binding bs)) 195 | rbrace 196 | 197 | | RecAttSet bs -> 198 | string "rec" ^^ space ^^ surround !indent 1 199 | lbrace 200 | (group (separate_map (break 1) doc_of_binding bs)) 201 | rbrace 202 | 203 | | Null -> 204 | string "null" 205 | 206 | let print chan expr = 207 | ToChannel.pretty 0.7 !out_width chan (doc_of_expr expr) 208 | 209 | end 210 | 211 | include PrettyPrinter 212 | -------------------------------------------------------------------------------- /simple_printer.ml: -------------------------------------------------------------------------------- 1 | module SimplePrinter : Pprinter.PPRINTER = struct 2 | open Nix.Ast 3 | 4 | let rec print chan = function 5 | | BinaryOp(op, lhs, rhs) -> 6 | print chan lhs; print_bop chan op; print chan rhs 7 | | UnaryOp(op, e) -> 8 | print_uop chan op; print chan e 9 | | Cond(e1, e2, e3) -> 10 | output_string chan "if "; 11 | print chan e1; 12 | output_string chan " then "; 13 | print_paren chan e2; 14 | output_string chan " else "; 15 | print chan e3 16 | | With(e1, e2) -> 17 | output_string chan "with "; 18 | print chan e1; 19 | output_string chan "; "; 20 | print chan e2 21 | | Assert(e1, e2) -> 22 | output_string chan "assert "; 23 | print chan e1; 24 | output_string chan "; "; 25 | print chan e2 26 | | Test(e1, attpath) -> 27 | print chan e1; 28 | output_string chan "? "; 29 | separated_list chan ", " attpath 30 | | Let(bindings, e) -> 31 | output_string chan "let "; 32 | List.iter (fun binding -> 33 | print_binding chan binding; 34 | output_char chan ' ' 35 | ) bindings; 36 | output_string chan " in "; 37 | print chan e 38 | | Val v -> 39 | print_val chan v 40 | | Id id -> 41 | output_string chan id 42 | | Select(e1, components, defval) -> 43 | print_path_component chan e1; 44 | List.iter (fun c -> 45 | output_char chan '.'; 46 | print_path_component chan c 47 | ) components; 48 | ( 49 | match defval with 50 | | Some e -> 51 | output_string chan " or "; 52 | print_paren chan e 53 | | None -> 54 | () 55 | ) 56 | 57 | | Apply(f, arg) -> 58 | print chan f; 59 | output_char chan ' '; 60 | delimited chan "(" arg ")" 61 | 62 | | Aquote e -> 63 | delimited chan "${" e "}" 64 | 65 | and print_paren chan e = 66 | delimited chan "(" e ")" 67 | 68 | and print_path_component chan = function 69 | | (Val _) as e -> print chan e 70 | | (Id _) as e -> print chan e 71 | | e -> print_paren chan e 72 | 73 | and print_bop chan op = 74 | output_string chan (match op with 75 | | Plus -> "+" 76 | | Minus -> "-" 77 | | Mult -> "*" 78 | | Div -> "/" 79 | | Gt -> ">" 80 | | Lt -> "<" 81 | | Lte -> "<=" 82 | | Gte -> ">=" 83 | | Eq -> "==" 84 | | Neq -> "!=" 85 | | Or -> "||" 86 | | And -> "&&" 87 | | Impl -> "->" 88 | | Merge -> "//" 89 | | Concat -> "++" 90 | ) 91 | 92 | and print_uop chan op = 93 | output_char chan (match op with | Negate -> '-' | Not -> '-') 94 | 95 | and print_val chan = function 96 | | Str(s, mids) -> print_str chan (s, mids) 97 | | IStr(i, s, mids) -> print_istr chan (i, s, mids) 98 | | Int i -> output_string chan i 99 | | Float f -> output_string chan f 100 | | Path p -> output_string chan p 101 | | SPath s -> output_string chan s 102 | | HPath h -> output_string chan h 103 | | Uri u -> output_string chan u 104 | | Bool b -> output_string chan b 105 | | Lambda(p, e) -> print_lam chan (p, e) 106 | | List es -> 107 | output_string chan "["; 108 | List.iter (fun e -> output_char chan ' '; 109 | print_paren chan e 110 | ) es; 111 | output_string chan " ]" 112 | | AttSet atts -> 113 | output_string chan "{ "; 114 | List.iter (fun att -> 115 | print_binding chan att; 116 | output_char chan ' ' 117 | ) atts; 118 | output_char chan '}' 119 | | RecAttSet atts -> 120 | output_string chan "rec { "; 121 | List.iter (fun att -> 122 | print_binding chan att; 123 | output_char chan ' ' 124 | ) atts; 125 | output_char chan '}' 126 | 127 | | Null -> 128 | output_string chan "null" 129 | 130 | and print_str chan (s, mids) = 131 | output_char chan '"'; 132 | output_string chan s; 133 | List.iter (fun (e, s) -> 134 | output_string chan "${"; 135 | print chan e; 136 | output_char chan '}'; 137 | output_string chan s 138 | ) mids; 139 | output_char chan '"' 140 | 141 | and print_istr chan (_, s, mids) = 142 | output_string chan "''"; 143 | output_string chan s; 144 | List.iter (fun (e, s) -> 145 | output_string chan "${"; 146 | print chan e; 147 | output_char chan '}'; 148 | output_string chan s 149 | ) mids; 150 | output_string chan "''" 151 | 152 | and print_lam chan (p, e) = 153 | (match p with 154 | | Alias x -> 155 | output_string chan x 156 | | ParamSet ps -> 157 | output_char chan '{'; 158 | print_param_set chan ps; 159 | output_char chan '}' 160 | | AliasedSet (id, ps) -> 161 | output_char chan '{'; 162 | print_param_set chan ps; 163 | output_string chan "}@ "; 164 | output_string chan id 165 | ); 166 | output_string chan ": "; 167 | print chan e 168 | 169 | and print_param_set chan = function 170 | | (head :: tail), Some () -> 171 | print_param chan head; 172 | List.iter (fun x -> 173 | output_string chan ", "; 174 | print_param chan x 175 | ) tail; 176 | output_string chan ", ..." 177 | 178 | | (head :: tail), None -> 179 | print_param chan head; 180 | List.iter (fun x -> 181 | output_string chan ", "; 182 | print_param chan x 183 | ) tail 184 | 185 | | [], Some () -> 186 | output_string chan "..." 187 | 188 | | [], None -> 189 | () 190 | 191 | and print_param chan (id, maybe_expr) = 192 | output_string chan id; 193 | match maybe_expr with 194 | | Some e -> 195 | output_string chan "? "; 196 | print chan e 197 | | None -> () 198 | 199 | and print_binding chan = function 200 | | AttrPath(es, e) -> 201 | separated_list chan "." es; 202 | output_string chan " = "; 203 | print chan e; 204 | output_char chan ';' 205 | | Inherit(maybe_e, ids) -> 206 | output_string chan "inherit "; 207 | ( 208 | match maybe_e with 209 | | Some e -> 210 | print_paren chan e 211 | | None -> () 212 | ); 213 | List.iter (fun x -> 214 | output_char chan ' '; 215 | output_string chan x 216 | ) ids; 217 | output_char chan ';' 218 | 219 | and delimited chan l e r = 220 | output_string chan l; 221 | print chan e; 222 | output_string chan r 223 | 224 | and separated_list chan sep xs = 225 | match xs with 226 | | head :: tail -> 227 | print chan head; 228 | List.iter (fun e -> 229 | output_string chan sep; 230 | print chan e 231 | ) tail 232 | | [] -> 233 | () 234 | 235 | end 236 | 237 | include SimplePrinter 238 | --------------------------------------------------------------------------------