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

429 lines, 213 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.RequiredBlockAsFrag(cmd_val)
288
289 with ctx_HayEval(self.hay_state, self.mutable_opts, self.mem):
290 # Note: we want all haynode invocations in the block to appear as
291 # our 'children', recursively
292 unused = self.cmd_ev.EvalCommandFrag(cmd)
293
294 result = self.hay_state.Result()
295
296 val = value.Dict(result)
297 self.mem.SetNamed(location.LName(var_name), val, scope_e.LocalOnly)
298
299 elif action == 'reset':
300 self.hay_state.Reset()
301
302 elif action == 'pp':
303 tree = self.hay_state.root_defs.PrettyTree()
304 ast_f = fmt.DetectConsoleOutput(mylib.Stdout())
305 fmt.PrintTree(tree, ast_f)
306 ast_f.write('\n')
307
308 else:
309 e_usage(_HAY_ACTION_ERROR, action_loc)
310
311 return 0
312
313
314class HayNode_(vm._Builtin):
315 """The FIXED builtin that is run after 'hay define'.
316
317 It evaluates a SUBTREE
318
319 Example:
320
321 package cppunit {
322 version = '1.0'
323 user bob
324 }
325
326 is short for
327
328 haynode package cppunit {
329 version = '1.0'
330 haynode user bob
331 }
332 """
333
334 def __init__(self, hay_state, mem, cmd_ev):
335 # type: (HayState, state.Mem, CommandEvaluator) -> None
336 self.hay_state = hay_state
337 self.mem = mem # isolation with mem.PushTemp
338 self.cmd_ev = cmd_ev # To run blocks
339 self.arena = cmd_ev.arena # To extract code strings
340
341 def Run(self, cmd_val):
342 # type: (cmd_value.Argv) -> int
343
344 arg_r = args.Reader(cmd_val.argv, locs=cmd_val.arg_locs)
345
346 hay_name, arg0_loc = arg_r.Peek2()
347 if hay_name == 'haynode': # haynode package glib { ... }
348 arg_r.Next()
349 hay_name = None # don't validate
350
351 # Should we call hay_state.AddChild() so it can be mutated?
352 result = NewDict() # type: Dict[str, value_t]
353
354 node_type, _ = arg_r.Peek2()
355 result['type'] = value.Str(node_type)
356
357 arg_r.Next()
358 arguments = arg_r.Rest()
359
360 lit_block = typed_args.OptionalLiteralBlock(cmd_val)
361
362 # package { ... } is not valid
363 if len(arguments) == 0 and lit_block is None:
364 e_usage('expected at least 1 arg, or a literal block { }',
365 arg0_loc)
366
367 items = [value.Str(s) for s in arguments] # type: List[value_t]
368 result['args'] = value.List(items)
369
370 if node_type.isupper(): # TASK build { ... }
371 if lit_block is None:
372 e_usage('command node requires a literal block argument',
373 loc.Missing)
374
375 if 0: # self.hay_state.to_expr ?
376 result['expr'] = lit_block # UNEVALUATED block
377 else:
378 # We can only extract code if the block arg is literal like package
379 # foo { ... }, not if it's like package foo (myblock)
380
381 brace_group = lit_block.brace_group
382 # BraceGroup has location for {
383 line = brace_group.left.line
384
385 # for the user to pass back to --location-str
386 result['location_str'] = value.Str(
387 ui.GetLineSourceString(line))
388 result['location_start_line'] = num.ToBig(line.line_num)
389
390 # Between { and }
391 code_str = alloc.SnipCodeBlock(brace_group.left,
392 brace_group.right,
393 lit_block.lines)
394
395 result['code_str'] = value.Str(code_str)
396
397 # Append after validation
398 self.hay_state.AppendResult(result)
399
400 else:
401 # Must be done before EvalCommand
402 self.hay_state.AppendResult(result)
403
404 if lit_block: # 'package foo' is OK
405 result['children'] = value.List([])
406
407 # Evaluate in its own stack frame. TODO: Turn on dynamic scope?
408 with state.ctx_Temp(self.mem):
409 with ctx_HayNode(self.hay_state, hay_name):
410 # Note: we want all haynode invocations in the block to appear as
411 # our 'children', recursively
412 self.cmd_ev.EvalCommandFrag(lit_block.brace_group)
413
414 # Treat the vars as a Dict
415 block_attrs = self.mem.TopNamespace()
416
417 attrs = NewDict() # type: Dict[str, value_t]
418 for name, cell in iteritems(block_attrs):
419
420 # User can hide variables with _ suffix
421 # e.g. for i_ in foo bar { echo $i_ }
422 if name.endswith('_'):
423 continue
424
425 attrs[name] = cell.val
426
427 result['attrs'] = value.Dict(attrs)
428
429 return 0