Minor readme updates
This commit is contained in:
parent
1a662c7ab7
commit
1a3eabd153
91
README.md
91
README.md
|
@ -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
|
||||
|
|
|
@ -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
71
test.fe
|
@ -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.
|
||||
|
||||
|
|
Loading…
Reference in New Issue