Lexer for Bash syntax.
Produces a stream of tokens with position information for the parser to consume. Handles quoting, escapes, and nested structures (command substitution, etc.) correctly.
Token Types
Tokens are tuples of {type, value, line, column} or {type, line, column} for
valueless tokens like operators.
Word tokens
{:word, parts, line, col}- A word with parts (literals, variables, etc.){:assignment_word, name, value_parts, line, col}- VAR=value
Operators
{:pipe, line, col}- |{:and_if, line, col}- &&{:or_if, line, col}- ||{:background, line, col}- &{:semi, line, col}- ;{:dsemi, line, col}- ;;{:semi_and, line, col}- ;&{:dsemi_and, line, col}- ;;&{:newline, line, col}- \n{:lparen, line, col}- ({:rparen, line, col}- ){:lbrace, line, col}- {{:rbrace, line, col}- }
Redirections
{:less, fd, line, col}- <{:greater, fd, line, col}- >{:dgreater, fd, line, col}- >>{:lessand, fd, line, col}- <&{:greaterand, fd, line, col}- >&{:lessgreat, fd, line, col}- <>{:dless, fd, line, col}- << (heredoc){:dlessdash, fd, line, col}- <<- (heredoc with tab stripping){:tless, fd, line, col}- <<< (herestring){:andgreat, line, col}- &>{:anddgreat, line, col}- &>>{:brace_fd, varname, line, col}- {VAR} before redirect operator
Reserved words (context-dependent)
{:if, line, col}{:then, line, col}{:else, line, col}{:elif, line, col}{:fi, line, col}{:case, line, col}{:esac, line, col}{:for, line, col}{:while, line, col}{:until, line, col}{:do, line, col}{:done, line, col}{:in, line, col}{:function, line, col}{:bang, line, col}- !
Test constructs
{:lbracket, line, col}- [{:rbracket, line, col}- ]{:dlbracket, line, col}- [[{:drbracket, line, col}- ]]
Arithmetic
{:arith_command, content, line, col}- ((...)) arithmetic command with raw content
Special
{:eof, line, col}- End of input{:comment, text, line, col}- # comment
Summary
Functions
Tokenize a Bash script into a list of tokens.
Types
@type brace_spec() :: %{ type: :list | :range, items: [[word_part()]] | nil, range_start: String.t() | nil, range_end: String.t() | nil, step: integer() | nil, zero_pad: non_neg_integer() | nil }
@type heredoc_pending() :: %{ delimiter: String.t(), strip_tabs: boolean(), expand: boolean(), start_line: pos_integer(), start_col: pos_integer() }
@type position() :: {line :: pos_integer(), column :: pos_integer()}
@type state() :: %{ input: String.t(), pos: non_neg_integer(), line: pos_integer(), column: pos_integer(), in_test_expr: boolean(), after_regex_op: boolean(), pending_heredocs: [heredoc_pending()] }
@type token() :: {:word, [word_part()], pos_integer(), pos_integer()} | {:assignment_word, String.t(), [word_part()], pos_integer(), pos_integer()} | {:append_word, String.t(), [word_part()], pos_integer(), pos_integer()} | {:arith_command, String.t(), pos_integer(), pos_integer()} | {atom(), pos_integer(), pos_integer()} | {atom(), non_neg_integer(), pos_integer(), pos_integer()} | {:comment, String.t(), pos_integer(), pos_integer()}
@type word_part() :: {:literal, String.t()} | {:single_quoted, String.t()} | {:double_quoted, [word_part()]} | {:variable, String.t()} | {:variable_braced, String.t(), keyword()} | {:command_subst, [token()]} | {:process_subst_in, [token()]} | {:process_subst_out, [token()]} | {:arith_expand, String.t()} | {:backtick, String.t()} | {:brace_expand, brace_spec()}
Functions
@spec tokenize(String.t()) :: {:ok, [token()]} | {:error, String.t(), pos_integer(), pos_integer()}
Tokenize a Bash script into a list of tokens.
Returns {:ok, tokens} or {:error, reason, line, column}.