OILS / builtin / func_misc.py View on Github | oils.pub

602 lines, 355 significant
1#!/usr/bin/env python2
2"""
3func_misc.py
4"""
5from __future__ import print_function
6
7from _devbuild.gen.value_asdl import (value, value_e, value_t, value_str, Obj)
8
9from core import error
10from core import num
11from display import pp_value
12from display import ui
13from core import vm
14from data_lang import j8
15from frontend import match
16from frontend import typed_args
17from mycpp import mops
18from mycpp import mylib
19from mycpp.mylib import NewDict, iteritems, log, tagswitch, str_cmp
20from ysh import val_ops
21
22from typing import TYPE_CHECKING, Dict, List, Optional, cast
23if TYPE_CHECKING:
24 from osh import split
25
26_ = log
27
28
29class Object(vm._Callable):
30 """OLD API to a value.Obj
31
32 The order of params follows JavaScript's Object.create():
33 var obj = Object(prototype, props)
34 """
35
36 def __init__(self):
37 # type: () -> None
38 pass
39
40 def Call(self, rd):
41 # type: (typed_args.Reader) -> value_t
42
43 prototype = rd.PosValue()
44 proto_loc = rd.BlamePos()
45
46 props = rd.PosDict()
47 rd.Done()
48
49 chain = None # type: Optional[Obj]
50 UP_prototype = prototype
51 with tagswitch(prototype) as case:
52 if case(value_e.Null):
53 pass
54 elif case(value_e.Obj):
55 prototype = cast(Obj, UP_prototype)
56 chain = prototype
57 else:
58 raise error.TypeErr(prototype, 'Object() expected Obj or Null',
59 proto_loc)
60
61 return Obj(chain, props)
62
63
64class Obj_call(vm._Callable):
65 """New API to create a value.Obj
66
67 It has a more natural order
68 var obj = Obj(props, prototype)
69
70 Until we have __call__, it's Obj:
71 var obj = Obj.new(props, prototype)
72 """
73
74 def __init__(self):
75 # type: () -> None
76 pass
77
78 def Call(self, rd):
79 # type: (typed_args.Reader) -> value_t
80
81 props = rd.PosDict()
82
83 prototype = rd.OptionalValue()
84 proto_loc = rd.BlamePos()
85
86 rd.Done()
87
88 chain = None # type: Optional[Obj]
89
90 if prototype is not None:
91 UP_prototype = prototype
92 with tagswitch(prototype) as case:
93 if case(value_e.Null): # Obj({}, null)
94 pass
95 elif case(value_e.Obj):
96 prototype = cast(Obj, UP_prototype)
97 chain = prototype
98 else:
99 raise error.TypeErr(prototype,
100 'Object() expected Obj or Null',
101 proto_loc)
102
103 return Obj(chain, props)
104
105
106class Prototype(vm._Callable):
107 """Get an object's prototype."""
108
109 def __init__(self):
110 # type: () -> None
111 pass
112
113 def Call(self, rd):
114 # type: (typed_args.Reader) -> value_t
115 obj = rd.PosObj()
116 rd.Done()
117
118 if obj.prototype is None:
119 return value.Null
120
121 return obj.prototype
122
123
124class PropView(vm._Callable):
125 """Get a Dict view of an object's properties."""
126
127 def __init__(self):
128 # type: () -> None
129 pass
130
131 def Call(self, rd):
132 # type: (typed_args.Reader) -> value_t
133 obj = rd.PosObj()
134 rd.Done()
135
136 return value.Dict(obj.d)
137
138
139class Len(vm._Callable):
140
141 def __init__(self):
142 # type: () -> None
143 pass
144
145 def Call(self, rd):
146 # type: (typed_args.Reader) -> value_t
147
148 x = rd.PosValue()
149 rd.Done()
150
151 UP_x = x
152 with tagswitch(x) as case:
153 if case(value_e.List):
154 x = cast(value.List, UP_x)
155 return num.ToBig(len(x.items))
156
157 elif case(value_e.Dict):
158 x = cast(value.Dict, UP_x)
159 return num.ToBig(len(x.d))
160
161 elif case(value_e.Str):
162 x = cast(value.Str, UP_x)
163 return num.ToBig(len(x.s))
164
165 raise error.TypeErr(x, 'len() expected Str, List, or Dict',
166 rd.BlamePos())
167
168
169class Type(vm._Callable):
170
171 def __init__(self):
172 # type: () -> None
173 pass
174
175 def Call(self, rd):
176 # type: (typed_args.Reader) -> value_t
177
178 val = rd.PosValue()
179 rd.Done()
180
181 # TODO: assert it's not Undef, Interrupted, Slice
182 # Then return an Obj type
183 #
184 # It would be nice if they were immutable, if we didn't have to create
185 # 23-24 dicts and 23-24 Obj on startup?
186 return value.Str(ui.ValType(val))
187
188
189class Join(vm._Callable):
190 """Both free function join() and List->join() method."""
191
192 def __init__(self):
193 # type: () -> None
194 pass
195
196 def Call(self, rd):
197 # type: (typed_args.Reader) -> value_t
198
199 li = rd.PosList()
200 delim = rd.OptionalStr(default_='')
201 rd.Done()
202
203 strs = [] # type: List[str]
204 for i, el in enumerate(li):
205 strs.append(val_ops.Stringify(el, rd.LeftParenToken(), 'join() '))
206
207 return value.Str(delim.join(strs))
208
209
210class Maybe(vm._Callable):
211
212 def __init__(self):
213 # type: () -> None
214 pass
215
216 def Call(self, rd):
217 # type: (typed_args.Reader) -> value_t
218
219 val = rd.PosValue()
220 rd.Done()
221
222 if val == value.Null:
223 return value.List([])
224
225 s = val_ops.ToStr(
226 val, 'maybe() expected Str, but got %s' % value_str(val.tag()),
227 rd.LeftParenToken())
228 if len(s):
229 return value.List([val]) # use val to avoid needlessly copy
230
231 return value.List([])
232
233
234class Bool(vm._Callable):
235
236 def __init__(self):
237 # type: () -> None
238 pass
239
240 def Call(self, rd):
241 # type: (typed_args.Reader) -> value_t
242
243 val = rd.PosValue()
244 rd.Done()
245
246 return value.Bool(val_ops.ToBool(val))
247
248
249class Int(vm._Callable):
250
251 def __init__(self):
252 # type: () -> None
253 pass
254
255 def Call(self, rd):
256 # type: (typed_args.Reader) -> value_t
257
258 val = rd.PosValue()
259 rd.Done()
260
261 UP_val = val
262 with tagswitch(val) as case:
263 if case(value_e.Int):
264 return val
265
266 elif case(value_e.Bool):
267 val = cast(value.Bool, UP_val)
268 return value.Int(mops.FromBool(val.b))
269
270 elif case(value_e.Float):
271 val = cast(value.Float, UP_val)
272 ok, big_int = mops.FromFloat(val.f)
273 if ok:
274 return value.Int(big_int)
275 else:
276 raise error.Expr(
277 "Can't convert float %s to Int" %
278 pp_value.FloatString(val.f), rd.BlamePos())
279
280 elif case(value_e.Str):
281 val = cast(value.Str, UP_val)
282 if not match.LooksLikeYshInt(val.s):
283 raise error.Expr("Can't convert %s to Int" % val.s,
284 rd.BlamePos())
285
286 s = val.s.replace('_', '')
287 ok, big_int = mops.FromStr2(s)
288 if not ok:
289 raise error.Expr("Integer too big: %s" % val.s,
290 rd.BlamePos())
291
292 return value.Int(big_int)
293
294 raise error.TypeErr(val, 'int() expected Bool, Int, Float, or Str',
295 rd.BlamePos())
296
297
298class Float(vm._Callable):
299
300 def __init__(self):
301 # type: () -> None
302 pass
303
304 def Call(self, rd):
305 # type: (typed_args.Reader) -> value_t
306
307 val = rd.PosValue()
308 rd.Done()
309
310 UP_val = val
311 with tagswitch(val) as case:
312 if case(value_e.Int):
313 val = cast(value.Int, UP_val)
314 return value.Float(mops.ToFloat(val.i))
315
316 elif case(value_e.Float):
317 return val
318
319 elif case(value_e.Str):
320 val = cast(value.Str, UP_val)
321 if not match.LooksLikeYshFloat(val.s):
322 raise error.Expr('Cannot convert %s to Float' % val.s,
323 rd.BlamePos())
324
325 return value.Float(float(val.s))
326
327 raise error.TypeErr(val, 'float() expected Int, Float, or Str',
328 rd.BlamePos())
329
330
331class Str_(vm._Callable):
332
333 def __init__(self):
334 # type: () -> None
335 pass
336
337 def Call(self, rd):
338 # type: (typed_args.Reader) -> value_t
339
340 val = rd.PosValue()
341 rd.Done()
342
343 with tagswitch(val) as case:
344 # Avoid extra allocation
345 if case(value_e.Str):
346 return val
347 else:
348 s = val_ops.Stringify(val, rd.LeftParenToken(), 'str() ')
349 return value.Str(s)
350
351
352class List_(vm._Callable):
353
354 def __init__(self):
355 # type: () -> None
356 pass
357
358 def Call(self, rd):
359 # type: (typed_args.Reader) -> value_t
360
361 val = rd.PosValue()
362 rd.Done()
363
364 l = [] # type: List[value_t]
365 it = None # type: val_ops.Iterator
366 UP_val = val
367 with tagswitch(val) as case:
368 if case(value_e.List):
369 val = cast(value.List, UP_val)
370 it = val_ops.ListIterator(val)
371
372 elif case(value_e.Dict):
373 val = cast(value.Dict, UP_val)
374 it = val_ops.DictIterator(val)
375
376 elif case(value_e.Range):
377 val = cast(value.Range, UP_val)
378 it = val_ops.RangeIterator(val)
379
380 else:
381 raise error.TypeErr(val,
382 'list() expected Dict, List, or Range',
383 rd.BlamePos())
384
385 assert it is not None
386 while True:
387 first = it.FirstValue()
388 if first is None:
389 break
390 l.append(first)
391 it.Next()
392
393 return value.List(l)
394
395
396class DictFunc(vm._Callable):
397
398 def __init__(self):
399 # type: () -> None
400 pass
401
402 def Call(self, rd):
403 # type: (typed_args.Reader) -> value_t
404
405 val = rd.PosValue()
406 rd.Done()
407
408 UP_val = val
409 with tagswitch(val) as case:
410 if case(value_e.Dict):
411 val = cast(value.Dict, UP_val)
412 d = NewDict() # type: Dict[str, value_t]
413 for k, v in iteritems(val.d):
414 d[k] = v
415
416 return value.Dict(d)
417
418 elif case(value_e.Obj):
419 val = cast(Obj, UP_val)
420 d = NewDict()
421 for k, v in iteritems(val.d):
422 d[k] = v
423
424 return value.Dict(d)
425
426 elif case(value_e.BashAssoc):
427 val = cast(value.BashAssoc, UP_val)
428 d = NewDict()
429 for k, s in iteritems(val.d):
430 d[k] = value.Str(s)
431
432 return value.Dict(d)
433
434 elif case(value_e.Frame):
435 val = cast(value.Frame, UP_val)
436 d = NewDict()
437 for k, cell in iteritems(val.frame):
438 d[k] = cell.val
439
440 return value.Dict(d)
441
442 raise error.TypeErr(val, 'dict() expected Dict, Obj, or BashAssoc',
443 rd.BlamePos())
444
445
446class Runes(vm._Callable):
447
448 def __init__(self):
449 # type: () -> None
450 pass
451
452 def Call(self, rd):
453 # type: (typed_args.Reader) -> value_t
454 return value.Null
455
456
457class EncodeRunes(vm._Callable):
458
459 def __init__(self):
460 # type: () -> None
461 pass
462
463 def Call(self, rd):
464 # type: (typed_args.Reader) -> value_t
465 return value.Null
466
467
468class Bytes(vm._Callable):
469
470 def __init__(self):
471 # type: () -> None
472 pass
473
474 def Call(self, rd):
475 # type: (typed_args.Reader) -> value_t
476 return value.Null
477
478
479class EncodeBytes(vm._Callable):
480
481 def __init__(self):
482 # type: () -> None
483 pass
484
485 def Call(self, rd):
486 # type: (typed_args.Reader) -> value_t
487 return value.Null
488
489
490class StrCmp(vm._Callable):
491
492 def __init__(self):
493 # type: () -> None
494 pass
495
496 def Call(self, rd):
497 # type: (typed_args.Reader) -> value_t
498 s = rd.PosStr()
499 s2 = rd.PosStr()
500 rd.Done()
501
502 return value.Int(mops.BigInt(str_cmp(s, s2)))
503
504
505class Split(vm._Callable):
506
507 def __init__(self, splitter):
508 # type: (split.SplitContext) -> None
509 vm._Callable.__init__(self)
510 self.splitter = splitter
511
512 def Call(self, rd):
513 # type: (typed_args.Reader) -> value_t
514 s = rd.PosStr()
515
516 ifs = rd.OptionalStr()
517
518 rd.Done()
519
520 l = [
521 value.Str(elem)
522 for elem in self.splitter.SplitForWordEval(s, ifs=ifs)
523 ] # type: List[value_t]
524 return value.List(l)
525
526
527class FloatsEqual(vm._Callable):
528
529 def __init__(self):
530 # type: () -> None
531 pass
532
533 def Call(self, rd):
534 # type: (typed_args.Reader) -> value_t
535 left = rd.PosFloat()
536 right = rd.PosFloat()
537 rd.Done()
538
539 return value.Bool(left == right)
540
541
542class ToJson8(vm._Callable):
543
544 def __init__(self, is_j8):
545 # type: (bool) -> None
546 self.is_j8 = is_j8
547
548 def Call(self, rd):
549 # type: (typed_args.Reader) -> value_t
550
551 val = rd.PosValue()
552 space = mops.BigTruncate(rd.NamedInt('space', 0))
553 type_errors = rd.NamedBool('type_errors', True)
554 rd.Done()
555
556 # Convert from external JS-like API to internal API.
557 if space <= 0:
558 indent = -1
559 else:
560 indent = space
561
562 buf = mylib.BufWriter()
563 try:
564 if self.is_j8:
565 j8.PrintMessage(val, buf, indent, type_errors)
566 else:
567 j8.PrintJsonMessage(val, buf, indent, type_errors)
568 except error.Encode as e:
569 raise error.Structured(error.CODEC_STATUS, e.Message(),
570 rd.LeftParenToken())
571
572 return value.Str(buf.getvalue())
573
574
575class FromJson8(vm._Callable):
576
577 def __init__(self, is_j8):
578 # type: (bool) -> None
579 self.is_j8 = is_j8
580
581 def Call(self, rd):
582 # type: (typed_args.Reader) -> value_t
583
584 s = rd.PosStr()
585 rd.Done()
586
587 p = j8.Parser(s, self.is_j8)
588 try:
589 val = p.ParseValue()
590 except error.Decode as e:
591 # Right now I'm not exposing the original string, because that
592 # could lead to a memory leak in the _error Dict.
593 # The message quotes part of the string, and we could improve
594 # that. We could have a substring with context.
595 props = {
596 'start_pos': num.ToBig(e.start_pos),
597 'end_pos': num.ToBig(e.end_pos),
598 } # type: Dict[str, value_t]
599 raise error.Structured(error.CODEC_STATUS, e.Message(),
600 rd.LeftParenToken(), props)
601
602 return val