1 #!/usr/bin/env python2
2 """Consts.py."""
3 from __future__ import print_function
4
5 from _devbuild.gen.types_asdl import (redir_arg_type_e, redir_arg_type_t,
6 bool_arg_type_t, opt_group_i)
7 from _devbuild.gen.id_kind_asdl import Id, Id_t, Kind_t
8 from frontend import builtin_def
9 from frontend import lexer_def
10 from frontend import option_def
11
12 from typing import Tuple, Optional, TYPE_CHECKING
13 if TYPE_CHECKING:
14 from _devbuild.gen.option_asdl import option_t, builtin_t
15
16 NO_INDEX = 0 # for Resolve
17
18 # Used as consts::STRICT_ALL, etc. Do it explicitly to satisfy MyPy.
19 STRICT_ALL = option_def.STRICT_ALL
20 YSH_UPGRADE = option_def.YSH_UPGRADE
21 YSH_ALL = option_def.YSH_ALL
22 DEFAULT_TRUE = option_def.DEFAULT_TRUE
23
24 PARSE_OPTION_NUMS = option_def.PARSE_OPTION_NUMS
25
26 SET_OPTION_NUMS = [
27 opt.index for opt in option_def._SORTED if opt.builtin == 'set'
28 ]
29 SET_OPTION_NAMES = [
30 opt.name for opt in option_def._SORTED if opt.builtin == 'set'
31 ]
32
33 SHOPT_OPTION_NUMS = [
34 opt.index for opt in option_def._SORTED if opt.builtin == 'shopt'
35 ]
36 SHOPT_OPTION_NAMES = [
37 opt.name for opt in option_def._SORTED if opt.builtin == 'shopt'
38 ]
39
40 VISIBLE_SHOPT_NUMS = option_def.VISIBLE_SHOPT_NUMS # used to print
41
42 BUILTIN_NAMES = builtin_def.BUILTIN_NAMES # Used by builtin_comp.py
43
44 # The 'compen' and 'type' builtins introspect on keywords and builtins.
45 OSH_KEYWORD_NAMES = [name for _, name, _ in lexer_def._KEYWORDS]
46 OSH_KEYWORD_NAMES.append('{') # not in our lexer list
47
48
49 def GetKind(id_):
50 # type: (Id_t) -> Kind_t
51 """To make coarse-grained parsing decisions."""
52
53 from _devbuild.gen.id_kind import ID_TO_KIND # break circular dep
54 return ID_TO_KIND[id_]
55
56
57 def BoolArgType(id_):
58 # type: (Id_t) -> bool_arg_type_t
59
60 from _devbuild.gen.id_kind import BOOL_ARG_TYPES # break circular dep
61 return BOOL_ARG_TYPES[id_]
62
63
64 #
65 # Redirect Tables associated with IDs
66 #
67
68 REDIR_DEFAULT_FD = {
69 # filename
70 Id.Redir_Less: 0, # cat <input.txt means cat 0<input.txt
71 Id.Redir_Great: 1,
72 Id.Redir_DGreat: 1,
73 Id.Redir_Clobber: 1,
74 Id.Redir_LessGreat: 0, # 'exec <> foo' opens a file with read/write
75 # bash &> and &>>
76 Id.Redir_AndGreat: 1,
77 Id.Redir_AndDGreat: 1,
78
79 # descriptor
80 Id.Redir_GreatAnd: 1, # echo >&2 means echo 1>&2
81 Id.Redir_LessAnd: 0, # echo <&3 means echo 0<&3, I think
82 Id.Redir_TLess: 0, # here word
83
84 # here docs included
85 Id.Redir_DLess: 0,
86 Id.Redir_DLessDash: 0,
87 }
88
89 REDIR_ARG_TYPES = {
90 # filename
91 Id.Redir_Less: redir_arg_type_e.Path,
92 Id.Redir_Great: redir_arg_type_e.Path,
93 Id.Redir_DGreat: redir_arg_type_e.Path,
94 Id.Redir_Clobber: redir_arg_type_e.Path,
95 Id.Redir_LessGreat: redir_arg_type_e.Path,
96 # bash &> and &>>
97 Id.Redir_AndGreat: redir_arg_type_e.Path,
98 Id.Redir_AndDGreat: redir_arg_type_e.Path,
99
100 # descriptor
101 Id.Redir_GreatAnd: redir_arg_type_e.Desc,
102 Id.Redir_LessAnd: redir_arg_type_e.Desc,
103 Id.Redir_TLess: redir_arg_type_e.Here, # here word
104 # note: here docs aren't included
105 }
106
107
108 def RedirArgType(id_):
109 # type: (Id_t) -> redir_arg_type_t
110 return REDIR_ARG_TYPES[id_]
111
112
113 def RedirDefaultFd(id_):
114 # type: (Id_t) -> int
115 return REDIR_DEFAULT_FD[id_]
116
117
118 #
119 # Builtins
120 #
121
122 _BUILTIN_DICT = builtin_def.BuiltinDict()
123
124
125 def LookupSpecialBuiltin(argv0):
126 # type: (str) -> builtin_t
127 """Is it a special builtin?"""
128 b = _BUILTIN_DICT.get(argv0)
129 if b and b.kind == 'special':
130 return b.index
131 else:
132 return NO_INDEX
133
134
135 def LookupAssignBuiltin(argv0):
136 # type: (str) -> builtin_t
137 """Is it an assignment builtin?"""
138 b = _BUILTIN_DICT.get(argv0)
139 if b and b.kind == 'assign':
140 return b.index
141 else:
142 return NO_INDEX
143
144
145 def LookupNormalBuiltin(argv0):
146 # type: (str) -> builtin_t
147 """Is it any other builtin?"""
148 b = _BUILTIN_DICT.get(argv0)
149 if b and b.kind == 'normal':
150 return b.index
151 else:
152 return NO_INDEX
153
154
155 def OptionName(opt_num):
156 # type: (option_t) -> str
157 """Get the name from an index."""
158 return option_def.OPTION_NAMES[opt_num]
159
160
161 OPTION_GROUPS = {
162 'strict:all': opt_group_i.StrictAll,
163
164 # Aliases to deprecate
165 'oil:upgrade': opt_group_i.YshUpgrade,
166 'oil:all': opt_group_i.YshAll,
167 'ysh:upgrade': opt_group_i.YshUpgrade,
168 'ysh:all': opt_group_i.YshAll,
169 }
170
171
172 def OptionGroupNum(s):
173 # type: (str) -> int
174 return OPTION_GROUPS.get(s, NO_INDEX) # 0 for not found
175
176
177 _OPTION_DICT = option_def.OptionDict()
178
179
180 def OptionNum(s):
181 # type: (str) -> int
182 return _OPTION_DICT.get(s, 0) # 0 means not found
183
184
185 #
186 # osh/builtin_meta.py
187 #
188
189
190 def IsControlFlow(name):
191 # type: (str) -> bool
192 return name in lexer_def.CONTROL_FLOW_NAMES
193
194
195 def IsKeyword(name):
196 # type: (str) -> bool
197 return name in OSH_KEYWORD_NAMES
198
199
200 #
201 # osh/prompt.py and osh/word_compile.py
202 #
203
204 _ONE_CHAR_C = {
205 '0': '\0',
206 'a': '\a',
207 'b': '\b',
208 'e': '\x1b',
209 'E': '\x1b',
210 'f': '\f',
211 'n': '\n',
212 'r': '\r',
213 't': '\t',
214 'v': '\v',
215 '\\': '\\',
216 "'": "'", # for $'' only, not echo -e
217 '"': '"', # not sure why this is escaped within $''
218 }
219
220
221 def LookupCharC(c):
222 # type: (str) -> str
223 """Fatal if not present."""
224 return _ONE_CHAR_C[c]
225
226
227 # NOTE: Prompts chars and printf are inconsistent, e.g. \E is \e in printf, but
228 # not in PS1.
229 _ONE_CHAR_PROMPT = {
230 'a': '\a',
231 'e': '\x1b',
232 'r': '\r',
233 'n': '\n',
234 '\\': '\\',
235 }
236
237
238 def LookupCharPrompt(c):
239 # type: (str) -> Optional[str]
240 """Returns None if not present."""
241 return _ONE_CHAR_PROMPT.get(c)
242
243
244 #
245 # Constants used by osh/split.py
246 #
247
248 # IFS splitting is complicated in general. We handle it with three concepts:
249 #
250 # - CH.* - Kinds of characters (edge labels)
251 # - ST.* - States (node labels)
252 # - EMIT.* Actions
253 #
254 # The Split() loop below classifies characters, follows state transitions, and
255 # emits spans. A span is a (ignored Bool, end_index Int) pair.
256
257 # As an example, consider this string:
258 # 'a _ b'
259 #
260 # The character classes are:
261 #
262 # a ' ' _ ' ' b
263 # Black DE_White DE_Gray DE_White Black
264 #
265 # The states are:
266 #
267 # a ' ' _ ' ' b
268 # Black DE_White1 DE_Gray DE_White2 Black
269 #
270 # DE_White2 is whitespace that follows a "gray" non-whitespace IFS character.
271 #
272 # The spans emitted are:
273 #
274 # (part 'a', ignored ' _ ', part 'b')
275
276 # SplitForRead() will check if the last two spans are a \ and \\n. Easy.
277
278 # Shorter names for state machine enums
279 from _devbuild.gen.runtime_asdl import state_t, emit_t, char_kind_t
280 from _devbuild.gen.runtime_asdl import emit_i as EMIT
281 from _devbuild.gen.runtime_asdl import char_kind_i as CH
282 from _devbuild.gen.runtime_asdl import state_i as ST
283
284 _IFS_EDGES = {
285 # Whitespace should have been stripped
286 (ST.Start, CH.DE_White): (ST.Invalid, EMIT.Nothing), # ' '
287 (ST.Start, CH.DE_Gray): (ST.DE_Gray, EMIT.Empty), # '_'
288 (ST.Start, CH.Black): (ST.Black, EMIT.Nothing), # 'a'
289 (ST.Start, CH.Backslash): (ST.Backslash, EMIT.Nothing), # '\'
290 (ST.Start, CH.Sentinel): (ST.Done, EMIT.Nothing), # ''
291 (ST.DE_White1, CH.DE_White): (ST.DE_White1, EMIT.Nothing), # ' '
292 (ST.DE_White1, CH.DE_Gray): (ST.DE_Gray, EMIT.Nothing), # ' _'
293 (ST.DE_White1, CH.Black): (ST.Black, EMIT.Delim), # ' a'
294 (ST.DE_White1, CH.Backslash): (ST.Backslash, EMIT.Delim), # ' \'
295 # Ignore trailing IFS whitespace too. This is necessary for the case:
296 # IFS=':' ; read x y z <<< 'a : b : c :'.
297 (ST.DE_White1, CH.Sentinel): (ST.Done, EMIT.Nothing), # 'zz '
298 (ST.DE_Gray, CH.DE_White): (ST.DE_White2, EMIT.Nothing), # '_ '
299 (ST.DE_Gray, CH.DE_Gray): (ST.DE_Gray, EMIT.Empty), # '__'
300 (ST.DE_Gray, CH.Black): (ST.Black, EMIT.Delim), # '_a'
301 (ST.DE_Gray, CH.Backslash): (ST.Black, EMIT.Delim), # '_\'
302 (ST.DE_Gray, CH.Sentinel): (ST.Done, EMIT.Delim), # 'zz:' IFS=': '
303 (ST.DE_White2, CH.DE_White): (ST.DE_White2, EMIT.Nothing), # '_ '
304 (ST.DE_White2, CH.DE_Gray): (ST.DE_Gray, EMIT.Empty), # '_ _'
305 (ST.DE_White2, CH.Black): (ST.Black, EMIT.Delim), # '_ a'
306 (ST.DE_White2, CH.Backslash): (ST.Backslash, EMIT.Delim), # '_ \'
307 (ST.DE_White2, CH.Sentinel): (ST.Done, EMIT.Delim), # 'zz: ' IFS=': '
308 (ST.Black, CH.DE_White): (ST.DE_White1, EMIT.Part), # 'a '
309 (ST.Black, CH.DE_Gray): (ST.DE_Gray, EMIT.Part), # 'a_'
310 (ST.Black, CH.Black): (ST.Black, EMIT.Nothing), # 'aa'
311 (ST.Black, CH.Backslash): (ST.Backslash, EMIT.Part), # 'a\'
312 (ST.Black, CH.Sentinel): (ST.Done, EMIT.Part), # 'zz' IFS=': '
313
314 # Here we emit an ignored \ and the second character as well.
315 # We're emitting TWO spans here; we don't wait until the subsequent
316 # character. That is OK.
317 #
318 # Problem: if '\ ' is the last one, we don't want to emit a trailing span?
319 # In all other cases we do.
320 (ST.Backslash, CH.DE_White): (ST.Black, EMIT.Escape), # '\ '
321 (ST.Backslash, CH.DE_Gray): (ST.Black, EMIT.Escape), # '\_'
322 (ST.Backslash, CH.Black): (ST.Black, EMIT.Escape), # '\a'
323 # NOTE: second character is a backslash, but new state is ST.Black!
324 (ST.Backslash, CH.Backslash): (ST.Black, EMIT.Escape), # '\\'
325 (ST.Backslash, CH.Sentinel): (ST.Done, EMIT.Escape), # 'zz\'
326 }
327
328
329 def IfsEdge(state, ch):
330 # type: (state_t, char_kind_t) -> Tuple[state_t, emit_t]
331 """Follow edges of the IFS state machine."""
332 return _IFS_EDGES[state, ch]