{"id":20425260,"url":"https://github.com/catseye/vinegar","last_synced_at":"2025-04-12T18:55:13.178Z","repository":{"id":44458764,"uuid":"414376378","full_name":"catseye/Vinegar","owner":"catseye","description":"MIRROR of https://codeberg.org/catseye/Vinegar : A semi-concatenative language where every operation can fail","archived":false,"fork":false,"pushed_at":"2023-11-03T16:23:53.000Z","size":44,"stargazers_count":12,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-26T13:11:51.394Z","etag":null,"topics":["concatenative","esolang","esoteric-language","esoteric-programming-language","experimental-language","failure-handling","semi-concatenative"],"latest_commit_sha":null,"homepage":"https://catseye.tc/node/Vinegar","language":"Python","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/catseye.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2021-10-06T21:36:21.000Z","updated_at":"2024-11-07T17:44:57.000Z","dependencies_parsed_at":"2022-08-30T09:22:37.044Z","dependency_job_id":"eb62ede4-a19c-4da8-ba5f-5d21cd13aed6","html_url":"https://github.com/catseye/Vinegar","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/catseye%2FVinegar","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/catseye%2FVinegar/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/catseye%2FVinegar/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/catseye%2FVinegar/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/catseye","download_url":"https://codeload.github.com/catseye/Vinegar/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248618262,"owners_count":21134200,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["concatenative","esolang","esoteric-language","esoteric-programming-language","experimental-language","failure-handling","semi-concatenative"],"created_at":"2024-11-15T07:12:43.528Z","updated_at":"2025-04-12T18:55:13.151Z","avatar_url":"https://github.com/catseye.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"Vinegar\n=======\n\nVersion 0.1\n| _See also:_ [Tandem](https://codeberg.org/catseye/Tandem#tandem)\n∘ [Tamsin](https://codeberg.org/catseye/Tamsin#tamsin)\n\n- - - -\n\n### Overview\n\n**Vinegar** is a semi-concatenative language where every operation can fail.\n\n### Whence \"semi-concatenative\"?\n\nWell, a concatenative language has a single binary operator with which one can\ncompose larger operations out of smaller operations, and that operator is\nrepresented by no explicit symbol but rather just the juxtaposition\n(concatenation) of operations.\n\nThe operations themselves are often functions that take stacks to stacks,\nand the binary operator usually works rather a lot like function composition.\n\nAnd since it's the only binary operator, and it's associative,\nthere are no parentheses, because there is no need for them.\n\nVinegar, in comparison, also has operations that work on stacks, and\nan implicit binary operator that works a lot like function composition.\nBut it has another binary operator too, called _alternation_.  This one\nis not implicit -- it's notated `|` and has a lower precedence than\nconcatenation -- and parentheses are available to group expressions.\n\nThis is not strictly speaking a concatenative language anymore, so we\ncall it \"semi-concatenative\".\n\n(But later on we'll consider a variation on how things are arranged\nhere that can make it all look\n\"[a bit more concatenative](#a-bit-more-concatenative)\" again.)\n\n### Whence \"every operation can fail\"?\n\nThis should be fairly intuitive -- when it is not possible for an\noperation to produce a sensible, expected result, it has failed.\n\nDivision is the classic example, where division by zero is undefined,\nand thus has failed.  But even addition can fail, if you consider\noverflow of the machine integer size to be a failure.\n\nAnd in a dynamically-typed stack-based language, such as concatenative\nlanguages often are, any operation could potentially underflow the stack.\nWhich would be, y'know.  A failure.\n\nIn Vinegar, every operation can fail.  When faced with operations\nthat seem like they always succeed, we sometimes contrive them so that\nthere are ways that they can fail.  And in cases where we cannot be\nbothered to do that, we just pretend there is a theoretical possibility\nthat they could fail.\n\nThe relevant question is, what happens when an operation fails?\n\nWell, that's what `|` is for.\n\nThe next section attempts to describe the semantics of concatenation\nand alternation in more detail.\n\n### Concatenation and alternation\n\nThe rules of the concatenation operator are:\n\nIf, in `a b`, `a` fails, then `b` is not performed, and `a b`\nfails with the error result produced by `a` failing.\n\nIf, in `a b`, `a` succeeds but `b` fails, then `a b`\nfails with the error result produced by `b` failing.\n\nIf, in `a b`, `a` succeeds, then `b` succeeds, then `a b`\nsucceeds with the succesful result of `b` (which built\non the successful result of `a`).\n\nThe rules for the alternation operator are:\n\nIf, in `a | b`, `a` succeeds, then `b` is not performed, and\n`a | b` succeeds with the result successfully produced by `a`.\n\nIf, in `a | b`, `a` fails, then `b` is performed, and the\nresult of `a | b` is the result of `b`.\n\nIf, in `a | b`, `a` fails, then `b` fails, then `a | b`\nfails with the error result produced by `b` failing.\n\nIt's worth noting that both concatenation and alternation\nare associative:\n\n    (a b) c = a (b c) = a b c\n\nand\n\n    (a | b) | c = a | (b | c) = a | b | c\n\n### Similar things\n\nIt's all a bit like `MonadPlus` in Haskell, with concatenation\nbeing a lot like `\u003e=\u003e`, although I really had `Either` more\nin mind than `Maybe`, but `Either` isn't an instance of\n`MonadPlus` for technical reasons, and I don't understand\nmonads anyway I'm sure.  I'd explain further, but it's strictly\ntaboo -- I might start babbling about burritos, you see.\n\nProbably a more familiar thing that it's similar to is the\nBourne shell.  If `a` and `b` are executables, then `a \u0026\u0026 b`\nexecutes `a` and checks the error code.  If the error code is\nnon-zero, it exits with that exit code, otherwise it executes\n`b` and exits with its exit code.  Alternately, `a || b`\nexecutes `a` and checks the error code.  If the error code is\n_zero_, it exits with that exit code, otherwise it executes\n`b` and exits with its exit code.\n\n### The practical upshot of all this\n\nWith the alternation operator, we can implement exception handling.\nIn something like `a b c | d`, the `d` will be executed if `a`\nfails, or if `b` fails, or if `c` fails.  Failing is like \"throw\"\nand `|` is like \"catch\".\n\nBut here is another twist.  We can _also_ use alternation to\nimplement plain old conditional execution.  Instead of checking _if_\nan expression equals some value, we _assert_ that it _does_\nequal some value.  If we happen to be wrong, then that is a failure.\nWe handle it on the RHS of a `|` just like we would handle any\nother failure.  Rather than putting it in an `else` clause.\n\nAs a bonus, a chain like `a | b | c | d` is a terse\nsubstitute for a chain of `elsif`s.\n\nSo what is the actual difference between these two language features?\nWell, failure happens for a reason.  Sometimes you have enough\ninformation to predict exactly what that reason would be, so you\ndon't really care about it, and your language construct doesn't\nprovide it (`if`, `Maybe`).  Other times, you don't have enough\ninformation to predict it, so you do want your language construct\nto provide it, so that you can work with it (`catch`, `Either`).\n\nHere we observe that, if we can pick only one of these alternatives,\nit's better to be provided with the reason for failure than to be\nnot provided with it, because we can't obtain it otherwise, and\nif we really don't want it, we can always throw it out.\n\nSo in Vinegar, the RHS of an alternation always begins executing\nwith the failure value that caused the RHS to execute pushed onto\nthe top of the stack.\n\n### Example: Factorial in Vinegar\n\nAs an example, let's try to write a factorial function in Vinegar.\n\nWell, first, let's get some preliminaries out of the way.  Up 'til\nnow we've been fairly vague about the actual language.  Let's pin\ndown some concrete syntax.\n\n    -\u003e Tests for functionality \"Execute Vinegar Program\"\n\n    -\u003e Functionality \"Execute Vinegar Program\" is implemented by\n    -\u003e shell command\n    -\u003e \"python3 bin/vinegar \u003c%(test-body-file)\"\n\nEach definition is on its own line, which is terminated by\na semicolon.  The result of executing a program, is the result\nof executing `main`.  The form `int[n]` where _n_ is a literal\ninteger in decimal notation, pushes _n_ onto the stack.\n\n    main = other;\n    other = int[3];\n    ==\u003e OK([3])\n\nWhy, you may ask, is there all this square brackets and stuff\naround simple literal values?  Ah!  That's so that literals can\nfail!  If it's not possible to parse the contents of the\n`[...]` part into a valid constant value, it has failed!\n\n    main = int[lEEt];\n    ==\u003e Failure(invalid literal for int() with base 10: 'lEEt')\n\nThere is a built-in operation to swap the top two values on the stack.\n\n    main = int[100] int[200] swap;\n    ==\u003e OK([200, 100])\n\nThere is a built-in operation to pop the topmost value off the stack and\ndiscard it.\n\n    main = int[40] int[50] pop int[60];\n    ==\u003e OK([40, 60])\n\nThere are some usual arithmetic operations too.\n\n    main = int[4] int[5] mul int[6] sub;\n    ==\u003e OK([14])\n\nIf there are not enough values on the stack for an operation, it fails\nwith underflow.\n\n    main = swap;\n    ==\u003e Failure(underflow)\n\nThere is a built-in operation to pop the topmost two values and assert\nthat they are equal.\n\n    main = int[5] int[5] eq!;\n    ==\u003e OK([])\n\n    main = int[5] int[8] eq!;\n    ==\u003e Failure(unequal)\n\nThere is a built-in operation to pop the topmost two values and assert\nthat the second is greater than the first.\n\n    main = int[5] int[5] gt!;\n    ==\u003e Failure(not greater than)\n\n    main = int[5] int[8] gt!;\n    ==\u003e Failure(not greater than)\n\n    main = int[8] int[5] gt!;\n    ==\u003e OK([])\n\nOK, _now_ let's try to write a factorial function in Vinegar.\n\n### That factorial function in full\n\n    fact = dup int[1] gt! dup int[1] sub fact mul | pop;\n    main = int[5] fact;\n    ==\u003e OK([120])\n\nWhat we have here is:\n\nTake the first \"argument\" to the `fact` function (with `dup`)\nand assert (with `gt!`) that it is greater than 1.  Then take that\nargument again (`dup` it again) and subtract one from it,\nget the `fact` of that value, and multiply the argument\n(itself - no `dup` this time) by that result.  And leave that\non the stack, as the result value.\n\nIf any operation there failed, we just do nothing (we take\nthe failure value off the stack with `pop` and discard it.)\nSo for example, if our assertion that the argument was\ngreater than 1 failed, it will just leave the argument on\nthe stack, as the result value.\n\nThat's not actually fantastic.  What if `mul` failed?  In\nthat case we probably don't want to return whatever working\ngarbage happens to be on the stack as our result.  Rather,\nwe want to fail too.  So maybe we can write this more\npointifically.\n\n    fact = dup int[1] eq! | pop dup int[1] sub fact mul;\n    main = int[5] fact;\n    ==\u003e OK([120])\n\nNow, we take the argument and assert that it *is* 1.  If\nit is, we just return it.  If not, we compute factorial\non it, and return that.  If anything in our factorial\ncomputation fails, `fact` itself fails, which is desirable.\n\nOf course, this still breaks down if we're passed an\nargument that is zero or negative.  What's the factorial\nof such a number?  Let's say, for the sake of argument,\nit's considered an error.  We can add that as an assertion.\n\n    fact = dup int[0] gt! (dup int[1] eq! | pop dup int[1] sub fact mul);\n    main = int[5] fact;\n    ==\u003e OK([120])\n\nIf it's 0 or negative, the `gt!` assertion fails, and\nthe whole thing fails.  If it's 1, the `eq!` assertion\ndoesn't fail, and 1 is returned.  If it's greater than\n1, the `eq!` assertion fails and factorial is computed.\n\nNote that we do need parentheses here so that when the\n`gt!` fails, all of `fact` fails, instead of it falling\nback to the operation on the RHS of the `|`.\n\n### A bit more concatenative\n\nFans of concatenative languages (concatenativophiles?\nconcatenativesters? concatenativeniks?) will no doubt\nbe disappointed at the appearance of an explicit binary\noperator, and even *parentheses* (ugh), in this language.\n\nBut if we take some frightful measures, we may put some\ndistance between us and these pedestrian things.\n\nIf we want our binary operator to be notated by mere\njuxtaposition, we obviously cannot have more than one\nbinary operator.  Yet here, in this language, we have two.\n\nWe surmount this obstacle by noting that, although\nwe have two binary operators in the *language*, we can\nrestrict each *definition* to having only one kind of\noperator, and have multiple kinds of *definitions*.\nIn some definitions, concatenation is implicit; in\nother definitions, alternation is implicit.\n\nTo be precise: A definition in which concatenation is implicit\nis introduced with `=\u0026`.  A definition in which alternation is\nimplicit is introduced with `=|`.\n\nArmed with these facts, we can rewrite the above definition of\n`fact` without infix operators and without parentheses\nusing multiple definitions, in this way:\n\n    fact =\u0026 fac1 fac2;\n    fac1 =\u0026 dup int[0] gt!;\n    fac2 =| fac3 fac4;\n    fac3 =\u0026 dup int[1] eq!;\n    fac4 =\u0026 pop dup int[1] sub fact mul;\n    main =\u0026 int[5] fact;\n    ==\u003e OK([120])\n\nThere!  \u003cs\u003eAre you happy now?\u003c/s\u003e  Don't that just beat all?\nIn light of this stellar feature it is expected that serious\nprogrammers would treat the plain `=` form of definition as\na kind of [wimpmode](https://esolangs.org/wiki/Wimpmode) and shun it.\n\n### Further work\n\nWhat we have here is probably sufficient for a version 0.1 release\nof the language -- it demonstrates most of the things I wanted to\ndemonstrate with this idea.  Still, it leaves a lot to be desired.\n\nIn particular, what *is* a Failure object?  In the current\nimplementation it contains a string, which is supposed to be\nthe reason that the failure happened.  Certainly, one could compare\ntwo Failures for equality, based on this failure message.  But how\nuseful or interesting is that, actually?  So it is not implemented.\n\nI think what a Failure *should* be is, not just a message, but a\nrepresentation of the program state when the failure occured.\nShould it be the entire program state?  I'm not sure.\n\nCertainly, real-life exceptions usually include a backtrace, which\nlists all the calls that were in effect when the failure occurred.\n\nIf a Failure were that, then a Failure would be a kind of list.\nAnd there hasn't been much thought into what kinds of values can\nactually exist on the Vinegar stack.  Can a list exist there?\nThere is a certain urge to disallow that in the name of simplifying\nmemory management -- the Forth school, sort of.  But, having lists\non the stack, and being able to manipulate them, would be very\npowerful; and having them unavoidably be Failures as well, would be\nrather esoteric.\n\nCan you `raise` a Failure once you have one on the stack?  You ought\nto, but it raises a vaguely philosophical question: since `raise` can\nfail (all operations can fail in Vinegar), how do you know when `raise`\nsucceeded?  How do you know that it actually re-raised a\n`Failure(underflow)` it had on the stack, versus that there was nothing\non the stack and it actually underflowed?  Maybe you don't, maybe you\ngive up on that epistemological question.\n\nIf a Failure represents the state of the program when it failed, is\nit a continuation?  Can you continue it where it left off, with something\nchanged, to try to make it avoid the failure this time?  Why, wouldn't\nthis be a neat way to implement backtracking search?  If you could work\nout how to make it work neatly, I mean.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcatseye%2Fvinegar","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcatseye%2Fvinegar","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcatseye%2Fvinegar/lists"}