1
|
module CodeRay module Scanners
|
2
|
|
3
|
class BASH < Scanner
|
4
|
|
5
|
register_for :bash
|
6
|
|
7
|
RESERVED_WORDS = %w{
|
8
|
if elif fi until while done for do case in esac select
|
9
|
break else then shift function
|
10
|
}
|
11
|
|
12
|
PREDEFINED_CONSTANTS = %w{
|
13
|
$CDPATH $HOME $IFS $MAIL $MAILPATH $OPTARG $LINENO $LINES
|
14
|
$OPTIND $PATH $PS1 $PS2 $BASH $BASH_ARGCBASH_ARGV
|
15
|
$BASH_COMMAND $BASH_ENV $BASH_EXECUTION_STRING
|
16
|
$BASH_LINENO $BASH_REMATCH $BASH_SOURCE $COLUMNS
|
17
|
$BASH_SUBSHELL $BASH_VERSINFO $BASH_VERSION $OSTYPE
|
18
|
$COMP_CWORD $COMP_LINE $COMP_POINT $COMP_WORDBREAKS
|
19
|
$COMP_WORDS $COMPREPLY $DIRSTACK $EMACS $EUID $OTPERR
|
20
|
$FCEDIT $FIGNORE $FUNCNAME $GLOBIGNORE $GROUPS $OLDPWD
|
21
|
$histchars $HISTCMD $HISTCONTROL $HISTFILE $MACHTYPE
|
22
|
$HISTFILESIZE $HISTIGNORE $HISTSIZE $HISTTIMEFOMAT
|
23
|
$HOSTFILE $HOSTNAME $HOSTTYPE $IGNOREEOF $INPUTRC $LANG
|
24
|
$LC_ALL $LC_COLLATE $LC_CTYPE $LC_MESSAGES $LC_NUMERIC
|
25
|
$PIPESTATUS $POSIXLY_CORRECT $MAILCHECK $PPID $PS3 $PS4
|
26
|
$PROMPT_COMMAND $PWD $RANDOM $REPLY $SECONDS $SHELL
|
27
|
$SHELLOPTS $SHLVL $TIMEFORMAT $TMOUT $TMPDIR $UID
|
28
|
}
|
29
|
|
30
|
BUILTIN = %w{
|
31
|
cd continue eval exec true false suspend unalias
|
32
|
exit export getopts hash pwd readonly return test
|
33
|
times trap umask unset alias bind builtin caller
|
34
|
command declare echo enable help let local logout
|
35
|
printf read shopt source type typeset ulimit
|
36
|
set dirs popd pushd bg fg jobs kill wait disown
|
37
|
}
|
38
|
|
39
|
IDENT_KIND = WordList.new(:ident).
|
40
|
add(RESERVED_WORDS, :reserved).
|
41
|
# add(PREDEFINED_CONSTANTS, :pre_constant).
|
42
|
add(BUILTIN, :method)
|
43
|
|
44
|
ESCAPE = / [\$rtnb\n\\'"] /x
|
45
|
# UNICODE_ESCAPE = / u[a-fA-F0-9]{4} | U[a-fA-F0-9]{8} /x
|
46
|
|
47
|
VARIABLE_SIMPLE = /\$[a-zA-Z]\w*/
|
48
|
VARIABLE_EXPRESSION = /\$\{[!#]?[a-zA-Z].*?\}/
|
49
|
|
50
|
def scan_tokens tokens, options
|
51
|
|
52
|
state = :initial
|
53
|
string_type = nil
|
54
|
|
55
|
until eos?
|
56
|
|
57
|
kind = nil
|
58
|
match = nil
|
59
|
|
60
|
if state == :initial
|
61
|
if scan(/ \s+ | \\\n /x)
|
62
|
kind = :space
|
63
|
elsif match = scan(/\#!.*/) # until eof
|
64
|
kind = :preprocessor
|
65
|
elsif scan(/\#.*/)
|
66
|
kind = :comment
|
67
|
elsif scan(/ [-+*\/=<>?:;,!&^|()\[\]{}~%] | \.(?!\d) /x)
|
68
|
kind = :operator
|
69
|
elsif match = scan(/[1-9][0-9]*/)
|
70
|
kind = :number
|
71
|
elsif scan(/ \\ (?: \S ) /mox)
|
72
|
kind = :char
|
73
|
elsif scan(/(#{VARIABLE_SIMPLE}|#{VARIABLE_EXPRESSION})/)
|
74
|
kind = :instance_variable
|
75
|
elsif match = scan(/ [$@A-Za-z_][A-Za-z_0-9]* /x)
|
76
|
kind = IDENT_KIND[match]
|
77
|
elsif match = scan(/["']/)
|
78
|
tokens << [:open, :string]
|
79
|
string_type = matched
|
80
|
state = :string
|
81
|
kind = :delimiter
|
82
|
else
|
83
|
getch
|
84
|
end
|
85
|
elsif state == :regex
|
86
|
if scan(/[^\\\/]+/)
|
87
|
kind = :content
|
88
|
elsif scan(/\\\/|\\/)
|
89
|
kind = :content
|
90
|
elsif scan(/\//)
|
91
|
tokens << [matched, :delimiter]
|
92
|
tokens << [:close, :regexp]
|
93
|
state = :initial
|
94
|
next
|
95
|
else
|
96
|
getch
|
97
|
kind = :content
|
98
|
end
|
99
|
|
100
|
elsif state == :string
|
101
|
if scan(/[^\\"']+/)
|
102
|
kind = :content
|
103
|
elsif scan(/["']/)
|
104
|
if string_type==matched
|
105
|
tokens << [matched, :delimiter]
|
106
|
tokens << [:close, :string]
|
107
|
state = :initial
|
108
|
string_type=nil
|
109
|
next
|
110
|
else
|
111
|
kind = :content
|
112
|
end
|
113
|
elsif scan(/ \\ (?: \S ) /mox)
|
114
|
kind = :char
|
115
|
elsif scan(/ \\ | $ /x)
|
116
|
# kind = :error
|
117
|
kind = :content
|
118
|
state = :initial
|
119
|
else
|
120
|
raise "else case \" reached; %p not handled." % peek(1), tokens
|
121
|
end
|
122
|
else
|
123
|
raise 'else-case reached', tokens
|
124
|
end
|
125
|
match ||= matched
|
126
|
tokens << [match, kind]
|
127
|
end
|
128
|
tokens
|
129
|
end
|
130
|
end
|
131
|
end end
|