Adds a few new special forms (var+ and set+), updates readme, adds license, adds test file

This commit is contained in:
sloum 2023-06-02 10:12:23 -07:00
parent c96e2fc0d9
commit e9d7a43e0c
8 changed files with 215 additions and 50 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
felise

View File

@ -4,6 +4,10 @@ _felise_ is a [concatenative](https://en.wikipedia.org/wiki/Concatenative_progra
## 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 file, `fibtest.fe`:
``` forth
proc fib
| calculates the nth fibonacci number
@ -12,35 +16,70 @@ proc fib
INT var fib#
set fib#
fib# 0 = if 1 .
fib# 0 = if 1 return .
fib# 1 = if 3 .
fib# 1 = if 3 return .
fib# 1 > if
INT var grandparent 1 set grandparent
INT var parent 3 set parent
INT var i 2 set i
INT var me
INT var grandparent 1 set grandparent
INT var parent 3 set parent
INT var i 2 set i
INT var me
proc continue-loop?
i fib# < i
.
proc continue-loop?
i fib# < .
<=fib#
while
3 parent *
grandparent -
set me
parent set grandparent
me set parent
i 1 + set i
<=fib#
.
me
.
.
continue-loop?
while
3 parent *
grandparent -
set me
parent set grandparent
me set parent
i 1 + set i
continue-loop? .
me .
5 fib println
40 fib
"Result: "
println println # print both the text and the output
```
Running `time felise ./fibtest.fe` outputs:
```
23416728348467685
real 0m0.009s
user 0m0.005s
sys 0m0.006s
```
Pretty snappy for this initial test as I am building things. Granted, it is iterative and not recursive (which would certainly slow down).
## 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
```
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.
## Status
- There are no modules/libraries or loading support for such things as of yet
- There is a small library of built-in procedures to enable the basic functionality of the language. Most of it deals with control flow, variable/procedure creation, and stack manipulation. More is needed and will be coming soon along with documentation
- 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)
- The repl works
- Loading of a single file works
- Error messages are at least mildly helpful
- They include a line number, a message, and if the error is nested inside of procedures a trace of the call order of the procedures
- There is no input functionality at present: no reading from file or connection, no command line args
- There is no support for arrays, but they are on the horizon
- User defined procedures cannot yet request the next execution token (similar to how the `var` or `set` builtins work), this has been figured out but has not been implemented yet: coming soon
## 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.

BIN
felise

Binary file not shown.

View File

@ -127,7 +127,7 @@ func isKeyword(s string) bool {
"length", "ref", "return", "print", "println", "dup",
"drop", "over", "swap", "cast", "type", "while", "dowhile",
"if", "else", "and", "or", ">", "<", "=", "stackdump",
"clearstack":
"clearstack", "var+", "set+":
return true
default:
return false

View File

@ -56,20 +56,16 @@ ReplacedSymbol:
return fmt.Errorf(stackErrFmt, t.line, err.Error())
}
cond := toBool(v).val.(bool)
if cond {
for {
err = interpret(t.val.(while).body, NewEnv(en))
if err != nil {
return err
}
v, err = globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, t.line, err.Error())
}
if !toBool(v).val.(bool) {
break
}
for cond {
err = interpret(t.val.(while).body, NewEnv(en))
if err != nil {
return err
}
v, err = globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, t.line, err.Error())
}
cond = toBool(v).val.(bool)
}
} else {
err = interpret(t.val.(while).body, NewEnv(en))
@ -104,7 +100,7 @@ ReplacedSymbol:
// This join is meant to create a callstack trace of sorts
// so that if an error was found multiple procs deep each
// _should_ adds it proc name and line to the list.
return errors.Join(err, fmt.Errorf(" - In procedure `%s` on line %d", t.val.(proc).name, t.line))
return errors.Join(err, fmt.Errorf(" - In procedure `%s`", t.val.(proc).name))
}
if globalRetStack.Depth() > 0 {
globalRetStack.Pop()

View File

@ -23,6 +23,7 @@ import (
TODO / Notes on broken things
- Add `not`
- Add a version of `var` and `set` that can set to a parent scope
*/
@ -38,8 +39,12 @@ func callKeyword(kw token, r *tokenReader, en *env) error {
return libDivide(kw.line)
case "var":
return libVar(kw.line, r, en)
case "var+":
return libVarPlus(kw.line, r, en)
case "set":
return libSet(kw.line, r, en)
case "set+":
return libSetPlus(kw.line, r, en)
case "print":
return libPrint(false, kw.line)
case "println":
@ -184,7 +189,45 @@ func libVar(line int, r *tokenReader, en *env) error {
}
newToken.val = typeInit(newToken.kind)
en.Add(t.val.(string), newToken)
fmt.Println(*en)
return nil
}
func libVarPlus(line int, r *tokenReader, en *env) error {
scope, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, line, err.Error())
}
if scope.kind != INT {
return fmt.Errorf("ERROR | Line %d\n `set+ expected an INT declaring how many scopes to jump up\n", line)
}
for i := scope.val.(int); i > 0; i-- {
if en.parent != nil {
en = en.parent
} else {
break
}
}
typeToken, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, line, err.Error())
}
if typeToken.kind != TYPE {
return fmt.Errorf("ERROR | Line %d\n `var` expects a type on the top of the stack\n ", line)
}
newToken := token{}
newToken.kind = typeToken.val.(int)
newToken.line = line
t, err := r.Read()
if err != nil {
return fmt.Errorf("ERROR | Line %d\n Unexpectedly reached EOF\n", line)
}
if t.kind != SYMBOL {
return fmt.Errorf("ERROR | Line %d\n A non-symbol value was given as an identifier to var\n", line)
}
newToken.val = typeInit(newToken.kind)
en.Add(t.val.(string), newToken)
return nil
}
@ -212,6 +255,46 @@ func libSet(line int, r *tokenReader, en *env) error {
return nil
}
// ANY INT set+ SYMBOL
// INT represents the number of scopes to jump up
func libSetPlus(line int, r *tokenReader, en *env) error {
t, err := r.Read()
if err != nil {
return fmt.Errorf("ERROR | Line %d\n Unexpectedly reached EOF\n", line)
}
if t.kind != SYMBOL {
return fmt.Errorf("ERROR | Line %d\n A non-symbol value was given as an identifier to set\n", line)
}
scope, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, line, err.Error())
}
if scope.kind != INT {
return fmt.Errorf("ERROR | Line %d\n `set+ expected an INT declaring how many scopes to jump up\n", line)
}
for i := scope.val.(int); i > 0; i-- {
if en.parent != nil {
en = en.parent
} else {
break
}
}
e, err := en.Find(t.val.(string))
if err != nil {
return fmt.Errorf("ERROR | Line %d\n %s\n", t.line, err.Error())
}
v1, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, line, err.Error())
}
current := e.vars[t.val.(string)]
if v1.kind != current.kind {
return fmt.Errorf("ERROR | Line %d\n Cannot assign %s to %s var\n", t.line, kindToString(v1.kind), kindToString(current.kind))
}
e.vars[t.val.(string)] = v1
return nil
}
func libPrint(newline bool, line int) error {
v1, err := globalStack.Pop()
if err != nil {

32
main.go
View File

@ -63,7 +63,8 @@ length [varname]
// Not yet sure how to do this
ref [ident/symbol]
Put the symbol on TOS, to reference something that would
otherwise be executed immediately (if, while, proc, etc)
otherwise be executed immediately (if, while, proc, etc).
Still thinking out the semantics.
---
@ -104,7 +105,7 @@ Builtins on radar:
Need some way to respond to args given at runtime... may need
an array for that. Or maybe not? Maybe an `arg` word can be
defined so that: `0 INT arg myarg` would let someone pass an
defined so that: `0 INT flag myarg` would let someone pass an
arg: `felise myfile.fe --myarg=24`, with the arg defaulting to
0... does something like that work?
*/
@ -162,22 +163,26 @@ func repl() {
text.WriteString(in)
}
if !cont {
lexParseInterpret(text.String(), baseEnv)
lexParseInterpret(text.String(), baseEnv, true)
text.Reset()
}
}
}
func lexParseInterpret(s string, en *env) {
func lexParseInterpret(s string, en *env, repl bool) {
lexedTokens := lex(s)
r := NewTokenReader(lexedTokens)
parsedTokens := parse(r)
err := interpret(parsedTokens, en)
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
globalStack.sp = 0
} else {
fmt.Println("\n\033[0mOk.")
if repl {
globalStack.sp = 0
} else {
os.Exit(1)
}
} else if repl {
fmt.Println("\n\033[0mOk.")
}
}
@ -186,11 +191,18 @@ func main() {
verFlag := flag.Bool("v", false, "Print version information and exit")
flag.BoolVar(&debug, "debug", false, "Turns on debug mode")
flag.Parse()
flagArgs := flag.Args()
if *verFlag {
fmt.Print(VersionString())
return
} else if len(flagArgs) > 0 {
s, err := readFile(ExpandedAbsFilepath(flagArgs[0]))
if err != nil {
fmt.Fprintf(os.Stderr, "felise could not read the given input file:\n `%s`\n", flagArgs[0])
os.Exit(1)
}
lexParseInterpret(s, NewEnv(nil), false)
} else {
repl()
}
repl()
}

34
test.fe Normal file
View File

@ -0,0 +1,34 @@
# comment test
proc fib
| calculates the nth fibonacci number
where n is an INT on TOS at call time |
INT var fib#
set fib#
fib# 0 = if 1 return .
fib# 1 = if 3 return .
INT var grandparent 1 set grandparent
INT var parent 3 set parent
INT var i 2 set i
INT var me
proc continue-loop?
i fib# < .
continue-loop?
while
3 parent *
grandparent -
set me
parent set grandparent
me set parent
i 1 + set i
continue-loop? .
me .
40 fib println