Minor readme updates

This commit is contained in:
sloum 2023-11-30 09:35:05 -08:00
parent 1a662c7ab7
commit 1a3eabd153
3 changed files with 73 additions and 96 deletions

View File

@ -2,33 +2,36 @@
_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.
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
Since things are still in a major early development phase I figure an example of the code and a metric will suffice for the readme for now.
A fibonocci procedure seems like a decent example. Below you will see variables, error handling, a procedure, a docstring, comments, conditionals, loops, program arguments, etc. Covers a lot of good bases.
A file, `fibtest.fe`:
A file, `test.fe` (included in the repo):
``` forth
proc fib
| calculates the nth fibonacci number
where n is an INT on TOS at call time |
| Stack: INT
Read :
Push : INT
Notes: Calculates the nth fibonacci number where n is an INT on TOS at call time |
INT var! fib# set! fib#
# svar! creates and sets a var in one call
svar! fib#
# var! creates a var with a zero value initialized
INT var! a
INT var! b 1 set! b
1 svar! b
INT var! s
proc continue-loop?
fib# 0 >
end
proc continue-loop? fib# 0 > end
continue-loop?
while
continue-loop? while
a b + set! s
b set! a
s set! b
@ -40,24 +43,17 @@ proc fib
end
# run it!
sys-args length 0 >
if
sys-args 1 <- INT cast fib println
sys-args length 0 > if
try
# attempt to convert to INT
sys-args 1 <- INT cast
catch
"Requires an integer" throw
end
fib println
else
"No input" stderr file-write
end
end
^
This last `end` closes the global scope, so anything
below it is unread by the interpreter. As such, this
is basically a comment. `end` always closes the current
scope for any construct that creates a scope (while,
proc, if, dowhile, proc!, try). The global scope can
be closed too, even though it doesn't have an explicit
start tag. You can use `.` instead of `end` if you
prefer.
```
Running `time felise ./fibtest.fe 40` on my Linux system outputs:
@ -65,24 +61,24 @@ Running `time felise ./fibtest.fe 40` on my Linux system outputs:
``` sh
102334155
real 0m0.010s
user 0m0.001s
sys 0m0.012s
real 0m0.015s
user 0m0.005s
sys 0m0.013s
```
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`, `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://tildegit.org/sloum/felise && cd felise && go install
``` bash
git clone https://tildegit.org/sloum/felise && cd felise && make
sudo make install # at your option
```
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.
You should then be able to run `felise` (or `./felise` if only `make` was called) to load the repl.
## Libraries
@ -109,39 +105,30 @@ You could also do something like the following to load arbitrary code from anoth
## Features
- Types
- `BOOL`
- `FLOAT`
- `INT`
- `LIST`
- `STRING`
- `TYPE`
- `BOOL`, `FLOAT`, `INT`, `LIST`, `STRING`, `TYPE`
- Blocks
- 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`
- `while`, `dowhile`
- Procedures
- `proc`
- `proc!`
- `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`
- `if`, `else`
- Error handling
- `try`
- `catch` (often used with `throw`)
- Lexical scoping with explicit scope jumping for variable creation/setting and closures
- `try`, `catch` (often used with `throw`)
- Lexical scoping with explicit scope jumping for variable creation/setting (`scoped-var!` & `scoped-set!`) 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...
- Setting indexes, getting indexes, slicing, joining two lists, and appending are all available
- Setting indexes, getting indexes, `slice`, joining two lists (`+`), joining a list into a string (`*`), `pop`, `shift`, and `append` are all available (generally with variants that can update a variable in addition to stack versions)
- 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 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`
- Repl with tab completions for built-ins, library procs, and imports
- 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-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

View File

@ -16,6 +16,7 @@ import (
"crypto/tls"
"fmt"
"io"
"math"
"net/url"
"strconv"
"strings"
@ -348,6 +349,12 @@ func tokenStringToInt(tokenString string) (int, error) {
if err == nil {
return int(i), nil
}
// If parsing as INT fails, try FLOAT then INT
y, err := strconv.ParseFloat(tokenString, 32)
if err == nil {
return int(math.Floor(y)), nil
}
return -1, fmt.Errorf("Could not convert STRING to INT")
}

71
test.fe
View File

@ -1,53 +1,36 @@
# This is a comment
proc fib
| calculates the nth fibonacci number
where n is an INT on TOS at call time |
| Stack: INT
Read :
Push : INT
Notes: Calculates the nth fibonacci number where n is an INT on TOS at call time |
INT var! fib#
set! fib#
INT var! a
INT var! b 1 set! b
INT var! s
svar! fib#
INT var! a
1 svar! b
INT var! s
proc continue-loop?
fib# 0 > end
proc continue-loop? fib# 0 > end
continue-loop?
while
a b + set! s
b set! a
s set! b
fib# 1 - set! fib#
continue-loop? end
a
continue-loop? while
a b + set! s
b set! a
s set! b
fib# 1 - set! fib#
continue-loop?
end
a
end
# run it!
sys-args length 0 >
if
sys-args 1 list-get INT cast fib println
sys-args length 0 > if
try
# attempt to convert to INT
sys-args 1 <- INT cast
catch
"Requires an integer" throw
end
fib println
else
"No input\n" stderr file-write
"No input" stderr file-write
end
end <-- This dot concludes reading of the file so
anything below is just comment space that will
not be read by the interpreter. Dots always
close off a given scope. Since we were in the
global scope right before that dot, it essentially
ends the program. All constructs that use a dot
work in this way. As such, all things that use
a dot create their own scope. To create a var
in a parent scope use: `1 scoped-var! myvar`,
that will jump up one scope and create the var.
You can jump any number of scopes and it will
never jump higher than the global scope, nor will
it error. So something like `1000 scoped-var! myvar`
should generally put something in the global scope.
The only time it wouldnt is in deep recursion, but
the stack is less than 1000, so you'd likely overflow
before it would be a problem.