{"id":22697595,"url":"https://github.com/corth-lang/corth","last_synced_at":"2025-04-13T04:50:19.710Z","repository":{"id":65610089,"uuid":"590202774","full_name":"corth-lang/Corth","owner":"corth-lang","description":"A self-hosted stack based language like Forth","archived":false,"fork":false,"pushed_at":"2025-02-02T10:58:19.000Z","size":7979,"stargazers_count":11,"open_issues_count":32,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-26T21:46:54.060Z","etag":null,"topics":["compiler","linux","nasm-assembly","programming-language","stack-based-language"],"latest_commit_sha":null,"homepage":"","language":null,"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/corth-lang.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}},"created_at":"2023-01-17T21:48:40.000Z","updated_at":"2025-02-07T18:58:57.000Z","dependencies_parsed_at":"2023-12-23T13:39:07.508Z","dependency_job_id":"db13de39-4fd6-4b15-b386-20e11aa48e6b","html_url":"https://github.com/corth-lang/Corth","commit_stats":null,"previous_names":["corth-lang/corth"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/corth-lang%2FCorth","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/corth-lang%2FCorth/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/corth-lang%2FCorth/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/corth-lang%2FCorth/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/corth-lang","download_url":"https://codeload.github.com/corth-lang/Corth/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248665757,"owners_count":21142123,"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":["compiler","linux","nasm-assembly","programming-language","stack-based-language"],"created_at":"2024-12-10T05:14:35.827Z","updated_at":"2025-04-13T04:50:19.678Z","avatar_url":"https://github.com/corth-lang.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# Corth\nA stack based programming language I designed based on Forth and Porth from the [Porth programming language](https://gitlab.com/tsoding/porth) series of [Tsoding Daily channel](https://www.youtube.com/@TsodingDaily).\nThe language is quite similar to Porth, however there are several differences.\nThe compiler was written in Python first, but then rewrote it using Corth language itself. It is now a self hosted language.\n\nThe repo consists of the compiler source code in Corth, an already compiled and assembled compiler, standard library and examples.\n\n## Requirements\n\n- Right now, the compiler can only compile in ELF64 format.\n- Compiler uses NASM which can be downloaded using a package manager or from its repo.\n\n## How to use the compiler?\n\nTo compile a Corth program to an ELF64 executable, cd to the directory that contains the corth executable and run:\n\n\n    ./corth compile \u003cfile-name\u003e -i ./std/\n    \n    \nThis will create an executable called *output* which can be directly run.\n\nThe compiler can be bootstrapped using the `bootstrap` subcommand:\n\n\n    ./corth bootstrap ./compiler/ --std ./std/\n    \n    \nThis will compile the compiler source code and place it at *./corth*.\n\n\n## Quick start to the Corth language\n\n- Corth is a concatinative (stack based) language. Every operation pops its arguments from the end of the stack and pushes its return values to the end of the stack.\n- Programs are compiled into NASM format first, then into object and executable files.\n- Indentation, spaces, newline characters are ignored by the compiler unless they are inside of a string.\n- Names can contain any character except whitespace (space, tab or newline); but can not start with a decimal digit, a dollar sign, single or double quote.\n- Language is made up of WORDS, instead of statements. Because of this, the lexer does not create a AST and instead only produces a sequence of tokens. These tokens are then run in order.\n- EVERYTHING IN THIS LANGUAGE CAN BE CHANGED AT ANY TIME. USE WITH CAUTION.\n\n### First program:\n\n    // From ./examples/hello_world.corth\n    \n    include \"linux_86x/stdio.corth\"\n    \n    proc main\n      int int -\u003e int\n    in let argc argv in\n      \"Hello, World!\\n\" puts\n    end 0 end \n    \n- This is a simple program that prints 'Hello, World!' when run.\n- Compile and run this program with `./corth compile ./examples/hello_world.corth -i ./std.corth \u0026\u0026 ./output`\n- [`include`](#include) keyword is used to include the library *stdio*, which contains some basic I/O procedures and macros for writing to/from files and the standard streams.\n- [`let`](#let) keyword is used to 'name' values. In this example, it is used to name the parameter values.\n- [`proc`](#procedures) keyword is used to define a procedure. `main` is where the program starts.\n- Writing anything in double quotes causes it to be interpreted as a string. A string is just a pointer to a character array and an integer that represents its length.\n- `puts` is defined in the *stdio* library, which prints the string that is passed to it in the standard output.\n- For more examples, you can check the *./examples/* directory.\n- For more information about these concepts, keep scrolling.\n\n### Numbers:\n\n    // Push and pop direction of the stack is kept on the right side in this document.\n    // This will hopefully help understand the concept of stack and 'let' keyword.\n    34        // Stack = { 34 }\n    0b101001  // Stack = { 34, 41 }\n    0o205126  // Stack = { 34, 41, 68182 }\n    0x5729da  // Stack = { 34, 41, 68182, 5712346 }\n    'a'       // Stack = { 34, 41, 68182, 5712346, 97 }\n    '\\n'      // Stack = { 34, 41, 68182, 5712346, 97, 10 }\n\n- Numbers push an integer type to the stack. An integer type of stack item stores an 8 byte value.\n- Lexer allows binary, octal and hexadecimal integers as well. The prefixes are `0b`, `0o` and `0x` respectively.\n- Putting a character between single quotes ('') causes it to be interpreted as an integer, which appends its ASCII value to the stack.\n- Escape statements such as `\\n` are supported in characters.\n\n### Strings:\n\n    \"This is a string\"   // Stack = { 0x648a15, 16 }\n\n    \"This also is a string.\n     In Corth, multi-line strings are supported.\"  // Stack = { 0x648a15, 16, 0x648a26, 71 }\n\n- Strings push two items, a pointer to the address and an integer length of the string.\n- Escape statements are also supported in strings.\n- It is not recommended to modify the contents of strings in program, although it is possible.\n\n### Arithmetic operators:\n\n    34 35 + // Stack = { 69 }\n    69 27 - // Stack = { 69, 42 }\n    68 inc  // Stack = { 69, 42, 69 }\n    43 dec  // Stack = { 69, 42, 69, 42 }\n    3 23 *  // Stack = { 69, 42, 69, 42, 69 }\n    85 2 /  // Stack = { 69, 42, 69, 42, 69, 42 }\n\n- `+` sums the last two items and pushes the result back, `-` subtracts signed or unsigned integers.\n- `*` multiplies and `/` divides signed integers. Right now, the compiler does not keep track of the integer 'signeded-ness', so every signed and unsigned operation can be used on any integer type. The unsigned versions of `\\*` and `/` are `u\\*` and `u/`.\n- `inc` and `dec` are macros defined as `1 +` and `1 -` in *core/arithmetic.corth*. They can be used to increment or decrement a number once.\n\n### Include:\n\n    include \"str.corth\"\n\n- When compiler sees an `include` keyword, it starts to compile the file whose path is given after the `include` keyword.\n- Right now, the compiler does not allow including directories. A todo error will be given if tried.\n- *./std/* directory contains some useful libraries like *core/stack.corth* or *str.corth*.\n\n### Stack operations:\n  \n    1 2 3 // Stack = { 1, 2, 3 }\n    swp   // Stack = { 1, 3, 2 }\n    drop  // Stack = { 1, 3 }\n    dup   // Stack = { 1, 3, 3 }\n    rot   // Stack = { 3, 3, 1 }\n    arot  // Stack = { 1, 3, 3 }\n\n- *./std/core/stack.corth* contains several macros for stack manipulation.\n- `drop` removes the last item from the stack.\n- `dup` duplicates the last item in the stack.\n- `swp` swaps the places of the last two items.\n- `rot` rotates the places of the last 3 items, by moving the first added to the last position.\n- `arot` does the exact opposite of what `rot` does.\n- These operations are macros defined using [let](#let), with names starting with underscore (_). For hard to manage stack operations, it is recommended to use `let` instead of these macros.\n- There are some others, but it is recommended to check *./std/core/stack.corth* for more information since they are a bit more complex.\n\n### I/O:\n\n    \"Hello, world!\\n\" puts                      // Prints 'Hello, world!' to the standard output.\n    34 35 + eputu \" is a nice number.\\n\" eputs  // Prints '69 is a nice number.' to the standard error.\n\n- *./std/linux_x86/stdio.corth* contains useful procedures for I/O operations like reading from and writing to streams.\n\n### Procedures:\n\n    proc arithmetic-average  // The name of the procedure is 'arithmetic-average'.\n      // Procedure takes two integers as arguments, and returns a single integer.\n      // The leftmost type is the oldest item in the stack.\n      int int -\u003e int\n    in\n      // This is where the code is located.\n      + 2 /\n    end\n\n    // This procedure will be run.\n    proc main\n      // Right now, only 'int int -\u003e int' argument layout is allowed for the main procedure.\n      int int -\u003e int\n    in let argc argv in\n      \"The arithmetic average of 53 and 31 is \" puts 53 31 arithmetic-average putu \".\\n\" puts\n    end 0 end  // Program exits with exit code 0.\n\n- `proc` defines a procedure, which can be called anywhere in the code.\n- `return` can be used to early return from a procedure, but the stack must match with the procedure's output layout.\n- Because of the way the stack works, procedures can return more than one value unlike most other languages.\n- Program starts from the `main` procedure; if it is not defined, the compiler will return an error.\n- If the code does not require recursion and is simple, it might be better to use a macro depending on the exact requirements.\n\n### Macros:\n\n    macro sayHi \n      // Name of the macro is 'sayHi'. This means when the compiler sees 'sayHi' anywhere in the code, it will convert it to these.\n      // Takes a string, the name and prints a welcome message.\n\n      \"Hi, \" puts puts \"!\\n\" puts\n    endmacro\n\n    proc main\n      int int -\u003e int\n    in let argc argv in\n      \"Josh\" \n      sayHi  // This will be converted to this:\n      // \"Hi, \" puts puts \"!\\n\" puts\n    end 0 end\n\n- `macro` keyword is used to define macros and `endmacro` is used to end the definition.\n- Macros expand at compile time, allowing simplifying and compressing code without losing functionality.\n- Macros are only compiled after expanding, so any compile time error that would be caused by a macro is not detected before expansion.\n- Using `let` inside a macro is usually a bad idea, although some library macros are designed that way (like `dup`, `swp` or `rot`). If the code requires `let`; either change that macro to a procedure, or name the let variable with names that starts and ends with underscores (_).\n\n### Name scopes:\n\n- Two global or two local variables can not have the same name, but if a local and a global variable have the same name, the local one will be reachable until it is removed from the scope.\n- If a name is defined globally (for example with `memory`), it can be reached from anywhere meaning any procedure in and out of the same file.\n- If a name is defined locally in a procedure, it can only be reached within the scope that the statement it is in. For example:\n\n\n        // 'x' is undefined here.\n        69 let x in \n          // 'x' is defined here.\n        end\n        // 'x' is undefined here.\n\n\n### Comments:\n\n    // This is a line comment, it can also come after code\n    34 35 + putu  // Just like that\n\n    /*\n      This is a block comment, aka a multi-line comment.\n      Block comments can span several lines.\n    */\n    \n- Comments do not have any affect on the compiled program.\n\n### Control flow:\n\n    include \"linux_x86/stdio.corth\"\n\n    proc main\n      int int -\u003e int\n    in let argc argv in\n      2 2 + 5 = if\n        \"Well, math is broken. Nice.\\n\" puts // Hopefully won't be printed.\n      end\n\n      3 4 \u003e if\n        \"Your computer has virus\\n\" puts     // Hopefully won't be printed.\n      else\n        \"Your computer is alright\\n\" puts    // Hopefully will be printed.\n      end\n\n      // Count from 0 to 9.\n      \"First 10 numbers from 0 are,\\n\" puts\n      0 while dup 10 \u003c do                    // Duplicate the number, and check if it is less then 10.\n        dup putu \"\\n\" puts                   // Print the number.\n      inc end drop                           // Increase the number.\n    end 0 end\n\n- `if` is used for conditions, it runs the code after it only if the last item on the stack is true.\n- `if` can be used with `else` for more functionality, which runs only if the bool is false.\n- Because of the way Corth works, the `if-else` statement can be used like a ternary operator in other languages.\n- `while` is used for loops, and `break` can be used to quit the loop early.\n- If `if` is used without an `else`, the stack must not change between `if` and `end` since otherwise it will create an ambiguity of what the stack will be after that part.\n- If `if` is used with an `else`, the stack change between `if` and `else` must be the same for the code between `else` and `end`.\n- Stack must not change between `while` and `end`.\n- Even though parser does not care for indentation, it is still a good idea to indent since in Corth these operations can get very hard to understand very quickly.\n- Sadly, Corth does not have `else if/elif` statements at the moment.\n\n### Let:\n\n     // The rightmost variable collects the newest item in the stack. So x = 3 and y = 4.\n     // From now on, x will be replaced with 3 and y will be replaced with 4.\n     // If 'let' variables are compile-time constants, the variables will be directly replaced for optimization reasons.\n     // Otherwise, the stack values will be stored in local memory and loaded inside the structure.\n     3 4 let x y in\n       x y * x + y -\n     end\n     // Stack = { 10 }\n\n- `let` keyword stores stack items in local memory. The stored values can not be modified but can be read later.\n- When any `let` variable is called, it directly returns its value; unlike a `memory` variable which returns its address.\n\n### Peek:\n\n     // The rightmost variable collects the newest item in the stack. So x = 3 and y = 4.\n     // From now on, x will be replaced with 3 and y will be replaced with 4.\n     3 4 peek x y in\n       x y * x + y -\n     end\n     // Stack = { 3, 4, 10 }\n\n- `peek` works very similar to `let`, except it does not pop the items from the stack. This allows it to directly load them from the stack instead of using the local memory.\n- When any `peek` variable is called, it directly returns its value; just like `let`.\n\n### Static memory management:\n\n    include \"core/memory.corth\"\n\n    // This is a global variable.\n    // The size must be a compile-time constant as memory is allocated in compile-time.\n    memory count sizeof(int) end\n    \n    proc increase -\u003e in\n      // Reads the value of 'count', adds one and writes back.\n      count @64 inc count !64\n    end\n\n    proc main\n      int int -\u003e int\n    in let argc argv in\n      // Set the value of 'count' to 0.\n      0 count !64\n\n      // Increase the value of 'count'.\n      increase\n\n      // Print the value stored at 'count'. Should print '1'.\n      count @64 putu putnl\n      \n      memory x sizeof(int) in\n      memory y sizeof(int) in // Size of the variable 'x' is equal to the size of an integer (8 bytes). Same with variable 'y'.\n\t       \n        0 x !64\n        x @64 puti \" is before saving 'x'\\n\" puts\n        420 x !64\n        x @64 puti \" is after saving 'x'\\n\" puts\n        \n        0 y !64\n        y @64 puti \" is before saving 'y'\\n\" puts\n        69 y !64\n        y @64 puti \" is after saving 'y'\\n\" puts\n        \n      end end\n    end 0 end\n\n- `memory` is used for allocating global or local memory in compile-time.\n- When a variable name declared by `memory` keyword is called; the address of the variable is returned.\n- When used globally, `memory` allocates memory that can be used from everywhere after definition.\n- When used locally, `memory` allocates memory that can be used only in between `in` and `end`.\n- `@64` loads 8 bytes of data from the address. (`@8`, `@16` and `@32` are also allowed)\n- `!64` stores 8 bytes of data to the address.\n\n### Dynamic memory management:\n\n    include \"dynamic/malloc.corth\"\n\n    // Assume this is inside a procedure.\n    100 malloc let buffer in  // Allocate 100 bytes of memory and save the object pointer as a constant named `buffer`.\n      // Loop through every byte in `buffer`.\n      buffer while dup buffer 100 + \u003c do peek address in\n        0x67 address !8  // Set the byte to 0x67.\n      end inc end drop\n\n      buffer mfree drop // Deallocate the space.\n    end\n\n- *std/dynamic/* directory contains some dynamic memory management utilities, like `malloc` or `djoin`. Please check the library to learn more.\n\n### Collections:\n\n- *std/collections/* directory contains different kinds of data structures. Most of these are terribly implemented though :(.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcorth-lang%2Fcorth","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcorth-lang%2Fcorth","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcorth-lang%2Fcorth/lists"}