rework grammar to be friendly to injections

want to later support the case of multiple nix-shell directives in injection
This commit is contained in:
2023-07-05 00:01:38 +00:00
parent d5adafbc03
commit c9d79b0734
5 changed files with 114 additions and 149 deletions

View File

@@ -2,136 +2,84 @@
empty nix-shell
==================
#!/usr/bin/env nix-shell
#!nix-shell
---
(source_file
(first_line)
(next_line
(annotation_line)))
(nix_shell_directive))
==================
whitespace in nix-shell
nix-shell no interpreter
==================
#!/usr/bin/env nix-shell
#! nix-shell
#!nix-shell -p gnused
---
(source_file
(first_line)
(next_line
(annotation_line)))
(nix_shell_directive))
==================
minimal nix-shell invocation with interpreter
==================
#!/usr/bin/env nix-shell
#!nix-shell -i bash
---
(source_file
(first_line)
(next_line
(annotation_line
(nix_shell_args
(interpreter
(sh_arg))))))
(nix_shell_directive
(interpreter)))
==================
whitespace in nix-shell
==================
#! nix-shell -i bash
---
(source_file
(nix_shell_directive
(interpreter)))
==================
non-interpreter argument
==================
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p gnused
---
(source_file
(first_line)
(next_line
(annotation_line
(nix_shell_args
(interpreter
(sh_arg))
(sh_arg)
(sh_arg)))))
(nix_shell_directive
(interpreter)))
==================
non-interpreter flags
==================
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p gnused awk
---
(source_file
(first_line)
(next_line
(annotation_line
(nix_shell_args
(interpreter
(sh_arg))
(sh_arg)
(sh_arg)
(sh_arg)))))
(nix_shell_directive
(interpreter)))
==================
quoted arguments
==================
#!/usr/bin/env nix-shell
#!nix-shell -i bash arg\ one 'arg"Two' "'argThree"
---
(source_file
(first_line)
(next_line
(annotation_line
(nix_shell_args
(interpreter
(sh_arg))
(sh_arg)
(sh_arg)
(sh_arg)))))
(nix_shell_directive
(interpreter)))
==================
order-independent
==================
#!/usr/bin/env nix-shell
#!nix-shell -p gnused -i bash
---
(source_file
(first_line)
(next_line
(annotation_line
(nix_shell_args
(sh_arg)
(sh_arg)
(interpreter
(sh_arg))))))
==================
multiple annotation lines
==================
#!/usr/bin/env nix-shell
#!nix-shell -p gnused
#!nix-shell -i bash
---
(source_file
(first_line)
(next_line
(annotation_line
(nix_shell_args
(sh_arg)
(sh_arg))))
(next_line
(annotation_line
(nix_shell_args
(interpreter
(sh_arg))))))
(nix_shell_directive
(interpreter)))

View File

@@ -1,3 +1,11 @@
==================
empty
==================
---
(source_file)
==================
nix-shell with a bash payload
==================
@@ -8,13 +16,8 @@ echo "hello, world!"
---
(source_file
(first_line)
(next_line
(annotation_line
(nix_shell_args
(interpreter
(sh_arg)))))
(next_line))
(nix_shell_directive
(interpreter)))
==================
nix-shell with interspersed payload
@@ -28,12 +31,39 @@ echo everybody
---
(source_file
(first_line)
(next_line)
(next_line)
(next_line
(annotation_line
(nix_shell_args
(interpreter
(sh_arg)))))
(next_line))
(nix_shell_directive
(interpreter)))
==================
nix-shell with multiple directives
==================
#!/usr/bin/env nix-shell
echo hello
#!nix-shell -p gnused
#!nix-shell -i bash
echo again
#!nix-shell
echo everybody
---
(source_file
(nix_shell_directive)
(nix_shell_directive
(interpreter))
(nix_shell_directive))
==================
nix-shell with conflicting interpreters (last wins, but good luck with that)
==================
#!/usr/bin/env nix-shell
#!nix-shell -i bash
#!nix-shell -i python3
---
(source_file
(nix_shell_directive
(interpreter))
(nix_shell_directive
(interpreter)))

View File

@@ -5,8 +5,7 @@ nix-shell shebang without any interpreter option
#!/usr/bin/env nix-shell
---
(source_file
(first_line))
(source_file)
==================
nix-shell shebang whitespace
@@ -15,8 +14,7 @@ nix-shell shebang whitespace
#! /usr/bin/env nix-shell
---
(source_file
(first_line))
(source_file)
==================
nix-shell shebang tab
@@ -25,8 +23,7 @@ nix-shell shebang tab
#! /usr/bin/env nix-shell
---
(source_file
(first_line))
(source_file)
==================
nix-shell shebang tabs and spaces
@@ -35,28 +32,7 @@ nix-shell shebang tabs and spaces
#! /usr/bin/env nix-shell
---
(source_file
(first_line))
==================
nix-shell shebang no env
==================
#!nix-shell
---
(source_file
(first_line))
==================
nix-shell shebang whitespace no env
==================
#! nix-shell
---
(source_file
(first_line))
(source_file)
==================
nix-shell shebang via relative env
@@ -65,8 +41,7 @@ nix-shell shebang via relative env
#!env nix-shell
---
(source_file
(first_line))
(source_file)
==================
nix-shell shebang via /bin/env
@@ -75,5 +50,14 @@ nix-shell shebang via /bin/env
#!/bin/env nix-shell
---
(source_file
(first_line))
(source_file)
==================
nix-shell multiple shebangs
==================
#!/usr/bin/env nix-shell
#!/usr/bin/env nix-shell
---
(source_file)

View File

@@ -2,26 +2,29 @@ module.exports = grammar({
name: 'nix_shell',
rules: {
// TODO: allow trailing whitespace on any line
source_file: $ => seq($.first_line, repeat($.next_line)),
first_line: $ => seq($._shebang_open, $._opt_ws, optional(seq($._env, $._ws)), $._nix_shell),
next_line: $ => seq($._newline, choice(
$.annotation_line,
repeat(/./),
)),
annotation_line: $ => seq($._shebang_open, $._opt_ws, $._nix_shell, optional($.nix_shell_args)),
source_file: $ => seq(
optional($._first_line),
repeat($._next_line),
),
_first_line: $ => choice(
$.nix_shell_directive,
repeat1(/./),
),
_next_line: $ => seq('\n', $._first_line),
nix_shell_directive: $ => seq(
/#![ \t]*nix-shell/,
repeat(seq($._ws, optional($._nix_shell_arg))),
),
// nix acts as if it actually just `exec`'s whichever line contains `#! nix-shell` in the style of `sh`'s `exec`.
// so arguments can be quoted, invoke subshells, interpolate environment variables, etc.
// the full scope cannot be covered, but the minimal scope here gets 99% of use cases.
nix_shell_args: $ => repeat1(seq($._ws, $._nix_shell_arg)),
_nix_shell_arg: $ => choice(
seq('-i', $._opt_ws, $.interpreter),
$.sh_arg,
$._sh_arg,
),
interpreter: $ => $.sh_arg,
sh_arg: $ => choice(
interpreter: $ => $._sh_arg,
_sh_arg: $ => choice(
$._sh_lit,
$._sh_quote1,
$._sh_quote2,
@@ -47,20 +50,12 @@ module.exports = grammar({
'"',
),
_shebang_open: $ => '#!',
// TODO: env accepts flags like `-v` or `--unset=NAME` or `VAR=VALUE` before `nix-shell`,
_env: $ => choice(
'/usr/bin/env',
'/bin/env',
'env',
),
_nix_shell: $ => 'nix-shell',
// N.B.: this accepts more than it needs to:
// - shebang parser allows tab characters
// - #!nix-shell directives do not support tabs
// - wherever #!nix-shell directives allow whitespace, it's a *single* space -- never multiple
// - some places where whitespace is accepted (e.g. `#! nix-shell`), only a *single* space is accepted.
// it's not clear how much of this is intentional, v.s. oversight in matching broader shell parsing,
// so err on overaccepting in case future nix-shell supports these.
_ws: $ => /[ \t]+/,
_opt_ws: $ => /[ \t]*/,
_newline: $ => '\n',
}
});

8
queries/injections.scm Normal file
View File

@@ -0,0 +1,8 @@
; if we find an interpreter, mark the *entire file* as injected content.
; nix-shell doesn't process the source file before sending it to the interpreter,
; so nix-shell directives like `#!nix-shell -i bash` are both a directive to nix-shell
; and actual language content handled by the specified interpreter.
((source_file
(nix_shell_directive
(interpreter) @injection.language)) @injection.content
(#set! injection.include-children))