Skip to content

Commit

Permalink
[prompt, test, cleanup] Add a cache for the prompt tokens.
Browse files Browse the repository at this point in the history
Also:

- Add a real-world demo/test for $PS1 (that still needs work.)
- Add a cache for the prompt tokens
- Rename to prompt.test.sh.
- [spec/loop] Make the test case a bit harder.
  • Loading branch information
Andy Chu committed Oct 14, 2018
1 parent 832b497 commit 0ebce48
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 24 deletions.
37 changes: 23 additions & 14 deletions core/ui.py
Expand Up @@ -164,13 +164,17 @@ def __init__(self, arena, parse_ctx, ex):
self.hostname = None
self.username = None

self.cache = _PromptCache()
self.parse_cache = {} # PS1 value -> CompoundWord.
self.cache = _PromptCache() # Cache to save syscalls / libc calls.

def _ReplaceBackslashCodes(self, s):
# These caches should reduce memory pressure a bit. We don't want to
# reparse the prompt twice every time you hit enter.
self.tokens_cache = {} # string -> list of tokens
self.parse_cache = {} # string -> CompoundWord.

def _ReplaceBackslashCodes(self, tokens):
ret = []
non_printing = 0
for id_, value in match.PS1_LEXER.Tokens(s):
for id_, value in tokens:
# BadBacklash means they should have escaped with \\, but we can't
# make this an error.
if id_ in (Id.PS_Literals, Id.PS_BadBackslash):
Expand Down Expand Up @@ -218,37 +222,42 @@ def _ReplaceBackslashCodes(self, s):

return ''.join(ret)

def PS1(self):
val = self.ex.mem.GetVar('PS1')
return self.EvalPS1(val)

def EvalPS1(self, val):
def EvalPrompt(self, val):
"""Perform the two evaluations that bash does. Used by $PS1 and ${x@P}."""
if val.tag != value_e.Str:
return DEFAULT_PS1
return DEFAULT_PS1 # no evaluation necessary

try:
tokens = self.tokens_cache[val.s]
except KeyError:
tokens = match.PS1_LEXER.Tokens(val.s)
self.tokens_cache[val.s] = tokens

# First replacements. TODO: Should we cache this too?
ps1_str = self._ReplaceBackslashCodes(val.s)
ps1_str = self._ReplaceBackslashCodes(tokens)

# The prompt is often constant, so we can avoid parsing it.
# NOTE: This is copied from the PS4 logic in Tracer.
try:
ps1_word = self.parse_cache[ps1_str]
except KeyError:
w_parser = self.parse_ctx.MakeWordParserForPlugin(ps1_str, self.arena)

try:
ps1_word = w_parser.ReadPS()
except Exception as e:
error_str = '<ERROR: cannot parse PS1>'
t = ast.token(Id.Lit_Chars, error_str, const.NO_INTEGER)
ps1_word = ast.CompoundWord([ast.LiteralPart(t)])

self.parse_cache[ps1_str] = ps1_word
self.parse_cache[ps1_str] = ps1_word

# e.g. "${debian_chroot}\u" -> '\u'
val2 = self.ex.word_ev.EvalWordToString(ps1_word)
return val2.s

def PS1(self):
val = self.ex.mem.GetVar('PS1')
return self.EvalPrompt(val)


def PrintFilenameAndLine(span_id, arena, f=sys.stderr):
line_span = arena.GetLineSpan(span_id)
Expand Down
2 changes: 1 addition & 1 deletion core/word_eval.py
Expand Up @@ -563,7 +563,7 @@ def _EvalBracedVarSub(self, part, part_vals, quoted):
if op.op_id == Id.VOp0_P:
# TODO: Use dependency injection
#val = self.prompt._EvalPS1(val)
prompt = ui.PROMPT.EvalPS1(val)
prompt = ui.PROMPT.EvalPrompt(val)
val = runtime.Str(prompt)
else:
raise NotImplementedError(op.op_id)
Expand Down
14 changes: 14 additions & 0 deletions demo/git-prompt.sh
@@ -0,0 +1,14 @@
#!/bin/bash
#
# Copied from my bashrc.

# TODO: The branch name isn't being displayed?
git-prompt() {
. testdata/completion/git
. ~/git-prompt.sh

export PS1='\u@\h \w$(__git_ps1 " (%s)")\$ '
}

git-prompt

21 changes: 14 additions & 7 deletions spec/loop.test.sh
Expand Up @@ -129,19 +129,26 @@ one

#### continue in subshell
for i in $(seq 3); do
echo $i
echo "> $i"
( if true; then continue; fi; echo "Should not print" )
echo ". $i"
done
## STDOUT:
1
2
3
> 1
. 1
> 2
. 2
> 3
. 3
## END
## BUG mksh STDOUT:
1
> 1
Should not print
2
. 1
> 2
Should not print
3
. 2
> 3
Should not print
. 3
## END
File renamed without changes.
4 changes: 2 additions & 2 deletions test/spec.sh
Expand Up @@ -207,8 +207,8 @@ smoke() {
sh-spec spec/smoke.test.sh ${REF_SHELLS[@]} $OSH_LIST "$@"
}

ps1-replacements() {
sh-spec spec/ps1-replacements.test.sh --osh-failures-allowed 1 \
prompt() {
sh-spec spec/prompt.test.sh --osh-failures-allowed 1 \
$BASH $OSH_LIST "$@"
}

Expand Down

0 comments on commit 0ebce48

Please sign in to comment.