A concatenative programming language
Go to file
Sloom Sloum Sluom IV 00f534639a Notes broken procs in strings lib 2024-05-16 08:57:38 -07:00
examples Finishes formatting docstrings everywhere, fixes parse error when catch is not present in try, adds catch-error and procarg tab completion and docstrings, moves fib example to proper folder 2024-05-13 20:14:35 -07:00
lib Notes broken procs in strings lib 2024-05-16 08:57:38 -07:00
.gitignore Adds a few new special forms (var+ and set+), updates readme, adds license, adds test file 2023-06-02 10:12:23 -07:00
LICENSE Better error handling, early return from procs working better - though needs more testing 2023-06-01 22:42:09 -07:00
Makefile Updates walkthrough 2024-05-08 23:03:36 -07:00
README.md Finishes formatting docstrings everywhere, fixes parse error when catch is not present in try, adds catch-error and procarg tab completion and docstrings, moves fib example to proper folder 2024-05-13 20:14:35 -07:00
docstring.go Finishes formatting docstrings everywhere, fixes parse error when catch is not present in try, adds catch-error and procarg tab completion and docstrings, moves fib example to proper folder 2024-05-13 20:14:35 -07:00
env.go Adds import support 2023-06-13 13:22:22 -07:00
felise.1 Updates walkthrough 2024-05-08 23:03:36 -07:00
filesystem.go go fmt, fixes issue where unclosed proc did not trigger error in parse, updates repl language surrounding repl input mode 2024-05-08 08:49:48 -07:00
globals.go go fmt, fixes issue where unclosed proc did not trigger error in parse, updates repl language surrounding repl input mode 2024-05-08 08:49:48 -07:00
go.mod Adds basic Makefile and bumps version number 2023-11-29 11:33:58 -08:00
go.sum Lots of progress. 2023-06-01 15:16:17 -07:00
helpers.go go fmt 2024-05-13 13:52:47 -07:00
interpret.go go fmt. More docstring fixes in libs 2024-05-14 08:55:03 -07:00
keywords.go go fmt. More docstring fixes in libs 2024-05-14 08:55:03 -07:00
lexer.go go fmt 2024-05-13 13:52:47 -07:00
main.go Finishes updating docstring style in all lib procs 2024-05-14 09:08:42 -07:00
parser.go go fmt. More docstring fixes in libs 2024-05-14 08:55:03 -07:00
stack.go go fmt, fixes issue where unclosed proc did not trigger error in parse, updates repl language surrounding repl input mode 2024-05-08 08:49:48 -07:00
types.go go fmt, fixes issue where unclosed proc did not trigger error in parse, updates repl language surrounding repl input mode 2024-05-08 08:49:48 -07:00
walkthrough.html Finishes formatting docstrings everywhere, fixes parse error when catch is not present in try, adds catch-error and procarg tab completion and docstrings, moves fib example to proper folder 2024-05-13 20:14:35 -07:00

README.md

felise

felise is a programming language occupying a space between concatenative and procedural programming (with, like many modern languages, some influence from functional programming). It is a hobby language designed as a successor to nimf (an earlier programming language by the same author). The language is implemented as a basic AST walking interpreter (though a compiler, JIT, or bytecode/vm would also be possible) in golang. It is not dynamically typed and all variables need to be declared as a given type (even control structures like if and while are technically types as well). Type checks are generally handled at runtime, with a few such checks handled in the lex/parse phases of runtime (rather than as encountered in running code).

Deliberate effort has gone into bridging some gaps between the stack based concatenative style and a procedural/imperative style. The later is represented by a heavy focus on variable use, lexical scoping, nested procedures forming closures, etc.

This README has gotten a little messy with lots of random information. TODO: clean it up.

Example

This is a fairly simple example, though verbosely handled (for clarity and correctness). It is a simple program to take a url and print out a numbered list of links found at that url (assuming an html document). It takes the url from the shell as an argument to the script and also checks for a -h flag or a lack of argument.

# Print a numbered list of the links on a web page
#   taking the URL as an argument from the shell

"paths" import

proc links
  | (STRING -- )  Takes an http(s) url STRING from TOS and prints a list of urls found on the given page. |

  # Vet the input as the correct url scheme
  dup url-scheme dup "https" = swap "http" =
  or not if
    "Expected an http or https url" throw
  end


  # Get the html body
  net-get("body") svar! html

  # Prep a procedure for our each loop
  INT var! counter
  proc list-item
    ++! counter
    counter print
    ". " print
    2 <- println
  end

  # Find all of the link targets as a LIST
  `<a href="([^"]+)"` html re-find-all


  each! list-item
end

proc help
  `Pass an http(s) url to this program and the program will print out a numbered listing of the link targets` "\n" + stderr file-write
end

# run it!
sys-args length 0 = sys-args "-h" in? or if
  help
else
  sys-args 1 <- links
end

Building

Assuming you have the mainline go compiler and git installed (and are using a posix-ish shell on a non-windows system):

git clone https://tildegit.org/sloum/felise && cd felise && make
sudo make install # at your option

You should then be able to run felise (or ./felise if only make was called) to load the repl.

Libraries

Libraries, written in felise, are baked into the executable at present. This makes it easier to work things out than depending on a file-system based approach. The available libraries are as follows:

  1. std (loaded by the repl automatically)
  2. math
  3. paths
  4. stack
  5. strings

To load a library follow this pattern:

"strings" import

You could also do something like the following to load arbitrary code from another file:

"~/my-code/felise-code.fe" import

Features

  • User Facing Types:
    • BOOL: true, false
    • DICT: {"Key1" "Value1" "Key2" 2 "Etc" {"SubKey1" 1}}
      • Keys must be STRING, values can be any type (including other DICT or LIST)
    • FLOAT: 235.6378, -1.5; 64 bit on 64-bit systems, 32 otherwise
    • INT: 235, -1; 64 bit on 64-bit systems, 32 otherwise
    • LIST: ["Hi" 5 false [1 2 3] 5.32 {"Hello" "World" INT}]
      • felise lists are 1 indexed
      • Most list operations allow negative index references: -1 would be the last item in the list, -2 the 2nd to last, and so on...
      • LISTs can contain any type
    • STRING: "Hello"
    • TYPE: INT, DICT, TYPE
  • Variables are typed at creation: INT var! myvar creates an int variable named myvar and is set to the type's zero value (0 in this case) at creation
    • Set variables after creation: 55 set! myvar
    • Or set and create with an inferred type: [1 2 3] svar! mylist
    • You can explicitly alter parent environments/scopes with scoped-var! & scoped-set!
  • Procedures: proc/proc!
    • Procedures opperate on the stack and do not take arguments
    • Procedures are not "first class" and cannot be passed around
    • User defined forward reading procedures (proc!) enable things like writing procedures that act on variables directly, like how var! and set! do
    • Using a forward reading procedure (created with proc!) to read a symbol representing a procedure allows for a sort of procedure or "callback" passing
  • Blocks: while, dowhile, proc, proc!, if/else, try/catch
    • Are lexically scoped and support closures
    • Alocks create their own scope as a child to the scope they were created in
    • Close their scope with the end keyword
    • proc and proc! use the keyword return to return early (it can be nested in other blocks)
    • while and dowhile use the keyword break to return early from the block (there is no equivalent of continue in other languages)
    • try is often used in combination with throw
  • Syntactic sugar is available to reference DICT/LIST items: myDictionary("Item1"), myList(-2), myDictWithList("colors")(1)
  • A solid base library of built-in procedures developed in golang directly, providing the language primitives and base features to build on without sacrificing speed
  • Helpful error messages with stack traces
  • Repl with tab completions for built-ins, library procs, imports, and user created procedures
  • Any arguments passed at the shell will be passed in as a list named sys-args
  • There is a lot of "operator overloading" going on with +, -, *, / to have them work on strings and lists in interesting ways.
  • Procedures have the ability to add a docstring proc myproc | I am a docstring | "hello" println end defines a procedure, and docstring! myproc would add the docstring for that proc to TOS (whence it could be printed). Built-ins also have docstrings available (learning the stack order for everything can be a pain without them). To see the available words/procedures the system knows use words, which will add a string containing them to TOS. You can then try to docstring! them and find out what they do
  • The functional programming operations each! and filter! are available. each!, by nature of operating on the stack, can also be used as reduce as well (0 [1 2 3] each! + sums all of the numbers in the list starting at 0, resulting in 6 on TOS)
  • Type comparisons are as easy as casting to TYPE and comparing. Assuming two items are on the stack: TYPE cast swap TYPE cast =
  • Procedures/built-ins that end their identifier with a ! indicate that they will "forward read" a word. So the code INT var! my-int will place INT (a TYPE) on the stack, then var! will pop the type and create a variable in the current scope with that type, var! will then "forward read" the token stream to get my-int and assign that SYMBOL to the newly created INT variable. Users can create their own forward reading procedures by using proc! instead of proc. This works sort of like a macro in some other languages: in the procedure body you can use the symbol procarg and when the procedure is run it will forward read to get a symbol or value and replace all instances of procarg with that value before executing the procedure. This allows for some form of argument passing beyond using the stack and is also the only way to reference a variable symbol without referencing its value

Gotchas / Weird Things

  • Unlike a traditional forth style system, felise encourages use of local variables. Its lexical scoping and garbage collection mean that the global scope does not get littered with random variables, and so it is very comfortable and easy to use local variables when defining procedures and not have to do the stack manipulation dance except where it makes sense to do so
  • LIST type items have a starting index of 1 (not the more common 0)
  • When using cast to go from FLOAT to INT, the number will be floored rather than truncated (as is common). Positive numbers will function like a trunc, but negative numbers will not (the FLOAT -1.57 would cast to the INT -2)
  • Using divide on two strings will do a split and produce a list of s1 split by s2 (TOS); this can be reversed by multiplying the list by a string. Similarly, subtracting anything from a LIST or STRING will attempt to remove the thing from the LIST or remove a stringified version of the thing from the STRING. This is just a sample of the "fun" overloading that is happening. Enjoy!
  • File I/O is done without file handles (from a user perspective) and consists only of append (file-write) and read all (file-read). When combined with file-remove (which also removed directories), mkdir, cd, pwd, and file-exists?, most common file actions can be accomplished with this set of features (when was the last time you needed to seek in a file?). It makes both writing and reading a bit more expensive, but simplicity is the tradeoff
  • You can close out the global scope with a terminal end (at your option) and anything after it in the file will be ignored by the interpreter, allowing for long comments/notes at the end of the file
  • While I write above that a non-windows system is required... it may build and work on windows. I'm not sure, as I do not use that OS and do not have a system to test it on. YMMV

License

Notice: This is not free/libre software as defined by the FSF or OSC. I do not ascribe to their vision of free software. I believe that capitalism is an inherent threat to freedom and is an injustice to everyone. As such I use the Floodgap Free Software License (Version 1). The license is included in the repo (see LICENSE file), and is mentioned and linked in each source code file. The basic idea is that you as an individual can do anything you like with this software that does not involve, directly or indirectly, money changing hands. There are a few exceptions to this noted in the license, but in general, that is the idea. Free to use in any way you like, so long as you don't charge for it. You are, of course, welcome to license code produced for the felise interpreter/language in any way you choose. The license I have chosen only applies to my interpreter code.