Skip to content

Shebang lines and bare expressions in .fsx scripts cause parse errors #167

@bbatsov

Description

@bbatsov

I'm building an Emacs major mode (fsharp-ts-mode) using this grammar and ran into two related issues with script files.

Shebang lines

A standard .fsx shebang produces an ERROR node and poisons the rest of the parse:

#!/usr/bin/env dotnet fsi
let x = 1

Parses as:

(file (ERROR) (application_expression ...) (ERROR (function_or_value_defn ...)))

Most tree-sitter grammars for scripting languages handle shebangs natively (e.g., tree-sitter-python, tree-sitter-bash). Would it be possible to add shebang support to the file rule?

I currently work around this by excluding the first line from the parser's range when it starts with #!.

Bare expressions between declarations

Mixing let bindings with bare expressions (common in .fsx scripts) causes the grammar to chain subsequent declarations under application_expression or produce ERROR nodes:

let x = 1
printfn "%d" x
let y = 2

Parses as:

(file
  (declaration_expression (function_or_value_defn ...))   ;; let x = 1
  (application_expression ...)                             ;; printfn "%d" x
  (ERROR (function_or_value_defn ...)))                    ;; let y = 2 ← ERROR

The let y = 2 ends up inside an ERROR because the grammar doesn't expect a declaration_expression after a bare application_expression at the top level. With more bare expressions the nesting gets deeper:

let x = 1
printfn "%d" x
let y = 2
printfn "%d" y
let z = 3

Here let y nests under application_expression and let z nests two levels deep. Using do printfn ... instead works correctly, but most F# scripts don't use do for bare expressions.

Could the file rule be extended to accept application_expression (or a more general expression) as a top-level alternative alongside declaration_expression? Something like how F# Interactive accepts both declarations and expressions at the top level.

I currently work around this by detecting declarations whose ancestor chain leads back to file through application_expression/ERROR nodes and forcing them to column 0, but it's fragile.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions