| 1 | #!/usr/bin/env python2
|
| 2 | """
|
| 3 | func_reflect.py - Functions for reflecting on Oils code - OSH or YSH.
|
| 4 | """
|
| 5 | from __future__ import print_function
|
| 6 |
|
| 7 | from _devbuild.gen.runtime_asdl import scope_e
|
| 8 | from _devbuild.gen.syntax_asdl import (Token, CompoundWord, source,
|
| 9 | debug_frame, debug_frame_e)
|
| 10 | from _devbuild.gen.value_asdl import (value, value_e, value_t, cmd_frag)
|
| 11 |
|
| 12 | from core import alloc
|
| 13 | from core import error
|
| 14 | from core import main_loop
|
| 15 | from core import state
|
| 16 | from core import vm
|
| 17 | from data_lang import j8
|
| 18 | from display import ui
|
| 19 | from frontend import location
|
| 20 | from frontend import reader
|
| 21 | from frontend import typed_args
|
| 22 | from mycpp import mops
|
| 23 | from mycpp import mylib
|
| 24 | from mycpp.mylib import log, tagswitch
|
| 25 |
|
| 26 | from typing import List, cast, TYPE_CHECKING
|
| 27 | if TYPE_CHECKING:
|
| 28 | from frontend import parse_lib
|
| 29 |
|
| 30 | _ = log
|
| 31 |
|
| 32 |
|
| 33 | class Id(vm._Callable):
|
| 34 | """Return an integer object ID, like Python's id().
|
| 35 |
|
| 36 | Long shot: pointer tagging, boxless value_t, and small string optimization
|
| 37 | could mean that value.Str is no longer heap-allocated, and thus doesn't
|
| 38 | have a GC ID?
|
| 39 |
|
| 40 | What about value.{Bool,Int,Float}?
|
| 41 |
|
| 42 | I guess only mutable objects can have IDs then
|
| 43 | """
|
| 44 |
|
| 45 | def __init__(self):
|
| 46 | # type: () -> None
|
| 47 | vm._Callable.__init__(self)
|
| 48 |
|
| 49 | def Call(self, rd):
|
| 50 | # type: (typed_args.Reader) -> value_t
|
| 51 | unused_vm = rd.PosValue() # vm.id()
|
| 52 | val = rd.PosValue()
|
| 53 | rd.Done()
|
| 54 |
|
| 55 | # Select mutable values for now
|
| 56 | with tagswitch(val) as case:
|
| 57 | if case(value_e.List, value_e.Dict, value_e.Obj):
|
| 58 | id_ = j8.HeapValueId(val)
|
| 59 | return value.Int(mops.IntWiden(id_))
|
| 60 | else:
|
| 61 | raise error.TypeErr(val, 'id() expected List, Dict, or Obj',
|
| 62 | rd.BlamePos())
|
| 63 | raise AssertionError()
|
| 64 |
|
| 65 |
|
| 66 | class GetFrame(vm._Callable):
|
| 67 |
|
| 68 | def __init__(self, mem):
|
| 69 | # type: (state.Mem) -> None
|
| 70 | vm._Callable.__init__(self)
|
| 71 | self.mem = mem
|
| 72 |
|
| 73 | def Call(self, rd):
|
| 74 | # type: (typed_args.Reader) -> value_t
|
| 75 | unused_self = rd.PosObj()
|
| 76 | index = mops.BigTruncate(rd.PosInt())
|
| 77 | rd.Done()
|
| 78 |
|
| 79 | length = len(self.mem.var_stack)
|
| 80 | if index < 0:
|
| 81 | i = index + length
|
| 82 | else:
|
| 83 | i = index
|
| 84 |
|
| 85 | if 0 <= i and i < length:
|
| 86 | return value.Frame(self.mem.var_stack[i])
|
| 87 | else:
|
| 88 | raise error.Structured(3, "Invalid frame %d" % index,
|
| 89 | rd.LeftParenToken())
|
| 90 |
|
| 91 |
|
| 92 | class BindFrame(vm._Callable):
|
| 93 |
|
| 94 | def __init__(self):
|
| 95 | # type: () -> None
|
| 96 | vm._Callable.__init__(self)
|
| 97 |
|
| 98 | def Call(self, rd):
|
| 99 | # type: (typed_args.Reader) -> value_t
|
| 100 |
|
| 101 | # TODO: also take an ExprFrag -> Expr
|
| 102 |
|
| 103 | frag = rd.PosCommandFrag()
|
| 104 | frame = rd.PosFrame()
|
| 105 | rd.Done()
|
| 106 | return value.Null
|
| 107 | # TODO: I guess you have to bind 2 frames?
|
| 108 | #return Command(cmd_frag.Expr(frag), frame, None)
|
| 109 |
|
| 110 |
|
| 111 | class GetDebugStack(vm._Callable):
|
| 112 |
|
| 113 | def __init__(self, mem):
|
| 114 | # type: (state.Mem) -> None
|
| 115 | vm._Callable.__init__(self)
|
| 116 | self.mem = mem
|
| 117 |
|
| 118 | def Call(self, rd):
|
| 119 | # type: (typed_args.Reader) -> value_t
|
| 120 | unused_self = rd.PosObj()
|
| 121 | rd.Done()
|
| 122 |
|
| 123 | debug_frames = [] # type: List[value_t]
|
| 124 | for fr in self.mem.debug_stack:
|
| 125 | # Don't show stack frames created when running the ERR trap - we
|
| 126 | # want the main stuff
|
| 127 | if fr.tag() == debug_frame_e.BeforeErrTrap:
|
| 128 | break
|
| 129 | if fr.tag() in (debug_frame_e.ProcLike, debug_frame_e.Source,
|
| 130 | debug_frame_e.CompoundWord, debug_frame_e.Token):
|
| 131 | debug_frames.append(value.DebugFrame(fr))
|
| 132 |
|
| 133 | if 0:
|
| 134 | for fr in debug_frames:
|
| 135 | log('%s', fr.frame)
|
| 136 | return value.List(debug_frames)
|
| 137 |
|
| 138 |
|
| 139 | def _FormatDebugFrame(buf, token):
|
| 140 | # type: (mylib.Writer, Token) -> None
|
| 141 | """
|
| 142 | Based on _AddCallToken in core/state.py
|
| 143 | Should probably move that into core/dev.py or something, and unify them
|
| 144 |
|
| 145 | We also want the column number so we can print ^==
|
| 146 | """
|
| 147 | # note: absolute path can be lon,g, but Python prints it too
|
| 148 | call_source = ui.GetLineSourceString(token.line)
|
| 149 | line_num = token.line.line_num
|
| 150 | call_line = token.line.content
|
| 151 |
|
| 152 | func_str = ''
|
| 153 | # This gives the wrong token? If we are calling p, it gives the definition
|
| 154 | # of p. It doesn't give the func/proc that contains the call to p.
|
| 155 |
|
| 156 | #if def_tok is not None:
|
| 157 | # #log('DEF_TOK %s', def_tok)
|
| 158 | # func_str = ' in %s' % lexer.TokenVal(def_tok)
|
| 159 |
|
| 160 | # should be exactly 1 line
|
| 161 | buf.write('%s:%d\n' % (call_source, line_num))
|
| 162 |
|
| 163 | maybe_newline = '' if call_line.endswith('\n') else '\n'
|
| 164 | buf.write(' %s%s' % (call_line, maybe_newline))
|
| 165 |
|
| 166 | buf.write(' ') # prefix
|
| 167 | ui.PrintCaretLine(call_line, token.col, token.length, buf)
|
| 168 |
|
| 169 |
|
| 170 | class DebugFrameToString(vm._Callable):
|
| 171 |
|
| 172 | def __init__(self):
|
| 173 | # type: () -> None
|
| 174 | vm._Callable.__init__(self)
|
| 175 |
|
| 176 | def Call(self, rd):
|
| 177 | # type: (typed_args.Reader) -> value_t
|
| 178 | frame = rd.PosDebugFrame()
|
| 179 |
|
| 180 | rd.Done()
|
| 181 |
|
| 182 | UP_frame = frame
|
| 183 | buf = mylib.BufWriter()
|
| 184 | with tagswitch(frame) as case:
|
| 185 | if case(debug_frame_e.ProcLike):
|
| 186 | frame = cast(debug_frame.ProcLike, UP_frame)
|
| 187 | invoke_token = location.LeftTokenForCompoundWord(
|
| 188 | frame.invoke_loc)
|
| 189 | assert invoke_token is not None, frame.invoke_loc
|
| 190 | _FormatDebugFrame(buf, invoke_token)
|
| 191 |
|
| 192 | elif case(debug_frame_e.Source):
|
| 193 | frame = cast(debug_frame.Source, UP_frame)
|
| 194 | invoke_token = location.LeftTokenForCompoundWord(
|
| 195 | frame.source_loc)
|
| 196 | assert invoke_token is not None, frame.source_loc
|
| 197 | _FormatDebugFrame(buf, invoke_token)
|
| 198 |
|
| 199 | elif case(debug_frame_e.CompoundWord):
|
| 200 | frame = cast(CompoundWord, UP_frame)
|
| 201 | invoke_token = location.LeftTokenForCompoundWord(frame)
|
| 202 | assert invoke_token is not None, frame
|
| 203 | _FormatDebugFrame(buf, invoke_token)
|
| 204 |
|
| 205 | elif case(debug_frame_e.Token):
|
| 206 | frame = cast(Token, UP_frame)
|
| 207 | _FormatDebugFrame(buf, frame)
|
| 208 |
|
| 209 | # The location is unused; it is a sentinel
|
| 210 | #elif case(debug_frame_e.BeforeErrTrap):
|
| 211 | # frame = cast(debug_frame.BeforeErrTrap, UP_frame)
|
| 212 | # _FormatDebugFrame(buf, frame.tok)
|
| 213 |
|
| 214 | else:
|
| 215 | raise AssertionError()
|
| 216 |
|
| 217 | return value.Str(buf.getvalue())
|
| 218 |
|
| 219 |
|
| 220 | class Shvar_get(vm._Callable):
|
| 221 | """Look up with dynamic scope."""
|
| 222 |
|
| 223 | def __init__(self, mem):
|
| 224 | # type: (state.Mem) -> None
|
| 225 | vm._Callable.__init__(self)
|
| 226 | self.mem = mem
|
| 227 |
|
| 228 | def Call(self, rd):
|
| 229 | # type: (typed_args.Reader) -> value_t
|
| 230 | name = rd.PosStr()
|
| 231 | rd.Done()
|
| 232 | return state.DynamicGetVar(self.mem, name, scope_e.Dynamic)
|
| 233 |
|
| 234 |
|
| 235 | class GetVar(vm._Callable):
|
| 236 | """Look up a variable, with normal scoping rules."""
|
| 237 |
|
| 238 | def __init__(self, mem):
|
| 239 | # type: (state.Mem) -> None
|
| 240 | vm._Callable.__init__(self)
|
| 241 | self.mem = mem
|
| 242 |
|
| 243 | def Call(self, rd):
|
| 244 | # type: (typed_args.Reader) -> value_t
|
| 245 | name = rd.PosStr()
|
| 246 | rd.Done()
|
| 247 | return state.DynamicGetVar(self.mem, name, scope_e.LocalOrGlobal)
|
| 248 |
|
| 249 |
|
| 250 | class SetVar(vm._Callable):
|
| 251 | """Set a variable in the local scope.
|
| 252 |
|
| 253 | We could have a separae setGlobal() too.
|
| 254 | """
|
| 255 |
|
| 256 | def __init__(self, mem):
|
| 257 | # type: (state.Mem) -> None
|
| 258 | vm._Callable.__init__(self)
|
| 259 | self.mem = mem
|
| 260 |
|
| 261 | def Call(self, rd):
|
| 262 | # type: (typed_args.Reader) -> value_t
|
| 263 | var_name = rd.PosStr()
|
| 264 | val = rd.PosValue()
|
| 265 | set_global = rd.NamedBool('global', False)
|
| 266 | rd.Done()
|
| 267 | scope = scope_e.GlobalOnly if set_global else scope_e.LocalOnly
|
| 268 | self.mem.SetNamed(location.LName(var_name), val, scope)
|
| 269 | return value.Null
|
| 270 |
|
| 271 |
|
| 272 | class ParseCommand(vm._Callable):
|
| 273 |
|
| 274 | def __init__(self, parse_ctx, mem, errfmt):
|
| 275 | # type: (parse_lib.ParseContext, state.Mem, ui.ErrorFormatter) -> None
|
| 276 | self.parse_ctx = parse_ctx
|
| 277 | self.mem = mem
|
| 278 | self.errfmt = errfmt
|
| 279 |
|
| 280 | def Call(self, rd):
|
| 281 | # type: (typed_args.Reader) -> value_t
|
| 282 | code_str = rd.PosStr()
|
| 283 | rd.Done()
|
| 284 |
|
| 285 | line_reader = reader.StringLineReader(code_str, self.parse_ctx.arena)
|
| 286 | c_parser = self.parse_ctx.MakeOshParser(line_reader)
|
| 287 |
|
| 288 | # TODO: it would be nice to point to the location of the expression
|
| 289 | # argument
|
| 290 | src = source.Dynamic('parseCommand()', rd.LeftParenToken())
|
| 291 | with alloc.ctx_SourceCode(self.parse_ctx.arena, src):
|
| 292 | try:
|
| 293 | cmd = main_loop.ParseWholeFile(c_parser)
|
| 294 | except error.Parse as e:
|
| 295 | # This prints the location
|
| 296 | self.errfmt.PrettyPrintError(e)
|
| 297 |
|
| 298 | # TODO: add inner location info to this structured error
|
| 299 | raise error.Structured(3, "Syntax error in parseCommand()",
|
| 300 | rd.LeftParenToken())
|
| 301 |
|
| 302 | # TODO: It's a little weird that this captures?
|
| 303 | # We should have scoping like 'eval $mystr'
|
| 304 | # Or we should have
|
| 305 | #
|
| 306 | # var c = parseCommand('echo hi') # raw AST
|
| 307 | # var block = Block(c) # attachs the current frame
|
| 308 | #
|
| 309 | # Yeah we might need this for value.Expr too, to control evaluation of
|
| 310 | # names
|
| 311 | #
|
| 312 | # value.Expr vs. value.BoundExpr - it's bound to the frame it's defined
|
| 313 | # in
|
| 314 | # value.Command vs. value.Block - BoundCommand?
|
| 315 |
|
| 316 | return value.Command(cmd_frag.Expr(cmd), self.mem.CurrentFrame(),
|
| 317 | self.mem.GlobalFrame())
|
| 318 |
|
| 319 |
|
| 320 | class ParseExpr(vm._Callable):
|
| 321 |
|
| 322 | def __init__(self, parse_ctx, errfmt):
|
| 323 | # type: (parse_lib.ParseContext, ui.ErrorFormatter) -> None
|
| 324 | self.parse_ctx = parse_ctx
|
| 325 | self.errfmt = errfmt
|
| 326 |
|
| 327 | def Call(self, rd):
|
| 328 | # type: (typed_args.Reader) -> value_t
|
| 329 | code_str = rd.PosStr()
|
| 330 | rd.Done()
|
| 331 |
|
| 332 | return value.Null
|