OILS / builtin / hay_ysh.py View on Github | oilshell.org

431 lines, 215 significant
1from __future__ import print_function
2
3from _devbuild.gen.option_asdl import option_i
4from _devbuild.gen.runtime_asdl import (scope_e, HayNode)
5from _devbuild.gen.syntax_asdl import loc
6from _devbuild.gen.value_asdl import (value, value_e, value_t)
7
8from asdl import format as fmt
9from core import alloc
10from core.error import e_usage, e_die
11from core import num
12from core import state
13from display import ui
14from core import vm
15from frontend import args
16from frontend import consts
17from frontend import location
18from frontend import typed_args
19from mycpp import mylib
20from mycpp.mylib import iteritems, NewDict, log
21
22from typing import List, Dict, Optional, Any, cast, TYPE_CHECKING
23if 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
32class 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
50class 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
114class 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
230class 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
316class 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