{"id":17174765,"url":"https://github.com/benhoyt/littlelang","last_synced_at":"2025-03-22T18:33:57.664Z","repository":{"id":56758742,"uuid":"114052644","full_name":"benhoyt/littlelang","owner":"benhoyt","description":"A little language interpreter written in Go","archived":false,"fork":false,"pushed_at":"2018-12-11T01:46:35.000Z","size":49,"stargazers_count":92,"open_issues_count":0,"forks_count":11,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-10-15T23:54:54.448Z","etag":null,"topics":["go","interpreter","language","parser","recursive-descent"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/benhoyt.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-12-13T00:11:56.000Z","updated_at":"2024-09-25T08:40:00.000Z","dependencies_parsed_at":"2022-08-16T01:50:52.848Z","dependency_job_id":null,"html_url":"https://github.com/benhoyt/littlelang","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benhoyt%2Flittlelang","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benhoyt%2Flittlelang/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benhoyt%2Flittlelang/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benhoyt%2Flittlelang/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/benhoyt","download_url":"https://codeload.github.com/benhoyt/littlelang/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":221832361,"owners_count":16888223,"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":["go","interpreter","language","parser","recursive-descent"],"created_at":"2024-10-14T23:54:51.938Z","updated_at":"2024-10-28T13:33:20.298Z","avatar_url":"https://github.com/benhoyt.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# A little language interpreter\n\nThe littlelang programming language is a little language (funny that) designed by Ben Hoyt for fun and (his own) learning. It's kind of a cross between Python, JavaScript, and Go. It's a dynamically but strongly-typed language with the usual data types, first-class functions, closures, and a bit more.\n\nThe code includes a tokenizer and parser and a (slowish but simple) tree-walk interpreter written in Go. There's also an [interpreter written in littlelang itself](https://github.com/benhoyt/littlelang/blob/master/littlelang.ll), just to prove the language is powerful enough to write somewhat real programs in.\n\nBelow are a couple of [examples](#some-little-examples) of the language, the full language [\"spec\"](#language-spec), and the littlelang [grammar](#grammar). However, you might be better off if you [**read my introduction first**](http://benhoyt.com/writings/littlelang/).\n\n\n## Some little examples\n\n```\n// Lists, the sort() builtin, and for loops\nlst = [\"foo\", \"a\", \"z\", \"B\"]\nsort(lst)\nprint(lst)\nsort(lst, lower)\nfor x in lst {\n    print(x)\n}\n// Output:\n// [\"B\", \"a\", \"foo\", \"z\"]\n// a\n// B\n// foo\n// z\n\n// A closure and first-class functions\nfunc make_adder(n) {\n    func adder(x) {\n        return x + n\n    }\n    return adder\n}\nadd5 = make_adder(5)\nprint(\"add5(3) =\", add5(3))\n// Output:\n// add5(3) = 8\n\n// A pseudo-class with \"methods\" using a closure\nfunc Person(name, age) {\n    self = {}\n    self.name = name\n    self.age = age\n    self.str = func() {\n        return self.name + \", aged \" + str(self.age)\n    }\n    return self\n}\np = Person(\"Bob\", 42)\nprint(p.str())\n// Output:\n// Bob, aged 42\n```\n\n\n## Language spec\n\nLittlelang's syntax is a cross between Go and Python. Like Go, it uses `func` to define functions (named or anonymous), requires `{` and `}` for blocks, and doesn't need semicolons. But like Python, it uses keywords for `and` and `or` and `in`. Like both those languages, it distinguishes expressions and statements.\n\nIt's dynamically typed and garbage collected, with the usual data types: nil, bool, int, str, list, map, and func. There are also several builtin functions.\n\nCalling this a \"spec\" is probably a bit grandiose, but it's the best you'll get.\n\n### Programs\n\nA littlelang program is simply zero or more statements. Statements don't actually have to be separated by newlines, only by whitespace. The following is a valid program (but you'd probably use newlines in the `if` block in real life):\n\n```\ns = \"world\"\nprint(\"Hello, \" + s)\nif s != \"\" { t = \"The end\"  print(t) }\n// Hello, world\n// The end\n```\n\nBetween tokens, whitespace and comments (`//` through to the end of a line) are ignored.\n\n### Types\n\nLittlelang has the following data types: nil, bool, int, str, list, map, and func. The int type is a signed 64-bit integer, strings are immutable arrays of bytes, lists are growable arrays (use the `append()` builtin), and maps are unordered hash tables. Trailing commas are allowed after the last element in a list or map:\n\nType      | Syntax                                    | Comments\n--------- | ----------------------------------------- | --------\nnil       | `nil`                                     |\nbool      | `true false`                              |\nint       | `0 42 1234 -5`                            | `-5` is actually `5` with unary `-`\nstr       | `\"\" \"foo\" \"\\\"quotes\\\" and a\\nline break\"` | Escapes: `\\\" \\\\ \\t \\r \\n`\nlist      | `[] [1, 2,] [1, 2, 3]`                    |\nmap       | `{} {\"a\": 1,} {\"a\": 1, \"b\": 2}`           |\n\n### If statements\n\nLittlelang supports `if`, `else if`, and `else`. You must use `{ ... }` braces around the blocks:\n\n```\na = 10\nif a \u003e 5 {\n    print(\"large\")\n} else if a \u003c 0 {\n    print(\"negative\")\n} else {\n    print(\"small\")\n}\n// large\n```\n\n### While loops\n\nWhile loops are very standard:\n\n```\ni = 3\nwhile i \u003e 0 {\n    print(i)\n    i = i - 1\n}\n// 3\n// 2\n// 1\n```\n\nLittlelang does not have `break` or `continue`, but you can `return value` as one way of breaking out of a loop early.\n\n### For loops\n\nFor loops are similar to Python's `for` loops and Go's `for range` loops. You can iterate through the (Unicode) characters in a string, elements in a list (the `range()` builtin returns a list), and keys in a map.\n\nNote that iteration order of a map is undefined -- create a list of keys and `sort()` if you need that.\n\n```\nfor c in \"foo\" {\n    print(c)\n}\n// f\n// o\n// o\n\nfor x in [nil, 3, \"z\"] {\n    print(x)\n}\n// nil\n// 3\n// z\n\nfor i in range(5) {\n    print(i, i*i)\n}\n// 0 0\n// 1 1\n// 2 4\n// 3 9\n// 4 16\n\nmap = {\"a\": 1, \"b\": 2}\nfor k in map {\n    print(k, map[k])\n}\n// a 1\n// b 2\n```\n\n### Functions and return\n\nYou can define named or anonymous functions, including functions inside functions that reference outer variables (closures). Vararg functions are supported with `...` syntax like in Go.\n\n```\nfunc add(a, b) {\n    return a + b\n}\nprint(add(3, 4))\n// 7\n\nfunc make_adder(n) {\n    func adder(x) {\n        return x + n\n    }\n    return adder\n}\nadd5 = make_adder(5)\nprint(add5(7))\n// 12\n\n// Anonymous function, equivalent to \"func plus(nums...)\"\nplus = func(nums...) {\n    sum = 0\n    for n in nums {\n        sum = sum + n\n    }\n    return sum\n}\nprint(plus(1, 2, 3))\nlst = [4, 5, 6]\nprint(plus(lst...))\n// 6\n// 15\n```\n\nA grammar note: you can't have a \"bare return\" -- it requires a return value. So if you don't want to return anything (functions always return at least nil anyway), just say `return nil`.\n\n### Assignment\n\nAssignment can assign to a name, a list element by index, or a map value by key. When assigning to a name (variable), it always assigns to the local function scope (like Python). You can't assign to an outer scope without using a mutable list or map (there's no `global` or `nonlocal` keyword).\n\nTo help with object-oriented programming, `obj.foo = bar` is syntactic sugar for `obj[\"foo\"] = bar`. They're exactly equivalent.\n\n```\ni = 1\nfunc nochange() {\n    i = 2\n    print(i)\n}\nprint(i)\nnochange()\nprint(i)\n// 1\n// 2\n// 1\n\nmap = {\"a\": 1}\nfunc change() {\n    map.a = 2\n    print(map.a)\n}\nprint(map.a)\nchange()\nprint(map.a)\n// 1\n// 2\n// 2\n\nlst = [0, 1, 2]\nlst[1] = \"one\"\nprint(lst)\n// [0, \"one\", 2]\n\nmap = {\"a\": 1, \"b\": 2}\nmap[\"a\"] = 3\nmap.c = 4\nprint(map)\n// {\"a\": 3, \"b\": 2, \"c\": 4}\n```\n\n### Binary and unary operators\n\nLittlelang supports pretty standard binary and unary operators. Here they are with their precedence, from highest to lowest (operators of the same precedence evaluate left to right):\n\nOperators      | Description\n-------------- | -----------\n`[]`           | Subscript\n`-`            | Unary minus\n`* / %`        | Multiplication\n`+ -`          | Addition\n`\u003c \u003c= \u003e \u003e= in` | Comparison\n`== !=`        | Equality\n`not`          | Logical not\n`and`          | Logical and (short-circuit)\n`or`           | Logical or (short-circuit)\n\nSeveral of the operators are overloaded. Here are the types they can operate on:\n\nOperator   | Types           | Action\n---------- | --------------- | ------\n`[]`       | `str[int]`      | fetch nth byte of str (0-based)\n`[]`       | `list[int]`     | fetch nth element of list (0-based)\n`[]`       | `map[str]`      | fetch map value by key str\n`-`        | `int`           | negate int\n`*`        | `int * int`     | multiply ints\n`*`        | `str * int`     | repeat str n times\n`*`        | `int * str`     | repeat str n times\n`*`        | `list * int`    | repeat list n times, give new list\n`*`        | `int * list`    | repeat list n times, give new list\n`/`        | `int / int`     | divide ints, truncated\n`%`        | `int % int`     | divide ints, give remainder\n`+`        | `int + int`     | add ints\n`+`        | `str + str`     | concatenate strs, give new string\n`+`        | `list + list`   | concatenate lists, give new list\n`+`        | `map + map`     | merge maps into new map, keys in right map win\n`-`        | `int - int`     | subtract ints\n`\u003c`        | `int \u003c int`     | true iff left \u003c right\n`\u003c`        | `str \u003c str`     | true iff left \u003c right (lexicographical)\n`\u003c`        | `list \u003c list`   | true iff left \u003c right (lexicographical, recursive)\n`\u003c= \u003e \u003e=`  | same as `\u003c`     | similar to `\u003c`\n`in`       | `str in str`    | true iff left is substr of right\n`in`       | `any in list`   | true iff one of list elements == left\n`in`       | `str in map`    | true iff key in map\n`==`       | `any == any`    | deep equality (always false if different type)\n`!=`       | `any != any`    | same as `not ==`\n`not`      | `not bool`      | inverse of bool\n`and`      | `bool and bool` | true iff both true, right not evaluated if left false\n`or`       | `bool or bool`  | true iff either true, right not evaluated if left true\n\n### Builtin functions\n\n`append(list, values...)` appends the given elements to list, modifying the list in place. It returns nil, rather than returning the list, to reinforce the fact that it has side effects.\n\n`args()` returns a list of the command-line arguments passed to the interpreter (after the littlelang source filename).\n\n`char(int)` returns a one-character string with the given Unicode codepoint.\n\n`exit([int])` exits the program immediately with given status code (0 if not given).\n\n`find(haystack, needle)` returns the index of needle str in haystack str, or the index of needle element in haystack list. Returns -1 if not found.\n\n`int(str_or_int)` converts decimal str to int (returns nil if invalid). If argument is an int already, return it directly.\n\n`join(list, sep)` concatenates strs in list to form a single str, with the separator str between each element.\n\n`len(iterable)` returns the length of a str (number of bytes), list (number of elements), or map (number of key/value pairs).\n\n`lower(str)` returns a lowercased version of str.\n\n`print(values...)` prints all values separated by a space and followed by a newline. The equivalent of `str(v)` is called on every value to convert it to a str.\n\n`range(int)` returns a list of the numbers from 0 through int-1.\n\n`read([filename])` reads standard input or the given file and returns the contents as a str.\n\n`rune(str)` returns the Unicode codepoint for the given 1-character str.\n\n`slice(str_or_list, start, end)` returns a subslice of the given str or list from index start through end-1. When slicing a list, the input list is not changed.\n\n`sort(list[, func])` sorts the list in place using a stable sort, and returns nil. Elements in the list must be orderable with `\u003c` (int, str, or list of those). If a key function is provided, it must take the element as an argument and return an orderable value to use as the sort key.\n\n`split(str[, sep])` splits the str using given separator, and returns the parts (excluding the separator) as a list. If sep is not given or nil, it splits on whitespace.\n\n`str(value)` returns the string representation of value: `nil` for nil, `true` or `false` for bool, decimal for int (eg: `1234`), the str itself for str (not quoted), the littlelang representation for list and map (eg: `[1, 2]` and `{\"a\": 1}` with keys sorted), and something like `\u003cfunc name\u003e` for func.\n\n`type(value)` returns a str denoting the type of value: `nil`, `bool`, `int`, `str`, `list`, `map`, or `func`.\n\n`upper(str)` returns an uppercased version of str.\n\n\n## Grammar\n\nBelow is the full littlelang grammar in pseudo-BNF format. Rules are in lowercase letters like \"statement\", and single tokens are in allcaps like \"COMMA\" and \"NAME\" (see tokenizer/tokenizer.go for the full list of tokens).\n\n```\nprogram    = statement*\nstatement  = if | while | for | return | func | assign | expression\nif         = IF expression block |\n             IF expression block ELSE block |\n             IF expression block ELSE if\nblock      = LBRACE statement* RBRACE\nwhile      = WHILE expression block\nfor        = FOR NAME IN expression block\nreturn     = RETURN expression\nfunc       = FUNC NAME params block |\n             FUNC params block\nparams     = LPAREN RPAREN |\n             LPAREN NAME (COMMA NAME)* ELLIPSIS? COMMA? RPAREN |\nassign     = NAME ASSIGN expression |\n             call subscript ASSIGN expression |\n             call dot ASSIGN expression\n\nexpression = and (OR and)*\nand        = not (AND not)*\nnot        = NOT not | equality\nequality   = comparison ((EQUAL | NOTEQUAL) comparison)*\ncomparison = addition ((LT | LTE | GT | GTE | IN) addition)*\naddition   = multiply ((PLUS | MINUS) multiply)*\nmultiply   = negative ((TIMES | DIVIDE | MODULO) negative)*\nnegative   = MINUS negative | call\ncall       = primary (args | subscript | dot)*\nargs       = LPAREN RPAREN |\n             LPAREN expression (COMMA expression)* ELLIPSIS? COMMA? RPAREN)\nsubscript  = LBRACKET expression RBRACKET\ndot        = DOT NAME\nprimary    = NAME | INT | STR | TRUE | FALSE | NIL | list | map |\n             FUNC params block |\n             LPAREN expression RPAREN\nlist       = LBRACKET RBRACKET |\n             LBRACKET expression (COMMA expression)* COMMA? RBRACKET\nmap        = LBRACE RBRACE |\n             LBRACE expression COLON expression\n                    (COMMA expression COLON expression)* COMMA? RBRACE\n```\n\n\n## Building and running\n\nTo build, [install Go](https://golang.org/), then fetch and build like so:\n\n```\ncd ~/go/src  # or wherever your Go code lives\ngo get github.com/benhoyt/littlelang\ncd github.com/benhoyt/littlelang/\ngo build\n```\n\nYou can then run one of the examples using the Go interpreter binary:\n\n```\n./littlelang examples/readme.ll\n```\n\nIf you want to get really meta, run the README example using the littlelang interpreter running under the Go interpreter:\n\n```\n./littlelang littlelang.ll examples/readme.ll\n./littlelang littlelang.ll littlelang.ll examples/readme.ll\n```\n\nHow deep does the rabbit hole go?\n\n\n## Credits\n\nMany thanks to Bob Nystrom for his free book [Crafting Interpreters](http://www.craftinginterpreters.com/), which is a great read and helped me understand how to implement closures.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenhoyt%2Flittlelang","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbenhoyt%2Flittlelang","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenhoyt%2Flittlelang/lists"}