{"id":20425373,"url":"https://github.com/catseye/wanda","last_synced_at":"2025-07-07T06:33:03.376Z","repository":{"id":142240026,"uuid":"172886531","full_name":"catseye/Wanda","owner":"catseye","description":"MIRROR of https://codeberg.org/catseye/Wanda : A little \"concatenative\" language that's not actually concatenative at all","archived":false,"fork":false,"pushed_at":"2023-11-09T17:48:52.000Z","size":58,"stargazers_count":8,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-12T18:57:55.436Z","etag":null,"topics":["concatenative-language","esolang","esoteric-language","esoteric-programming-language","not-actually-concatenative","not-actually-stack-based","stack-based","string-rewriting"],"latest_commit_sha":null,"homepage":"https://catseye.tc/node/Wanda","language":"Lua","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,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2019-02-27T09:39:07.000Z","updated_at":"2025-02-12T16:06:04.000Z","dependencies_parsed_at":"2025-04-12T18:55:38.357Z","dependency_job_id":"748a8d3e-7d3a-45f6-a3d1-098c3b5c13c6","html_url":"https://github.com/catseye/Wanda","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/catseye/Wanda","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/catseye%2FWanda","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/catseye%2FWanda/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/catseye%2FWanda/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/catseye%2FWanda/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/catseye","download_url":"https://codeload.github.com/catseye/Wanda/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/catseye%2FWanda/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264027575,"owners_count":23546102,"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-language","esolang","esoteric-language","esoteric-programming-language","not-actually-concatenative","not-actually-stack-based","stack-based","string-rewriting"],"created_at":"2024-11-15T07:13:06.094Z","updated_at":"2025-07-07T06:33:03.363Z","avatar_url":"https://github.com/catseye.png","language":"Lua","funding_links":[],"categories":[],"sub_categories":[],"readme":"Wanda\n=====\n\nWanda is a Forth-like, \"concatenative\" programming language that's arguably\nnot concatenative at all, nor even \"stack-based\", because it's based on a\nstring-rewriting semantics.\n\nThe remainder of this document will describe the language and will attempt\nto justify the above statement.\n\nBasics\n------\n\n    -\u003e Tests for functionality \"Run Wanda program\"\n\nA Wanda program is a string of symbols.  Each symbol consists of one or more\nnon-whitespace characters.  In the string, symbols are separated by whitespace.\n\nHere is a legal Wanda program.  (The `===\u003e` is not part of the program;\nit only shows the expected result of running it.)\n\n    $ 2 3 + 4 *\n    ===\u003e 20 $\n\nEvaluation happens by successively rewriting parts of this string of symbols.\nFor example, in the above,\n\n*   `$ 2` is rewritten into `2 $`\n*   `$ 3` is then rewritten into `3 $`\n*   `2 3 $ +` is then rewritten into `5 $`\n*   `$ 4` is then rewritten into `4 $`\n*   finally, `5 4 $ *` is rewritten into `20 $`.\n\nRewrites occur when parts of the string match the pattern of one of the\nrewrite rules in effect.  For instance, the rule for `+` has the pattern\n`X Y $ +`, where X and Y will match any integer symbols; the part of the\nstring that matches this pattern is replaced by a single integer symbol which\nis the sum of X and Y, followed by a `$`.\n\nYou can think of `$` as a symbol that delineates the stack (on the left)\nfrom the program (on the right).  When constants are encountered in the\nprogram, they are pushed onto the stack.\n\nBut if you do think of it this way, bear in mind that it is only a\nconvenient illusion.  For, despite looking like and evaluating like a Forth\nprogram, there is no \"stack\" that is distinct from the program — it's\nall just a string that gets rewritten.  `2` is neither an element on the\nstack, nor an instruction that pushes the value 2 onto the stack; it's just\na `2`.\n\nIndeed, observe that, if no patterns match anywhere in the string, the\nexpression remains unchanged and evaluation terminates:\n\n    2 $ +\n    ===\u003e 2 $ +\n\n### Some other builtins\n\nWe've seen `+` and `*`, which are built-in functions (or rules).\nThere are a handful of other built-in functions (or rules).\n\n    $ 7 sgn 0 sgn -14 sgn\n    ===\u003e 1 0 -1 $\n\n    5 4 $ pop\n    ===\u003e 5 $\n\n    4 $ dup\n    ===\u003e 4 4 $\n\nDefining functions\n------------------\n\nWanda supports a special form for defining functions, which is very similar to\nForth's `:` ... `;` block.  The main difference is that there is a `-\u003e` symbol\ninside it, which you can think of as a way to making it explicit where the\nfunction naming ends and the function definition begins.\n\n    4 10 $\n    : $ perim -\u003e $ + 2 * ;\n    perim\n    ===\u003e 28 $\n\nYou can in fact think of this special form as something that gets rewritten\ninto nothingness (a zero-length string) and which introduces a new rule as a\nside effect.  The new rule matches the function naming (in this case `$ perim`)\nand replaces it with its definition (in this case `$ + 2 *`), like so:\n\n    4 10 $ + 2 *\n\n(And then evaluation continues as usual to obtain the final result.)\n\nSome things to note:\n\nThis special form only gets rewritten when it appears immediately to the\nright of a `$`.\n\n    : $ foo -\u003e $ ; $ 1 2 +\n    ===\u003e : $ foo -\u003e $ ; 3 $\n\nSo, you can think of this special form as something that is \"executed\"\nin the same way the builtins we've described above are.\n\nRules defined this way are applied in the order in which they were defined.\nYou can think of this as functions being redefined.\n\n    $\n    : $ ten -\u003e $ 10 ;\n    ten\n    : $ ten -\u003e $ 11 ;\n    ten\n    ===\u003e 10 11 $\n\n### Derivable operations\n\nWe can define functions for some common operations seen in other Forth-like\nlanguages, by deriving them from the built-in operations.\n\n    $\n    : $ abs -\u003e $ dup sgn * ;\n    7 abs 0 abs -14 abs\n    ===\u003e 7 0 14 $\n\n    $\n    : $ abs -\u003e $ dup sgn * ;\n    : $ not -\u003e $ sgn abs 1 - abs ;\n    0 not 1 not -1 not 999 not -999 not\n    ===\u003e 1 0 0 0 0 $\n\n    $\n    : $ abs -\u003e $ dup sgn * ;\n    : $ not -\u003e $ sgn abs 1 - abs ;\n    : $ eq? -\u003e $ - not ;\n    14 14 eq? 9 8 eq? -100 100 eq?\n    ===\u003e 1 0 0 $\n\n    $\n    : $ abs -\u003e $ dup sgn * ;\n    : $ not -\u003e $ sgn abs 1 - abs ;\n    : $ eq? -\u003e $ - not ;\n    : $ gt? -\u003e $ - sgn 1 eq? ;\n    5 4 gt? 5 5 gt? 5 6 gt?\n    ===\u003e 1 0 0 $\n\nRecursion\n---------\n\nIf we include the name of the function in its own definition, recursion ought\nto happen.  And indeed, it does.  For example if we said\n\n    : $ fact -\u003e $ dup 1 - fact * ;\n\nthen\n\n    3 $ fact\n\nwould rewrite to\n\n    3 $ dup 1 - fact *\n\nwhich is fine, the next `fact` will get rewritten the same way in due course,\nall fine except for the troublesome matter of it never terminating because we\nhaven't given a base case.  Viewing the trace of execution for the first few\nsteps makes this clear:\n\n    -\u003e Tests for functionality \"Trace Wanda program\"\n\n    3 $\n    : $ fact -\u003e $ dup 1 - fact * ;\n    fact\n    ===\u003e 3 $ fact\n    ===\u003e 3 $ dup 1 - fact *\n    ===\u003e 3 3 $ 1 - fact *\n    ===\u003e 3 3 1 $ - fact *\n    ===\u003e 3 2 $ fact *\n    ===\u003e 3 2 $ dup 1 - fact * *\n    ===\u003e 3 2 2 $ 1 - fact * *\n    ===\u003e 3 2 2 1 $ - fact * *\n    ===\u003e 3 2 1 $ fact * *\n    ===\u003e 3 2 1 $ dup 1 - fact * * *\n    ===\u003e 3 2 1 1 $ 1 - fact * * *\n    ===\u003e 3 2 1 1 1 $ - fact * * *\n    ===\u003e 3 2 1 0 $ fact * * *\n    ===\u003e 3 2 1 0 $ dup 1 - fact * * * *\n    ===\u003e 3 2 1 0 0 $ 1 - fact * * * *\n\n    -\u003e Tests for functionality \"Run Wanda program\"\n\nWhat would be great would be some way for `0 fact` to be immediately rewritten\ninto `1` instead of recursing.\n\nWell, this is what the extra `-\u003e` is for in a `:` ... `;` block — so that we\ncan specify both the pattern and the replacement.  So, if we say\n\n    : 0 $ fact -\u003e $ 1 ;\n\nwe have defined a rule which matches `0 $ fact` and replaces it with `$ 1`\n(which will immediately be rewritten to `1 $`).  Thus the recursion will\nnow terminate.\n\n    $\n    : 0 $ fact -\u003e $ 1 ;\n    : $ fact -\u003e $ dup 1 - fact * ;\n    5 fact\n    ===\u003e 120 $\n\nAt first blush it may seem like the order of rule application matters in\nthe above, but in fact it does not:\n\n    $\n    : $ fact -\u003e $ dup 1 - fact * ;\n    : 0 $ fact -\u003e $ 1 ;\n    5 fact\n    ===\u003e 120 $\n\nThis is because the string is searched left-to-right for the first match,\nand if the string contains `0 fact`, this will always match a pattern of\n`0 fact` before we're even in a position to check the parts of the string\nto the right of the `0` that would match the pattern `fact`.\n\nComputational class\n-------------------\n\nWe can ask ourselves: if we stop here, what kinds of things can we compute\nwith what we have so far?\n\nWell, we have a first-in-first-out stack discipline, and it's well-known\nthat if you have a strict stack discipline you have a push-down automaton,\nnot a Turing machine.\n\nIf we had unbounded integers, and a division operation or `swap`, we might\nbe able to make a 1- or 2-counter [Minsky machine][].  But we don't have\nthose operations, and I haven't said anything about the boundedness of\nintegers yet.\n\nAnd anyway, that all assumes this is a traditional stack-based language,\nwhich it's not!  It's a string-rewriting language, and it naturally has\naccess to the deep parts of the stack, because it goes and looks for\npatterns in them.\n\nIn fact, from this viewpoint, the language looks a lot like a deterministic\nversion of [Thue][].  Every time we define a function like\n\n    : $ not -\u003e $ sgn abs 1 - abs ;\n\nit's not unlike defining a rule in Thue like\n\n    $N::=$SA1-A\n\nAnd Thue is Turing-complete, and the additional determinism isn't an\nimpediment to what it can compute — a program which is written to\naccomodate an unspecified rewriting order (as Thue programs generally\nare) can be written to work the same way when the order is specified and\nfixed.\n\nSo, if we were to leave the language as it is so far, we could conclude\nit's Turing-complete.  Which is great, but also somewhat unsatisfying.\nI'd like for Wanda to be more than just a Thue-in-Forth's-clothing.\n\nSo to make it more interesting, let's intentionally restrict the\nlanguage so that we can't easily map programs to Thue programs.\n\nWe could say we have unbounded integers, but I don't think that helps\n(at least not without some other twist(s) that I don't see offhand)\nbecause you can just embed a finite alphabet a la Thue in your unbounded\nalphabet of integers.\n\nBut what if we place restrictions on the function definitions?\n\nSpecifically, let's say every rewrite rule must contain exactly one `$`\non the left and exactly one `$` on the right.\n\nThis might seem to do the trick: you can now rewrite the string in\nonly one place: around the leftmost `$`.  That's a pretty big impediment.\n\nConcretely, let's say that if you actually violate this constraint when\ndefining a function, the Wanda implementation may flag up some kind of\nwarning, but at any rate, it will erase the special form, but it not\nintroduce any new rules.\n\n    $\n    : $ ten -\u003e 10 ;\n    ten\n    ===\u003e $ ten\n\n    $\n    : ten -\u003e $ 10 ;\n    ten\n    ===\u003e $ ten\n\n    $\n    : ten -\u003e 10 ;\n    ten\n    ===\u003e $ ten\n\n    $\n    : $ $ ten -\u003e $ 10 ;\n    ten\n    ===\u003e $ ten\n\n    $\n    : $ ten -\u003e $ $ 10 ;\n    ten\n    ===\u003e $ ten\n\nBut this isn't quite enough, because you can add rules that move the `$`\naround in the string.  If you want to rewrite some other part of the\nstring, you can just add some rules that move the `$` there first.\n\nSo we'll make the restriction even stronger: on the right-hand side\n(but not necessarily the left-hand side), the single `$` must always\nappear as the *leftmost* symbol.\n\n    $\n    : $ ten -\u003e dix $ ;\n    ten\n    ===\u003e $ ten\n\n    $\n    : 10 $ ten -\u003e $ dix ;\n    10 ten\n    ===\u003e $ dix\n\nAnyway, the point is, this prevents us from ever writing a rule that moves\nthe `$` to the right.  And so this prevents us from arbitrarily moving the\n`$` around, which prevents us from being able to rewrite arbitrary parts\nof the string, which prevents it being Turing-complete in the way Thue is.\nBut it continues to be able to express all the functions we've shown so far.\n\nBut what of the built-in functions?  It's true that they allow us to\nmove some information from the right of the `$` in the string to the left.\n`$ 10`, for example, rewrites to `10 $`.  But each of these can only move\na *bounded* amount of information, and this prevents us from getting to\narbitrary parts of the string and rewriting them.\n\nIn fact, I _think_ that this limits the kinds of rewrites that can be\nundertaken in exactly the same way a strict stack discipline does, i.e. it\ncan only compute what a push-down automaton can compute.\n\nBut I have not got a proof of that.  It may turn out, in fact, that even\nwith these restrictions, the language is Turing-complete, due to something\nI've missed.\n\nSo, I'll hedge a bit, and I'll describe the feature that will be added in the\nnext section like so: it makes Wanda Turing-complete, even if the language\nwe've described so far already is.\n\nConcrete Shoes\n--------------\n\nLet's introduce some built-in rules that allow us to manipulate values\nat the left end of the string, i.e. deep in the \"stack\".\n\nIn fact, since we're imagining part of this string is a \"stack\" anyway,\nwe might as well go further and imagine it's a body of water.\n\nTo store a value on the left end of the string, what we'll do is\n\"tie a weight\" to it and let it \"sink\" to the bottom.\n\nWe should make \"the bottom\" explicit, as well.  It will be the `)` symbol.\n\n    ) 1 2 3 4 5 $ 99 sink\n    ===\u003e ) 99 1 2 3 4 5 $\n\nIt might be illustrative to show the trace of this.\n\n    -\u003e Tests for functionality \"Trace Wanda program\"\n\n    ) 1 2 3 4 5 $ 99 sink\n    ===\u003e ) 1 2 3 4 $ 99 sink 5\n    ===\u003e ) 1 2 3 $ 99 sink 4 5\n    ===\u003e ) 1 2 $ 99 sink 3 4 5\n    ===\u003e ) 1 $ 99 sink 2 3 4 5\n    ===\u003e ) $ 99 sink 1 2 3 4 5\n    ===\u003e ) $ 99 1 2 3 4 5\n    ===\u003e ) 99 $ 1 2 3 4 5\n    ===\u003e ) 99 1 $ 2 3 4 5\n    ===\u003e ) 99 1 2 $ 3 4 5\n    ===\u003e ) 99 1 2 3 $ 4 5\n    ===\u003e ) 99 1 2 3 4 $ 5\n    ===\u003e ) 99 1 2 3 4 5 $\n\nNote that after the value has \"sunk\", the `$` will \"bubble up\" all\nby itself, assuming the values on the stack are integers.\n\nIt should now be straightforward to construct a [Tag system][] in Wanda,\nby matching patterns at the top (i.e. at the right edge of the string),\nand, upon a successful match, \"sinking\" new values to the bottom\n(the left edge of string).  And because Tag systems are Turing-complete\nand Wanda can simulate any Tag system, Wanda is Turing-complete.\n\nHistory\n-------\n\nWanda was originally conceived in 2009 (I distinctly remember implementing\nthe idea, in Haskell, on a laptop in a laundromat in Seattle), but it wasn't\nas developed as what you see here; the idea that a Forth-like language could\nbe defined using string-rewriting semantics was there, but it didn't really\ncarry through with it.\n\nThere are probably several reasons for this.\n\nOne is that I thought it should have a right-to-left rewriting order.\nI don't remember my reason for that (if I actually had one).  It did not\nhave the distinguished `$` symbol, so this would have resulted in an\nodd (or at least unintuitive) order of evaluation, and I never really\nworked out the full implications of that.\n\nAnother is that, the way I was implementing it in Haskell, it would have\nbeen most natural to describe the reduction function with an [infinite type][].\nDiscovering that Haskell did not support that \"out of the box\" was\nsomewhat discouraging.  Now, of course, I realize that you can fake that\nsort of thing with Haskell's `newtype`, but at the time it wasn't obvious.\n\nDid it also seem like already-explored territory to me?  Perhaps; it\nfeels like I felt that way at some point.  I don't think I had encountered\n[Enchilada][] back then (I don't think I had even heard of \"concatenative\"\nlanguages at that time), but a year or two later, when I did learn there\nwas already a stack-based rewriting-based language out there, it may have\ndiscouraged me further.\n\nBut Enchilada is really not all that similar to Wanda, and the idea and\nthe desire to turn Wanda into a real (toy) language never really went away.\nSo here we are.\n\nFurther Work\n------------\n\nThere may be more features that might be productively added to the language,\nif we wanted more from it than just showing that it's Turing-complete.\n\nOne logical extension would be, since we are able to `sink` values to\nthe bottom of the stack, also be able to `float` them up from the bottom\nagain.  This would let the bottom of the stack be usable as a temporary\nstorage area, rather than just as a way to use the string as a queue.\n\nAnother thing that seems attractive is the possibility for creating new\nrules that are not written statically in the initial program.  (And\npossibly retracting existing rules too, but this seems less exciting.)\nBut I haven't worked out a way to do this yet that I really like.\n\nHappy nonconcatenativeing!  \nChris Pressey  \nLondon, England  \nFeb 27, 2019\n\n[Enchilada]: http://www.enchiladacode.nl/\n[Minsky machine]: https://esolangs.org/wiki/Minsky_machine\n[Thue]: https://esolangs.org/wiki/Thue\n[Tag system]: https://esolangs.org/wiki/Tag%20system\n[infinite type]: https://mail.haskell.org/pipermail/haskell-cafe/2006-December/020074.html\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcatseye%2Fwanda","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcatseye%2Fwanda","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcatseye%2Fwanda/lists"}