Minor readme updates

This commit is contained in:
sloum 2023-11-28 23:01:54 -08:00
parent 45e396993d
commit cd33b9f3bd
1 changed files with 59 additions and 24 deletions

View File

@ -1,6 +1,6 @@
# felise
_felise_ is a programming language occupying a space between [concatenative](https://en.wikipedia.org/wiki/Concatenative_programming_language) and [procedural](https://en.wikipedia.org/wiki/Procedural_programming) programming (with, like many modern languages, some influence from functional programming). It is a hobby language designed as a successor to [nimf](https://git.rawtext.club/nimf-lang/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](https://golang.org). 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).
_felise_ is a programming language occupying a space between [concatenative](https://en.wikipedia.org/wiki/Concatenative_programming_language) and [procedural](https://en.wikipedia.org/wiki/Procedural_programming) 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](https://golang.org). 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).
As a new language there is no idiomatic _felise_ style at present and anything goes. 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.
@ -43,7 +43,7 @@ end
sys-args length 0 >
if
sys-args 1 list-get INT cast fib println
sys-args 1 <- INT cast fib println
else
"No input" stderr file-write
end
@ -72,57 +72,92 @@ sys 0m0.012s
Pretty snappy for this initial test as I am building things. Granted, it is iterative and not recursive (which would certainly slow down).
Available types in the language are `STRING`, `INT`, `BOOL`, `FLOAT`, `PROC`, and `LIST`. There a few other types available to the interpreter, but that a user would not use themselves. The `LIST` type is the core data structure of the language, beyond the stack, and a `LIST` can hold a mix of any of the above types, including `LIST` items. As such, this creates a strange blend of typed variables and untyped data structures. Still working out the ramifications of this decision...
Available types in the language are `STRING`, `INT`, `BOOL`, `FLOAT`, `PROC`, `TYPE`, and `LIST`. There a few other types available to the interpreter, but that a user would not use themselves. The `LIST` type is the core data structure of the language, beyond the stack, and a `LIST` can hold a mix of any of the above types, including `LIST` items. As such, this creates a strange blend of typed variables and untyped data structures. Still working out the ramifications of this decision...
## Building
Assuming you have the mainline [go compiler](https://go.dev/dl/) and [git](https://git-scm.com/) installed (and are using a posix-ish shell on a non-windows system):
```
git clone https://git.rawtext.club/sloum/felise && cd felise && go install
git clone https://tildegit.org/sloum/felise && cd felise && go install
```
You should then be able to run `felise` to load the repl (assuming the path that `go install` installs to is on your path -- you can always use `go build` to build in the local directory instead). As things develop a Makefile will likely be added, but right now it just isnt necessary.
## 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
- Available types: `INT`, `FLOAT`, `STRING`, `BOOL`, `LIST`
- Types
- `BOOL`
- `FLOAT`
- `INT`
- `LIST`
- `STRING`
- `TYPE`
- Blocks
- All block types end with a `.` or `end` token (they are equivalent)
- Blocks create their own scope as a child to the scope they were created in
- All block types end with `end`, which always closes out the current scope (including the main scope)
- Block types:
- Loops: `while`, `dowhile`
- Procedures: `proc`, `proc!`
- User defined forward reading procedures (`proc!`) enable things like writing procedures that act on variables directly, like how `var!` and `set!` do
- Conditional: `if`/`else`
- Error handling: `try`/`catch` (used with `throw`)
- Lexical scoping
- A solid library of built-in procedures developed in golang directly, providing the language primitives and base features to build on without sacrificing speed
- Loops
- `while`
- `dowhile`
- Procedures
- `proc`
- `proc!`
- User defined forward reading procedures (`proc!`) enable things like writing procedures that act on variables directly, like how `var!` and `set!` do
- Conditionals
- `if`
- `else`
- Error handling
- `try`
- `catch` (often used with `throw`)
- Lexical scoping with explicit scope jumping for variable creation/setting and closures
- 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
- A `list` type that can contain any other type, including lists themselves
- _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
- Most list operations allow negative index references. -1 would be the last item in the list, -2 the 2nd to last, and so on...
- Setting indexes, getting indexes, slicing, joining two lists, and appending are all available
- Variables are typed at creation: `INT var! myvar` creates an int variable named `myvar` and is set to `0` at creation
- 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
- Helpful error messages with stack straces
- Repl with tab completions for built-ins
- Repl with tab completions for built-ins and imports
- Loading of a single file works, and any arguments passed at the shell will be passed in as a list named `sys-args`
- Variables can be created in parent scopes with `scoped-var!` or updated in parent scopes with `scoped-var!`
- 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 .` 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 it)
- A few basic libs written in felise are included. The `std` lib is imported automatically, but `math` and `stack` are also available for import (`"math" import`, for example). You can also import files: `"../myfile.fe" import`.
- The functional programming style operations `each!` and `filter!` are available. `each!`, by nature of operating on the stack, can also be used as a reduce process as well (`0 [1 2 3] each! +` sums all of the numbers in the list starting at `0`, resulting in `6` on TOS)
- Variables can be created in parent scopes with `scoped-var!` or updated in parent scopes with `scoped-set!`
- 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 _identifier_ without referencing its _value_
- 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 means 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
- 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**
- 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` 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 if the tradeoff
- You can close out the global scope with a terminal `.` (at your option) and anything after it in the file will be ignored by the interpreter
- 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](https://en.wiktionary.org/wiki/your_mileage_may_vary)
## License