-
-
Notifications
You must be signed in to change notification settings - Fork 163
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement printf %()T %.s %*s %.*s #668
Changes from 8 commits
44d5af8
8142a78
99ead0e
c5d8736
b695317
27e435c
28c169f
1c84df0
cb08494
88360e5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,14 +5,16 @@ | |
from __future__ import print_function | ||
|
||
from _devbuild.gen.id_kind_asdl import Id, Kind | ||
from _devbuild.gen.runtime_asdl import cmd_value__Argv | ||
from _devbuild.gen.runtime_asdl import cmd_value__Argv, value_e, value__Str | ||
from _devbuild.gen.syntax_asdl import ( | ||
printf_part, printf_part_t, | ||
source | ||
source, | ||
) | ||
from _devbuild.gen.types_asdl import lex_mode_e, lex_mode_t | ||
|
||
import sys | ||
import time | ||
import os | ||
|
||
from asdl import runtime | ||
from core import error | ||
|
@@ -27,7 +29,7 @@ | |
from osh import string_ops | ||
from osh import word_compile | ||
|
||
from typing import Dict, List, TYPE_CHECKING | ||
from typing import Dict, List, TYPE_CHECKING, cast | ||
|
||
if TYPE_CHECKING: | ||
from frontend.lexer import Lexer | ||
|
@@ -40,12 +42,15 @@ | |
PRINTF_SPEC = arg_def.Register('printf') # TODO: Don't need this? | ||
PRINTF_SPEC.ShortFlag('-v', args.Str) | ||
|
||
shell_start_time = time.time() | ||
|
||
class _FormatStringParser(object): | ||
""" | ||
Grammar: | ||
|
||
fmt = Format_Percent Flag? Num? (Dot Num)? Type | ||
width = Num | Star | ||
precision = Dot (Num | Star | Zero)? | ||
fmt = Percent (Flag | Zero)* width? precision? (Type | Time) | ||
part = Char_* | Format_EscapedPercent | fmt | ||
printf_format = part* Eof_Real # we're using the main lexer | ||
|
||
|
@@ -70,7 +75,7 @@ def _ParseFormatStr(self): | |
self._Next(lex_mode_e.PrintfPercent) # move past % | ||
|
||
part = printf_part.Percent() | ||
if self.token_type == Id.Format_Flag: | ||
if self.token_type in (Id.Format_Flag, Id.Format_Zero): | ||
part.flag = self.cur_token | ||
self._Next(lex_mode_e.PrintfPercent) | ||
|
||
|
@@ -79,16 +84,18 @@ def _ParseFormatStr(self): | |
if flag in '# +': | ||
p_die("osh printf doesn't support the %r flag", flag, token=part.flag) | ||
|
||
if self.token_type == Id.Format_Num: | ||
if self.token_type in (Id.Format_Num, Id.Format_Star): | ||
part.width = self.cur_token | ||
self._Next(lex_mode_e.PrintfPercent) | ||
|
||
if self.token_type == Id.Format_Dot: | ||
self._Next(lex_mode_e.PrintfPercent) # past dot | ||
part.precision = self.cur_token | ||
self._Next(lex_mode_e.PrintfPercent) | ||
self._Next(lex_mode_e.PrintfPercent) # past dot | ||
if self.token_type in (Id.Format_Num, Id.Format_Star, Id.Format_Zero): | ||
part.precision = self.cur_token | ||
self._Next(lex_mode_e.PrintfPercent) | ||
|
||
if self.token_type == Id.Format_Type: | ||
if self.token_type in (Id.Format_Type, Id.Format_Time): | ||
part.type = self.cur_token | ||
|
||
# ADDITIONAL VALIDATION outside the "grammar". | ||
|
@@ -108,7 +115,7 @@ def _ParseFormatStr(self): | |
p_die(msg, token=self.cur_token) | ||
|
||
# Do this check AFTER the floating point checks | ||
if part.precision and part.type.val not in 'fs': | ||
if part.precision and part.type.val[-1] not in 'fsT': | ||
p_die("precision can't be specified when here", | ||
token=part.precision) | ||
|
||
|
@@ -205,28 +212,83 @@ def Run(self, cmd_val): | |
out.append(s) | ||
|
||
elif isinstance(part, printf_part.Percent): | ||
try: | ||
width = None | ||
if part.width: | ||
if part.width.id in (Id.Format_Num, Id.Format_Zero): | ||
width = part.width.val | ||
width_spid = part.width.span_id | ||
elif part.width.id == Id.Format_Star: | ||
if arg_index < num_args: | ||
width = varargs[arg_index] | ||
width_spid = spids[arg_index] | ||
arg_index += 1 | ||
else: | ||
width = '' | ||
width_spid = runtime.NO_SPID | ||
else: | ||
raise AssertionError() | ||
|
||
try: | ||
width = int(width) | ||
except ValueError: | ||
if width_spid == runtime.NO_SPID: | ||
width_spid = part.width.span_id | ||
self.errfmt.Print("printf got invalid number %r for the width", s, | ||
span_id = width_spid) | ||
return 1 | ||
|
||
precision = None | ||
if part.precision: | ||
if part.precision.id == Id.Format_Dot: | ||
precision = '0' | ||
precision_spid = part.precision.span_id | ||
elif part.precision.id in (Id.Format_Num, Id.Format_Zero): | ||
precision = part.precision.val | ||
precision_spid = part.precision.span_id | ||
elif part.precision.id == Id.Format_Star: | ||
if arg_index < num_args: | ||
precision = varargs[arg_index] | ||
precision_spid = spids[arg_index] | ||
arg_index += 1 | ||
else: | ||
precision = '' | ||
precision_spid = runtime.NO_SPID | ||
else: | ||
raise AssertionError() | ||
|
||
try: | ||
precision = int(precision) | ||
except ValueError: | ||
if precision_spid == runtime.NO_SPID: | ||
precision_spid = part.precision.span_id | ||
self.errfmt.Print("printf got invalid number %r for the precision", s, | ||
span_id = precision_spid) | ||
return 1 | ||
|
||
if arg_index < num_args: | ||
s = varargs[arg_index] | ||
word_spid = spids[arg_index] | ||
except IndexError: | ||
arg_index += 1 | ||
else: | ||
s = '' | ||
word_spid = runtime.NO_SPID | ||
|
||
typ = part.type.val | ||
if typ == 's': | ||
if part.precision: | ||
precision = int(part.precision.val) | ||
if precision is not None: | ||
s = s[:precision] # truncate | ||
elif typ == 'q': | ||
s = string_ops.ShellQuoteOneLine(s) | ||
elif typ in 'diouxX': | ||
elif typ in 'diouxX' or part.type.id == Id.Format_Time: | ||
try: | ||
d = int(s) | ||
except ValueError: | ||
if len(s) >= 2 and s[0] in '\'"': | ||
# TODO: utf-8 decode s[1:] to be more correct. Probably | ||
# depends on issue #366, a utf-8 library. | ||
d = ord(s[1]) | ||
elif len(s) == 0 and part.type.id == Id.Format_Time: | ||
d = -1 | ||
else: | ||
# This works around the fact that in the arg recycling case, you have no spid. | ||
if word_spid == runtime.NO_SPID: | ||
|
@@ -252,14 +314,31 @@ def Run(self, cmd_val): | |
s = '%x' % d | ||
elif typ == 'X': | ||
s = '%X' % d | ||
elif part.type.id == Id.Format_Time: | ||
# set timezone | ||
tzcell = self.mem.GetCell('TZ') | ||
if tzcell and tzcell.exported and tzcell.val.tag_() == value_e.Str: | ||
tzval = cast(value__Str, tzcell.val) | ||
os.environ['TZ'] = tzval.s | ||
elif 'TZ' in os.environ: | ||
del os.environ['TZ'] | ||
time.tzset() | ||
|
||
if d == -1: # now | ||
d = None | ||
elif d == -2: # shell start time | ||
d = shell_start_time | ||
s = time.strftime(typ[1:-2], time.localtime(d)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no semicolon needed Also where does There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed the semicolon and added comments in cb08494. Also, I changed to explicitly set |
||
if precision is not None: | ||
s = s[:precision] # truncate | ||
|
||
else: | ||
raise AssertionError() | ||
|
||
else: | ||
raise AssertionError() | ||
|
||
if part.width: | ||
width = int(part.width.val) | ||
if width is not None: | ||
if part.flag: | ||
flag = part.flag.val | ||
if flag == '-': | ||
|
@@ -272,7 +351,6 @@ def Run(self, cmd_val): | |
s = s.rjust(width, ' ') | ||
|
||
out.append(s) | ||
arg_index += 1 | ||
|
||
else: | ||
raise AssertionError() | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain this part in a high level comment? I don't know these APIs very well and eventually they will have to be translated to C.
(Actually if you prefer to write in C, then
native/libc.c
has related functions and is fairly easy to modify.)Does
strftime
reados.environ['TZ']
? I'm not sure how it works exactly.OK I looked over the docs a bit, I guess
time.tzset()
readsos.environ['TZ']
. That's kind of confusing. Please add a comment about that.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cb08494 Thank you! I added code comments. (edit: added a typofix)