1 | from __future__ import print_function
|
2 |
|
3 | from _devbuild.gen.option_asdl import option_i
|
4 | from _devbuild.gen.runtime_asdl import (scope_e, HayNode)
|
5 | from _devbuild.gen.syntax_asdl import loc
|
6 | from _devbuild.gen.value_asdl import (value, value_e, value_t)
|
7 |
|
8 | from asdl import format as fmt
|
9 | from core import alloc
|
10 | from core.error import e_usage, e_die
|
11 | from core import num
|
12 | from core import state
|
13 | from core import ui
|
14 | from core import vm
|
15 | from frontend import args
|
16 | from frontend import consts
|
17 | from frontend import location
|
18 | from frontend import typed_args
|
19 | from mycpp import mylib
|
20 | from mycpp.mylib import iteritems, NewDict, log
|
21 |
|
22 | from typing import List, Dict, Optional, Any, cast, TYPE_CHECKING
|
23 | if TYPE_CHECKING:
|
24 | from _devbuild.gen.runtime_asdl import cmd_value
|
25 | from osh.cmd_eval import CommandEvaluator
|
26 |
|
27 | _ = log
|
28 |
|
29 | _HAY_ACTION_ERROR = "builtin expects 'define', 'reset' or 'pp'"
|
30 |
|
31 |
|
32 | class ctx_HayNode(object):
|
33 | """Haynode builtin makes new names in the tree visible."""
|
34 |
|
35 | def __init__(self, hay_state, hay_name):
|
36 | # type: (HayState, Optional[str]) -> None
|
37 | #log('pairs %s', pairs)
|
38 | self.hay_state = hay_state
|
39 | self.hay_state.Push(hay_name)
|
40 |
|
41 | def __enter__(self):
|
42 | # type: () -> None
|
43 | return
|
44 |
|
45 | def __exit__(self, type, value, traceback):
|
46 | # type: (Any, Any, Any) -> None
|
47 | self.hay_state.Pop()
|
48 |
|
49 |
|
50 | class ctx_HayEval(object):
|
51 | """
|
52 | - Turn on shopt ysh:all and _running_hay
|
53 | - Disallow recursive 'hay eval'
|
54 | - Ensure result is isolated for 'hay eval :result'
|
55 |
|
56 | More leakage:
|
57 |
|
58 | External:
|
59 | - execute programs (ext_prog)
|
60 | - redirect
|
61 | - pipelines, subshell, & etc?
|
62 | - do you have to put _running_hay() checks everywhere?
|
63 |
|
64 | Internal:
|
65 |
|
66 | - state.Mem()
|
67 | - should we at least PushTemp()?
|
68 | - But then they can do setglobal
|
69 | - Option state
|
70 |
|
71 | - Disallow all builtins except echo/write/printf?
|
72 | - maybe could do that at the top level
|
73 | - source builtin, read builtin
|
74 | - cd / pushd / popd
|
75 | - trap -- hm yeah this one is bad
|
76 |
|
77 | - procs? Not strictly necessary
|
78 | - you should be able to define them, but not call the user ...
|
79 |
|
80 | """
|
81 |
|
82 | def __init__(self, hay_state, mutable_opts, mem):
|
83 | # type: (HayState, state.MutableOpts, state.Mem) -> None
|
84 | self.hay_state = hay_state
|
85 | self.mutable_opts = mutable_opts
|
86 | self.mem = mem
|
87 |
|
88 | if mutable_opts.Get(option_i._running_hay):
|
89 | # This blames the right 'hay' location
|
90 | e_die("Recursive 'hay eval' not allowed")
|
91 |
|
92 | for opt_num in consts.YSH_ALL:
|
93 | mutable_opts.Push(opt_num, True)
|
94 | mutable_opts.Push(option_i._running_hay, True)
|
95 |
|
96 | self.hay_state.PushEval()
|
97 | self.mem.PushTemp()
|
98 |
|
99 | def __enter__(self):
|
100 | # type: () -> None
|
101 | return
|
102 |
|
103 | def __exit__(self, type, value, traceback):
|
104 | # type: (Any, Any, Any) -> None
|
105 |
|
106 | self.mem.PopTemp()
|
107 | self.hay_state.PopEval()
|
108 |
|
109 | self.mutable_opts.Pop(option_i._running_hay)
|
110 | for opt_num in consts.YSH_ALL:
|
111 | self.mutable_opts.Pop(opt_num)
|
112 |
|
113 |
|
114 | class HayState(object):
|
115 | """State for DSLs."""
|
116 |
|
117 | def __init__(self):
|
118 | # type: () -> None
|
119 | ch = NewDict() # type: Dict[str, HayNode]
|
120 | self.root_defs = HayNode(ch)
|
121 | self.cur_defs = self.root_defs # Same as ClearDefs()
|
122 | self.def_stack = [self.root_defs]
|
123 |
|
124 | node = self._MakeOutputNode()
|
125 | self.result_stack = [node] # type: List[Dict[str, value_t]]
|
126 | self.output = None # type: Dict[str, value_t]
|
127 |
|
128 | def _MakeOutputNode(self):
|
129 | # type: () -> Dict[str, value_t]
|
130 | d = NewDict() # type: Dict[str, value_t]
|
131 | d['source'] = value.Null
|
132 | d['children'] = value.List([])
|
133 | return d
|
134 |
|
135 | def PushEval(self):
|
136 | # type: () -> None
|
137 |
|
138 | # remove previous results
|
139 | node = self._MakeOutputNode()
|
140 | self.result_stack = [node]
|
141 |
|
142 | self.output = None # remove last result
|
143 |
|
144 | def PopEval(self):
|
145 | # type: () -> None
|
146 |
|
147 | # Save the result
|
148 | self.output = self.result_stack[0]
|
149 |
|
150 | # Clear results
|
151 | node = self._MakeOutputNode()
|
152 | self.result_stack = [node]
|
153 |
|
154 | def AppendResult(self, d):
|
155 | # type: (Dict[str, value_t]) -> None
|
156 | """Called by haynode builtin."""
|
157 | UP_children = self.result_stack[-1]['children']
|
158 | assert UP_children.tag() == value_e.List, UP_children
|
159 | children = cast(value.List, UP_children)
|
160 | children.items.append(value.Dict(d))
|
161 |
|
162 | def Result(self):
|
163 | # type: () -> Dict[str, value_t]
|
164 | """Called by hay eval and eval_hay()"""
|
165 | return self.output
|
166 |
|
167 | def HayRegister(self):
|
168 | # type: () -> Dict[str, value_t]
|
169 | """Called by _hay() function."""
|
170 | return self.result_stack[0]
|
171 |
|
172 | def Resolve(self, first_word):
|
173 | # type: (str) -> bool
|
174 | return first_word in self.cur_defs.children
|
175 |
|
176 | def DefinePath(self, path):
|
177 | # type: (List[str]) -> None
|
178 | """Fill a tree from the given path."""
|
179 | current = self.root_defs
|
180 | for name in path:
|
181 | if name not in current.children:
|
182 | ch = NewDict() # type: Dict[str, HayNode]
|
183 | current.children[name] = HayNode(ch)
|
184 | current = current.children[name]
|
185 |
|
186 | def Reset(self):
|
187 | # type: () -> None
|
188 |
|
189 | # reset definitions
|
190 | ch = NewDict() # type: Dict[str, HayNode]
|
191 | self.root_defs = HayNode(ch)
|
192 | self.cur_defs = self.root_defs
|
193 |
|
194 | # reset output
|
195 | self.PopEval()
|
196 |
|
197 | def Push(self, hay_name):
|
198 | # type: (Optional[str]) -> None
|
199 | """
|
200 | Package cppunit {
|
201 | } # pushes a namespace
|
202 |
|
203 | haynode package cppunit {
|
204 | } # just assumes every TYPE 'package' is valid.
|
205 | """
|
206 | top = self.result_stack[-1]
|
207 | # TODO: Store this more efficiently? See osh/builtin_pure.py
|
208 | children = cast(value.List, top['children'])
|
209 | last_child = cast(value.Dict, children.items[-1])
|
210 | self.result_stack.append(last_child.d)
|
211 |
|
212 | #log('> PUSH')
|
213 | if hay_name is None:
|
214 | self.def_stack.append(self.cur_defs) # no-op
|
215 | else:
|
216 | # Caller should ensure this
|
217 | assert hay_name in self.cur_defs.children, hay_name
|
218 |
|
219 | self.cur_defs = self.cur_defs.children[hay_name]
|
220 | self.def_stack.append(self.cur_defs)
|
221 |
|
222 | def Pop(self):
|
223 | # type: () -> None
|
224 | self.def_stack.pop()
|
225 | self.cur_defs = self.def_stack[-1]
|
226 |
|
227 | self.result_stack.pop()
|
228 |
|
229 |
|
230 | class Hay(vm._Builtin):
|
231 | """hay builtin
|
232 |
|
233 | hay define -- package user
|
234 | hay define -- user/foo user/bar # second level
|
235 | hay pp
|
236 | hay reset
|
237 | """
|
238 |
|
239 | def __init__(self, hay_state, mutable_opts, mem, cmd_ev):
|
240 | # type: (HayState, state.MutableOpts, state.Mem, CommandEvaluator) -> None
|
241 | self.hay_state = hay_state
|
242 | self.mutable_opts = mutable_opts
|
243 | self.mem = mem
|
244 | self.cmd_ev = cmd_ev # To run blocks
|
245 |
|
246 | def Run(self, cmd_val):
|
247 | # type: (cmd_value.Argv) -> int
|
248 | arg_r = args.Reader(cmd_val.argv, locs=cmd_val.arg_locs)
|
249 | arg_r.Next() # skip 'hay'
|
250 |
|
251 | action, action_loc = arg_r.Peek2()
|
252 | if action is None:
|
253 | e_usage(_HAY_ACTION_ERROR, action_loc)
|
254 | arg_r.Next()
|
255 |
|
256 | if action == 'define':
|
257 | # TODO: accept --
|
258 | #arg, arg_r = flag_spec.ParseCmdVal('hay-define', cmd_val)
|
259 |
|
260 | # arg = args.Parse(JSON_WRITE_SPEC, arg_r)
|
261 | first, _ = arg_r.Peek2()
|
262 | if first is None:
|
263 | e_usage('define expected a name', action_loc)
|
264 |
|
265 | names, name_locs = arg_r.Rest2()
|
266 | for i, name in enumerate(names):
|
267 | path = name.split('/')
|
268 | for p in path:
|
269 | if len(p) == 0:
|
270 | e_usage(
|
271 | "got invalid path %r. Parts can't be empty." %
|
272 | name, name_locs[i])
|
273 | self.hay_state.DefinePath(path)
|
274 |
|
275 | elif action == 'eval':
|
276 | # hay eval :myvar { ... }
|
277 | #
|
278 | # - turn on ysh:all
|
279 | # - set _running_hay -- so that hay "first words" are visible
|
280 | # - then set the variable name to the result
|
281 |
|
282 | var_name, _ = arg_r.ReadRequired2("expected variable name")
|
283 | if var_name.startswith(':'):
|
284 | var_name = var_name[1:]
|
285 | # TODO: This could be fatal?
|
286 |
|
287 | cmd = typed_args.OptionalBlock(cmd_val)
|
288 | if not cmd: # 'package foo' is OK
|
289 | e_usage('eval expected a block', loc.Missing)
|
290 |
|
291 | with ctx_HayEval(self.hay_state, self.mutable_opts, self.mem):
|
292 | # Note: we want all haynode invocations in the block to appear as
|
293 | # our 'children', recursively
|
294 | unused = self.cmd_ev.EvalCommand(cmd)
|
295 |
|
296 | result = self.hay_state.Result()
|
297 |
|
298 | val = value.Dict(result)
|
299 | self.mem.SetNamed(location.LName(var_name), val, scope_e.LocalOnly)
|
300 |
|
301 | elif action == 'reset':
|
302 | self.hay_state.Reset()
|
303 |
|
304 | elif action == 'pp':
|
305 | tree = self.hay_state.root_defs.PrettyTree()
|
306 | ast_f = fmt.DetectConsoleOutput(mylib.Stdout())
|
307 | fmt.PrintTree(tree, ast_f)
|
308 | ast_f.write('\n')
|
309 |
|
310 | else:
|
311 | e_usage(_HAY_ACTION_ERROR, action_loc)
|
312 |
|
313 | return 0
|
314 |
|
315 |
|
316 | class HayNode_(vm._Builtin):
|
317 | """The FIXED builtin that is run after 'hay define'.
|
318 |
|
319 | It evaluates a SUBTREE
|
320 |
|
321 | Example:
|
322 |
|
323 | package cppunit {
|
324 | version = '1.0'
|
325 | user bob
|
326 | }
|
327 |
|
328 | is short for
|
329 |
|
330 | haynode package cppunit {
|
331 | version = '1.0'
|
332 | haynode user bob
|
333 | }
|
334 | """
|
335 |
|
336 | def __init__(self, hay_state, mem, cmd_ev):
|
337 | # type: (HayState, state.Mem, CommandEvaluator) -> None
|
338 | self.hay_state = hay_state
|
339 | self.mem = mem # isolation with mem.PushTemp
|
340 | self.cmd_ev = cmd_ev # To run blocks
|
341 | self.arena = cmd_ev.arena # To extract code strings
|
342 |
|
343 | def Run(self, cmd_val):
|
344 | # type: (cmd_value.Argv) -> int
|
345 |
|
346 | arg_r = args.Reader(cmd_val.argv, locs=cmd_val.arg_locs)
|
347 |
|
348 | hay_name, arg0_loc = arg_r.Peek2()
|
349 | if hay_name == 'haynode': # haynode package glib { ... }
|
350 | arg_r.Next()
|
351 | hay_name = None # don't validate
|
352 |
|
353 | # Should we call hay_state.AddChild() so it can be mutated?
|
354 | result = NewDict() # type: Dict[str, value_t]
|
355 |
|
356 | node_type, _ = arg_r.Peek2()
|
357 | result['type'] = value.Str(node_type)
|
358 |
|
359 | arg_r.Next()
|
360 | arguments = arg_r.Rest()
|
361 |
|
362 | lit_block = typed_args.OptionalLiteralBlock(cmd_val)
|
363 |
|
364 | # package { ... } is not valid
|
365 | if len(arguments) == 0 and lit_block is None:
|
366 | e_usage('expected at least 1 arg, or a literal block { }',
|
367 | arg0_loc)
|
368 |
|
369 | items = [value.Str(s) for s in arguments] # type: List[value_t]
|
370 | result['args'] = value.List(items)
|
371 |
|
372 | if node_type.isupper(): # TASK build { ... }
|
373 | if lit_block is None:
|
374 | e_usage('command node requires a literal block argument',
|
375 | loc.Missing)
|
376 |
|
377 | if 0: # self.hay_state.to_expr ?
|
378 | result['expr'] = lit_block # UNEVALUATED block
|
379 | else:
|
380 | # We can only extract code if the block arg is literal like package
|
381 | # foo { ... }, not if it's like package foo (myblock)
|
382 |
|
383 | brace_group = lit_block.brace_group
|
384 | # BraceGroup has location for {
|
385 | line = brace_group.left.line
|
386 |
|
387 | # for the user to pass back to --location-str
|
388 | result['location_str'] = value.Str(
|
389 | ui.GetLineSourceString(line))
|
390 | result['location_start_line'] = num.ToBig(line.line_num)
|
391 |
|
392 | # Between { and }
|
393 | code_str = alloc.SnipCodeBlock(brace_group.left,
|
394 | brace_group.right,
|
395 | lit_block.lines)
|
396 |
|
397 | result['code_str'] = value.Str(code_str)
|
398 |
|
399 | # Append after validation
|
400 | self.hay_state.AppendResult(result)
|
401 |
|
402 | else:
|
403 | # Must be done before EvalCommand
|
404 | self.hay_state.AppendResult(result)
|
405 |
|
406 | if lit_block: # 'package foo' is OK
|
407 | result['children'] = value.List([])
|
408 |
|
409 | # Evaluate in its own stack frame. TODO: Turn on dynamic scope?
|
410 | with state.ctx_Temp(self.mem):
|
411 | with ctx_HayNode(self.hay_state, hay_name):
|
412 | # Note: we want all haynode invocations in the block to appear as
|
413 | # our 'children', recursively
|
414 | self.cmd_ev.EvalCommand(lit_block.brace_group)
|
415 |
|
416 | # Treat the vars as a Dict
|
417 | block_attrs = self.mem.TopNamespace()
|
418 |
|
419 | attrs = NewDict() # type: Dict[str, value_t]
|
420 | for name, cell in iteritems(block_attrs):
|
421 |
|
422 | # User can hide variables with _ suffix
|
423 | # e.g. for i_ in foo bar { echo $i_ }
|
424 | if name.endswith('_'):
|
425 | continue
|
426 |
|
427 | attrs[name] = cell.val
|
428 |
|
429 | result['attrs'] = value.Dict(attrs)
|
430 |
|
431 | return 0
|