5 ms·
I've had two projects that end up being "oops, I made an interpreter". It starts innocently enough, you just have a JSON that has some basic functionality. Th
by tombert 2y ago
I've had two projects that end up being "oops, I made an interpreter".
It starts innocently enough, you just have a JSON that has some basic functionality. Then you decide it would be cool to nest functionality because there's no reason not to, so you build a recursive parser. Then you think it'd be neat to be able to add some arguments to the recursive stuff, because then you can more easily parameterize the JSON. Then you realize it might be nice to assign names to these things as you call them, you implement that, and now you're treating JSON as an AST and you're stuck with maintaining an interpreter, and life is pain.
- gavinhoward 2y agoSee also the Configuration Complexity Clock: https://mikehadlow.blogspot.com/2012/05/configuration-complexity-clock.html https://mikehadlow.blogspot.com/2012/05/configuration-comple... . This is why I have a separate config language and code language. My config language is essentially JSON with newline separators and a first-class binary type (base64). I added little else. When I get the temptation to add code to it, I just pull out my other, general-purpose language instead.
- aapoalas 2y agoI'm basically in charge of maintaining and developing a product that (on purpose) started at 9 o'clock. We've resisted the call to implement loops and such in our DSL but I can hear the wolves howling and I doubt I have much longer...
- pxc 2y agoI think the world needs programmable config langs because slinging YAML and JSON quickly becomes miserable (as is extending them through templating alone) and general-purpose programming languages usually have shitty ergonomics for writing configuration. The only question in my mind is whether our common config langs should be Turing-complete (e.g., Nix, Nickel, Jsonnet, Pkl) or not (e.g., HCL, CUE, Starlark, Dhall), which I think can only be determined through experience over the long term.
- gavinhoward 2y agoHate to burst your bubble, but because of `break` and early return, Starlark is basically Turing-complete. https://gavinhoward.com/2024/03/what-computers-cannot-do-the-consequences-of-turing-completeness/ https://gavinhoward.com/2024/03/what-computers-cannot-do-the...
- pxc 2y agoThe Starlark spec discusses this, and I consulted it before I wrote my comment. > It is a dynamic error for a function to call itself or another function value with the same declaration. > This rule, combined with the invariant that all loops are iterations over finite sequences, implies that Starlark programs are not Turing-complete. However, an implementation may allow clients to disable this check, allowing unbounded recursion. https://github.com/bazelbuild/starlark/blob/master/spec.md https://github.com/bazelbuild/starlark/blob/master/spec.md I do kind of agree with you about this, though: > At that point, just give your users a `while` loop for usability's sake. I'm generally in favor of powerful, purpose-built configuration languages, including Turing-complete ones. And the goal of assuring users that their programs will terminate at all is definitely aligned with a fuzzier goal, less formalizable, that their programs will terminate in a 'reasonable' period of time, whatever that means.
- __MatrixMan__ 2y agoIf possible, I'd say it's best to just stay at 12:00. Let them be "hard coded" in ~/.config/myapp/config.py or such so that it's very clear which edits should only be done by a wizard and which ones are suitable for a user. This way all your users are suitably placed on a slippery slope where one edit in the same language but to another file will transition them from user to contributor. Jail them in a separate language and they'll stay users forever. Besides, config languages never have the level of polish (re: tab completion and static analysis) that the main language does. Why deny your users the niceties that you set up for yourself?
- pxc 2y ago> Besides, config languages never have the level of polish (re: tab completion and static analysis) that the main language does. They don't have to! > Why deny your users the niceties that you set up for yourself? Don't do this, of course. :) For instance, Nickel gained[1] an LSP implementation years before it hit version 1.0. When editing Nickel configs,byou get all the stuff you'd expect for a programming language: syntax highlighting, type hints, autocompletion, etc. But beyond static analysis, Nickel can also validate your configurations dynamically, according to arbitrary predicates-- and that too, integrates with your editor through the language server. Nickel ships with a command-line evaluator, REPL, pretty printer for data structures, code formatter, documentation generator, and CLI tab completions for Bash, zsh, Fish, PowerShell, and Elvish. Upstream distributes the tooling (which is, Go-style, a single executable with subcommands) as a Nix flake, a Docker image, and static executables for Linux aarch64 and x86_64 (< 30 MB, for now at least). Nickel doesn't yet have a package manager[3], but other than that it honestly has a better tooling story than many general-purpose programming languages currently in widespread use. CUE, an older (but not old) configuration language (very cool in its own right), likewise has great tooling. It ships with a command-line evaluator, a pretty printer, a formatter, package management based on Go modules (i.e., it has both a `mod` and a `get` subcommand), a few refactoring tools, and tab completions for bash, zsh, and fish.[4] It currently lacks a language server[5] but there is recent and ongoing work to enable that. There's no reason that a configuration language can't have first-rate tooling! There is a new crop of really innovative, powerful configuration languages out there. I hope they start catching on. > This way all your users are suitably placed on a slippery slope where one edit in the same language but to another file will transition them from user to contributor. > Jail them in a separate language and they'll stay users forever. This, I admit, deeply appeals to me. It's one of the things I really like about the Nix language, wart-y though it may be: the tools you use to configure your NixOS computer are the same ones you can go on to use to extend NixOS. I think that's an enormous strength. But I also like for simple configuration to look really simple, and not trouble you much with the details of the language (or, God forbid, configuring the runtime). Perhaps programmable configuration languages, then, are good fits for tools like build systems and packaging systems (where the 'upstream' code users might next want to play with is also essentially fancy, expressive configuration in a special domain), but embedded DSLs might be better for applications (and work for packaging and build systems, too). Re: creating a continuous road for users to hack on the software they use, GNU is really trying to do something special there: with Guix you have application configuration, package management, and system configuration handled in a DSL in Guile Scheme. But then Guix itself is also written in Guile! And at the same time, Guile is the official extension language of the GNU project, so GNU applications all (hopefully?) have Guile bindings, so you can use the same language to plug into (hopefully?) all the other GNU applications on your system. It's a cool idea. -- 1: https://github.com/tweag/nickel/pull/405 https://github.com/tweag/nickel/pull/405 2: https://www.tweag.io/blog/2024-05-16-nickel-programmable-lsp/ https://www.tweag.io/blog/2024-05-16-nickel-programmable-lsp... 3: https://github.com/tweag/nickel/issues/1585 https://github.com/tweag/nickel/issues/1585 4: https://cuelang.org/docs/reference/command/ https://cuelang.org/docs/reference/command/ 5: https://github.com/cue-lang/cue/issues/142 https://github.com/cue-lang/cue/issues/142
- everforward 2y agoI've hit this point a few times, and I always advocate for cutting the losses and switching to embedding an existing interpreter. Lua and Lisp both have embeddings for most popular languages, Starlark is Python-y and embeddable, many languages can embed a Javascript engine. Imo, users asking for stuff like loops is a signal that they want a real language and not a bolted-on DSL. I've seen far too many cases of "extending a DSL until it's a full-flegdged but awkward and horrible language". Ansible's YAML and Terraform's are my prime examples. Both have grown into basically full languages featuring imports, for loops, etc, and both suck to use because of how awkward reaching for them is. I don't want to have to remember Ansible's bastardized, YAML-encoded for loop syntax, just let me use a Python for loop.
- jolt42 2y agoPersonally I've seen 9pm be a holy grail, an exceedingly advantageous position to be in. But, it's exceedingly challenging to figure out what that DSL should and should not have as well.
- sjducb 2y ago> and life is pain So glad you finished with this. Right now I’m working with a guy who wants to write an interpreter…
- emseetech 2y agoWriting an interpreter for a project that isn't explicitly an interpreter is a code smell equivalent to microwaving fish. It is an incredibly fun side project though.
- bunderbunder 2y agoI think this really depends a lot on the task as well as how you frame it. A whole lot of the "parse, don't validate" ethos is potentially about handling incoming data with complicated formats by writing a minimalist interpreter that reads the input and outputs either guaranteed-valid domain objects or error messages. Most the time you can get away with just a parser, but one occasionally runs into more complex formats that are easier to handle if you think of them as a declarative language that you handle using general-purpose interpreter implementation patterns. And it's just not a wheel worth reinventing. The "very clever JSON" example the parent poster gives isn't particularly farfetched. The description reminds me of, for example, every Jetbrains REST API I've ever had to interact with. Also the data file formats for some commercial applications I've had to interact with. I don't like formats like this, but when I'm stuck with them, choosing the most maintainable option from the choices I've been left with is not a code smell; it's just sound engineering.
- diggan 2y ago> Right now I’m working with a guy who wants to write an interpreter… Stuff like that works out great if you have a static set of features that will never change and you can at one point say "it's done", and won't have to touch it again. Problem is that features are almost never static across their lifetime, and some poor sucker has to modify it at one point.
- __s 2y agoSkip straight to Lua
- anonymoushn 2y agoI'm pretty happy with "json scripting" for an implementation[0] of card game[1] with relatively low rules complexity. For a time it could evaluate arithmetic expressions, but I got rid of that because it was a bit unwieldy. The main pain point is that it runs slower than I'd like, so I may end up porting it all to actual Javascript functions or to Zig. [0]: https://github.com/sharpobject/yisim/blob/master/swogi.json https://github.com/sharpobject/yisim/blob/master/swogi.json [1]: https://store.steampowered.com/app/1948800/Yi_Xian_The_Cultivation_Card_Game/ https://store.steampowered.com/app/1948800/Yi_Xian_The_Culti...
- tombert 2y agoThe first time I did this, I ended up writing stuff that could use replace strings with regular expressions, do arithmetic, string concatenation, and a few other things. It was honestly kind of cool but it was horrible to maintain and I wish I had just kept the JSON as data.
- axegon_ 2y agoLikewise, I've done that more times than I'd like to admit. And I took it a step further with an LLM like a month and a half ago fenced behind a number of json and yaml instructions, containing conditions and validators. It works like a charm but yeah... Oops, I did it again.
- 082349872349872 2y ago> Oops, I did it again I played with `eval`, got lost in codegen: O(AB), maybe?
- CyberDildonics 2y agoLots of people learn the same lesson at some price. Data and execution are two separate things and should remain as separate as possible. The reason is that once you mix data and execution, suddenly you don't know what your data is until you execute it. Then you can only deal with it in the context of whatever tools you write and you can never just look at it straight. On the other hand now execution depends on lots of data and is no longer modular or general/generic and so becomes a one off solution somewhere. At some point everyone has the "what if" idea and hopefully it only burns them instead of lots of other people through poor design.
- renox 2y agoNice theory, how do you do in the real world where you have a 'data' whose value depends on the OS used?
- CyberDildonics 2y agoWhat are you talking about exactly? This thread is about static data files. Did you think I was saying a program shouldn't have variables?
- rqtwteye 2y agoThis happened to me with an XML based rules engine I wrote. First I needed conditions, then I introduced variables, then loops, then if-then-else. When I needed to handle errors, I realized that I just had invented something like BASIC in XML format. The interpreter was surprisingly short and concise mainly because XML ensured I didn't have to do the parsing. Switched to dynamically compiled C# eventually.