Separate parsing from reading shell input#263
Draft
jpco wants to merge 7 commits intowryun:masterfrom
Draft
Conversation
This primitive will be used as a target for $&prompt's new "reader commands". We don't shuffle any code around for this yet; consolidating readline logic into something like a readline.c file will be done later.
$&newparse is like $&parse, except instead of taking prompt arguments and doing all the reading itself, it takes a reader command as its arguments and calls out to that to fetch input. Ideally $&parse should be replaced with $&newparse and then a lot of code can be cleaned up.
Not all of this actually needs $&newparse as a prerequisite, like input->eof and removing the various fill functions.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
NOTE: I've been a bit aggressive with unilaterally merging PRs lately, but this one will not be getting merged without explicit feedback.
The short version: This PR changes the
$&parseprimitive. Previously,$&parsewould take an optional set of prompts and use that to read and parse shell input, producing a parsed command. Now,$&parsetakes a command and calls that command once or more in order to read shell input, which it parses.%parsehas been rewritten to wrap the new$&parsein such a way that its behavior hasn't changed. This PR also exposes the shell's existing readline logic via the$&readlineprimitive, and the new$&parsecalls this primitive.The impact of this is that it decouples prompting, reading, parsing, and writing to history. You can see that in these snippets from initial.es. In this first one, we define the
%read-linefunction, which takes a prompt, prints it, and then reads a line. If it exists, the$&readlineprimitive can implement this function, or it can be done with anechoand a$&read.The second snippet uses this
%read-linefunction, along with the new$&parseand the%write-historyhook, to do all the work that would previously happen within%parse:Doing this allows arbitrary flexibility in writing to history or prompting -- per-line history writing such as before #65 can be done by people who want that, or a different prompt could be used for each and every line. Something like zsh's
TRANSIENT_RPROMPTcould be added. You could even add a hook to transform read-in text before parsing it, in order to add!!-style history expansion or old-school string-replacement-based aliases.This PR also moves both
$&parseand$&readlinetowards being "normal" primitives. In fact, with this PR, all the readline code in the shell is collected into a single file, prim-readline.c. Adding support for an alternative library should only require adding similar primitives in a similar file, as well as some es script to tie things together, which is a much simpler integration story than the dozen or so#if HAVE_READLINEblocks the shell has had till now. (This starts to connect to a potential concept of "extensible primitives", or even "modules", which is in my opinion a very interesting way to begin to direct the shell. Es could inherit loadable modules from Inferno's sh, a shell that arguably descended from es!)Improving the potential to swap out
$&parsemay sound strange, but could also be useful. In addition to changes to internals (switching to a hand-written parser for better error reporting, or an incremental parser, etc.), swappable parsers could be a coarse way to allow certain changes to syntax.A note on the particular API of
$&parse-- why not go even simpler and pass$&parsean already-read string which it is to parse? This would make$&parse, effectively, a "push-style" parser. The major issue with this for es is that push-style parsers must be able to be called multiple times over the course of a single parse: it would be called with one line, return a value indicating it needs more input, and then would be called again with the next, and this would repeat until enough has been fed to it that it can return a fully-parsed statement.Within es, the problem with this pattern is managing parser state across calls. How do we differentiate a second
$&parsecall just trying to parse two lines from a second, unrelated$&parsecall, especially given es lacks opaque handles, so we can't just say$&parse $parser ...? It is much simpler to use a "pull-style" parser, where we give$&parsea way to read input for itself, and have each$&parsecall correspond with a complete, returned command.There are still some corner-cases to handle with this PR. The major remaining problem with it (as evidenced by the test failures) is the fact that
$&read, which the shell now actually uses to read input (when not using$&readline), throws an exception when reading a NUL byte, whereas the previous logic skipped a NUL byte and printed a warning to stderr. Other corner-cases include: how$&readlineshould behave when not reading from a terminal, how to handle multiple$&parses on a single Input.We may want to add some data for
$&parseto pass to reader commands when calling them. One example would be something indicating whether a heredoc is being read, in order to do something like print a different prompt. I'm not in a hurry to add something like this; I think more time is needed to think of what might be included. (I also imagine that a richer API to a curretly-running parser might eventually be useful for something like a tab-completion system, but also don't have any real design for that yet.)