| 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
 |