1 #!/usr/bin/env python2
2 """Typed_args.py."""
3 from __future__ import print_function
4
5 from _devbuild.gen.runtime_asdl import value_t
6 from _devbuild.gen.syntax_asdl import (loc, ArgList, BlockArg, command_t,
7 expr_e, expr_t, CommandSub)
8 from core import error
9 from core.error import e_usage
10 from mycpp.mylib import dict_erase, tagswitch
11 from ysh import val_ops
12
13 from typing import Optional, Dict, List, cast
14
15
16 class Reader(object):
17 """
18 func f(a Str) {
19
20 is equivalent to
21
22 t = typed_args.Reader(pos_args, named_args)
23 a = t.PosStr()
24 t.Done() # checks for no more args
25
26 func f(a Str, b Int, ...args; c=0, d='foo', ...named) {
27
28 is equivalent to
29
30 t = typed_args.Reader(pos_args, named_args)
31 a = t.PosStr()
32 b = t.PosInt()
33 args = t.RestPos()
34
35 t.NamedInt('c', 0)
36 t.NamedStr('d', 'foo')
37 named = t.RestNamed()
38
39 t.Done()
40
41 procs have more options:
42
43 proc p(a, b; a Str, b Int; c=0; block) {
44
45 is equivalent to
46
47 t = typed_args.Reader(argv, pos_args, named_args)
48
49 a = t.Word()
50 b = t.Word()
51
52 t.NamedInt('c', 0)
53
54 block = t.Block()
55
56 t.Done()
57 """
58
59 def __init__(self, pos_args, named_args):
60 # type: (List[value_t], Dict[str, value_t]) -> None
61 self.pos_args = pos_args
62 self.pos_consumed = 0
63 self.named_args = named_args
64
65 ### Words: untyped args for procs
66
67 def Word(self):
68 # type: () -> str
69 return None # TODO
70
71 def RestWords(self):
72 # type: () -> List[str]
73 return None # TODO
74
75 ### Typed positional args
76
77 def _GetNextPos(self):
78 # type: () -> value_t
79 if len(self.pos_args) == 0:
80 # TODO: may need location info
81 raise error.TypeErrVerbose(
82 'Expected at least %d arguments, but only got %d' %
83 (self.pos_consumed + 1, self.pos_consumed), loc.Missing)
84
85 self.pos_consumed += 1
86 return self.pos_args.pop(0)
87
88 def PosStr(self):
89 # type: () -> str
90 arg = self._GetNextPos()
91 msg = 'Arg %d should be a Str' % self.pos_consumed
92 return val_ops.ToStr(arg, msg, loc.Missing)
93
94 def PosInt(self):
95 # type: () -> int
96 arg = self._GetNextPos()
97 msg = 'Arg %d should be an Int' % self.pos_consumed
98 return val_ops.ToInt(arg, msg, loc.Missing)
99
100 def PosFloat(self):
101 # type: () -> float
102 arg = self._GetNextPos()
103 msg = 'Arg %d should be a Float' % self.pos_consumed
104 return val_ops.ToFloat(arg, msg, loc.Missing)
105
106 def PosList(self):
107 # type: () -> List[value_t]
108 arg = self._GetNextPos()
109 msg = 'Arg %d should be a List' % self.pos_consumed
110 return val_ops.ToList(arg, msg, loc.Missing)
111
112 def PosDict(self):
113 # type: () -> Dict[str, value_t]
114 arg = self._GetNextPos()
115 msg = 'Arg %d should be a Dict' % self.pos_consumed
116 return val_ops.ToDict(arg, msg, loc.Missing)
117
118 def PosCommand(self):
119 # type: () -> command_t
120 arg = self._GetNextPos()
121 msg = 'Arg %d should be a Command' % self.pos_consumed
122 return val_ops.ToCommand(arg, msg, loc.Missing)
123
124 def PosValue(self):
125 # type: () -> value_t
126 return self._GetNextPos()
127
128 def NumPos(self):
129 # type: () -> int
130 return len(self.pos_args)
131
132 def RestPos(self):
133 # type: () -> List[value_t]
134 ret = self.pos_args
135 self.pos_args = []
136 return ret
137
138 ### Typed named args
139
140 def NamedStr(self, param_name, default_):
141 # type: (str, str) -> str
142 if param_name not in self.named_args:
143 return default_
144
145 msg = 'Named arg %r should be a Str' % param_name
146 ret = val_ops.ToStr(self.named_args[param_name], msg, loc.Missing)
147 dict_erase(self.named_args, param_name)
148 return ret
149
150 def NamedInt(self, param_name, default_):
151 # type: (str, int) -> int
152 if param_name not in self.named_args:
153 return default_
154
155 msg = 'Named arg %r should be an Int' % param_name
156 ret = val_ops.ToInt(self.named_args[param_name], msg, loc.Missing)
157 dict_erase(self.named_args, param_name)
158 return ret
159
160 def NamedFloat(self, param_name, default_):
161 # type: (str, float) -> float
162 if param_name not in self.named_args:
163 return default_
164
165 msg = 'Named arg %r should be a Float' % param_name
166 ret = val_ops.ToFloat(self.named_args[param_name], msg, loc.Missing)
167 dict_erase(self.named_args, param_name)
168 return ret
169
170 def NamedList(self, param_name, default_):
171 # type: (str, List[value_t]) -> List[value_t]
172 if param_name not in self.named_args:
173 return default_
174
175 msg = 'Named arg %r should be a List' % param_name
176 ret = val_ops.ToList(self.named_args[param_name], msg, loc.Missing)
177 dict_erase(self.named_args, param_name)
178 return ret
179
180 def NamedDict(self, param_name, default_):
181 # type: (str, Dict[str, value_t]) -> Dict[str, value_t]
182 if param_name not in self.named_args:
183 return default_
184
185 msg = 'Named arg %r should be a Dict' % param_name
186 ret = val_ops.ToDict(self.named_args[param_name], msg, loc.Missing)
187 dict_erase(self.named_args, param_name)
188 return ret
189
190 def RestNamed(self):
191 # type: () -> Dict[str, value_t]
192 ret = self.named_args
193 self.named_args = {}
194 return ret
195
196 def Block(self):
197 # type: () -> command_t
198 """
199 Block arg for proc
200 """
201 # TODO: is this BraceGroup?
202 return None # TODO
203
204 def Done(self):
205 # type: () -> None
206 """
207 Check that no extra arguments were passed
208
209 4 checks: words, pos, named, block
210
211 It's a little weird that we report all errors at the end, but no
212 problem
213 """
214 # Note: Python throws TypeError on mismatch
215 if len(self.pos_args):
216 raise error.TypeErrVerbose('Expected %d arguments, but got %d' %
217 (self.pos_consumed, self.pos_consumed +
218 len(self.pos_args)), loc.Missing)
219
220 if len(self.named_args):
221 bad_args = ','.join(self.named_args.keys())
222 raise error.TypeErrVerbose('Got unexpected named args: %s' % bad_args, loc.Missing)
223
224
225 def DoesNotAccept(arg_list):
226 # type: (Optional[ArgList]) -> None
227 if arg_list is not None:
228 e_usage('got unexpected typed args', arg_list.left)
229
230
231 def RequiredExpr(arg_list):
232 # type: (Optional[ArgList]) -> Optional[expr_t]
233 if arg_list is None:
234 e_usage('Expected an expression', loc.Missing)
235
236 n = len(arg_list.pos_args)
237 if n == 0:
238 e_usage('Expected an expression', arg_list.left)
239
240 elif n == 1:
241 return arg_list.pos_args[0]
242
243 else:
244 e_usage('Too many typed args (expected one expression)', arg_list.left)
245
246
247 def GetOneBlock(arg_list):
248 # type: (Optional[ArgList]) -> Optional[command_t]
249 """Returns the first block arg, if any.
250
251 For cd { }, shopt { }, etc.
252
253 Errors:
254 - the first arg isn't a block
255 - more than 1 arg
256 """
257
258 if arg_list is None:
259 return None
260
261 n = len(arg_list.pos_args)
262 if n == 0:
263 return None
264
265 elif n == 1:
266 arg = arg_list.pos_args[0]
267 UP_arg = arg
268
269 # Could we somehow consolidate these?
270 with tagswitch(arg) as case:
271 if case(expr_e.BlockArg): # cd /tmp { echo hi }
272 arg = cast(BlockArg, UP_arg)
273 return arg.brace_group
274
275 # TODO: we need an expr_ev for cd /tmp (myblock)
276 elif case(expr_e.CommandSub): # cd /tmp (^(echo hi))
277 arg = cast(CommandSub, UP_arg)
278 return arg.child
279
280 else:
281 e_usage('Expected block argument', arg_list.left)
282
283 else:
284 e_usage('Too many typed args (expected one block)', arg_list.left)
285
286
287 def GetLiteralBlock(arg_list):
288 # type: (Optional[ArgList]) -> Optional[BlockArg]
289 """Returns the first block literal arg, if any.
290
291 For Hay evaluation.
292
293 Errors:
294 - more than 1 arg
295 """
296
297 if arg_list is None:
298 return None
299
300 n = len(arg_list.pos_args)
301 if n == 0:
302 return None
303
304 elif n == 1:
305 arg = arg_list.pos_args[0]
306 if arg.tag() == expr_e.BlockArg:
307 return cast(BlockArg, arg)
308 else:
309 return None
310
311 else:
312 e_usage('Too many typed args (expected one block)', arg_list.left)