Replies: 3 comments
-
This is actually wider than stdio, it's about laying out common interface layers to get everything to plug together like lego instead of something bespoke each time. Here's the rough structure I'm thinking of. |
Beta Was this translation helpful? Give feedback.
-
An Observation of a
|
Beta Was this translation helpful? Give feedback.
-
Hi 👋 I realise it's been a while but @sogaiu directed me to this discussion as I collaborate with him on an improved Conjure client for Janet. For background, the client I'm considering would use the proto-repl protocol over stdio. I think Conjure should offer:
The protocol libraries would be built on the messaging library which would be built on the interface libraries. This would allow a client author to choose the level appropriate for their client. If you need to control the IO directly, you can build on top of the IO library. If you're trying to implement an unsupported protocol, you'd use the messaging library but handle the protocol encoding and decoding in your client. If you're making a client that uses a supported protocol, you've got it the easiest: just use the respective library and away you go. So, for example, what I'm looking for is a very simple library that, based on a configuration table, (1) starts my REPL process and hooks up the IO, (2) sets a general response callback and (3) returns a 'REPL object' I can use for calling a send function. Since the protocol I want to implement is stateless, I don't need (or want) the library to try to match messages with callbacks. Just call my response callback with every message you get and I'll handle interpreting them. |
Beta Was this translation helpful? Give feedback.
-
This is sort of in response to an email from @russtoku, so thank you very much for prompting the thought on this! Hopefully writing down my thoughts in a public way like this will prove beneficial to a few people.
The current
conjure.remote.stdio
Clients that rely on interacting with processes over stdio all share a dependency on
conjure.remote.stdio
, it's a generic wrapper of the libuv process management functions that allows clients to start, manage and talk to a sub process. It uses some pattern matching (Lua lacks regular expressions but it does have "patterns", worth getting familiar with it) to work out when the REPL has sent a "prompt" to stdout, signifying that it's completed something and / or is ready to accept input.So if we want to wait until the REPL prints
$>
, we'd configure the prompt pattern for the stdio remote to look for that, then we can send code to it and the system will extract the text between the$>
prompts as the output of the command. Let's look at the following interaction:Conjure sent the
(+ 10 20)
and now it spots the$>
. It's been storing all output from when it last sent an input, so when it sees the next prompt it cuts off the result string, prints it and resets it. This is extremely simplistic and doesn't account for a bunch of things in different REPLs.It assumes your REPL only ever prints stdout / eval results and a prompt of some kind that remains fairly stable. That's a big assumption! And they say to assume is to make an ass out of u and me.
The desired future of
conjure.remote.stdio2
So, this is too simplistic and tightly coupled to stdio. It's tightly coupled to the idea of a REPL with a prompt and some sort of user -> system -> user -> system only flow. Anything that deviates from that just won't work or will be very wonky. So, let's split these ideas up a little:
So we'd have the IO separate from the stateful side and helpers separate from the domain specific ideas of each individual REPL and language. Specialisations here would include things like how some Lisps drop into a debug interface on error, so if you invoke something it doesn't like it will display a DIFFERENT prompt that indicates that you can now inspect the state of the REPL to debug what happened.
They essentially default to a stepping debugger when something goes wrong, we can't support that right now but this IO/state/domain specific separation would allow us to express that pretty well. We can't just view REPLs as "all you need is the last line", we need to store some scroll back and inspect it when new lines arrive to build context.
Conclusion
So I want to separate the IO from the parsing logic and I want to make the REPLs more stateful (in a contained way) relying on shared tooling keep code to a minimum. Some REPLs that are simpler and really just need that original REPL prompt pattern style from before could share a module with similar ideas. Common Lisp could build on this idea and add support for the debugger side.
I haven't nailed down that stateful layer or shared functions just yet. That's something that will have to remain simple, open and flexible. It'll be built out for one implementation first while leaving room to expand for languages that require more tools and ideas.
I want to see what others think before I really commit to this sort of architecture and would appreciate help in implementing this sort of things for the various clients we have. Feedback is much appreciated. Thanks!
Beta Was this translation helpful? Give feedback.
All reactions