starting over

This commit is contained in:
Brian Picciano 2014-04-16 11:05:52 -05:00
parent 0eda1e5681
commit 98c2218239
7 changed files with 0 additions and 916 deletions

View File

@ -1,16 +0,0 @@
# Ginger
A scripted lisp language with simple syntax, immutable data structures, concurrency built-in, and
minimal time between starting the runtime and actual execution.
# Documentation
Documentation is broken up into different parts, more or less in the order a newcomer should read them to get familiar
with the language.
* [syntax/data structures](/doc/syntax.md) - Ginger is a lisp-based language, so if you know the syntax
for the available data structures you know the syntax for the language
itself. There's very few data structures, meaning there's minimal syntax.
* [runtime](/doc/runtime.md) - How to structure your ginger data to be runnable by the ginger interpreter.
Includes execution, variable definition, scope, and function definition.
* [pattern matching](/doc/pattern.md) - Deconstruction of data structures and case statements on them.

View File

@ -1,207 +0,0 @@
# Pattern Matching
Pattern matching in ginger is used both to assign variables and test for the contents of them.
## Assignment
The `=` function's job is to assign values to variables. In the simplest case:
```
(= Foo 5)
```
Assigns the value `5` to the variable named `Foo`. We can assign more complicated values to variables
as well:
```
(= Foo [1 2 3 4])
```
### Deconstruction
With pattern matching, we can deconstruct the value on the right side and assign its individual parts
to their own individual variables:
```
[
(= [Foo Bar [Baz Box] Bus] [1 a [3 4] [5 6]])
(println Foo)
(println Bar)
(println Baz)
(println Box)
(println Bus)
]
```
The above would print out:
```
1
a
3
4
[5 6]
```
We can also deconstruct a previously defined variable:
```
[
(= Foo [a b c])
(= [A B C] Foo)
]
```
The above would assign `a` to `A`, `b` to `B`, and `c` to `C`.
### No Match
What happens if we try to pattern match a value that doesn't match the left side?:
```
(= [Foo Bar Baz Box] [1 2 3]) ; => [error nomatch]
```
The value itself is a vector with three items, but we want a vector with four! This returns an error
and none of the variables were assigned.
### Blanks
If there's a variable we expect the right side to have, but we don't actually want to do anything with
it, we can use `_`:
```
(= [Foo _ Baz] [1 2 3])
```
The above would assign `1` to `Foo` and `2` to `Baz`.
### Left-side checks
The left side isn't exclusively used for assignment, it can also be used to check certain values on
the right:
```
(= [1 Foo 2 Bar] [1 a 2 b]) ; => success
(= [1 Foo 2 Bar] [1 a 3 b]) ; => [error nomatch]
```
If a variable is already defined it can be used as a check as well:
```
[
(= Foo 1)
(= [Foo Foo Bar] [1 1 2]) ; => success
(= [Foo Foo Baz] [1 2 3]) ; => [error nomatch]
]
```
Finally, undefined variables on the left side that are used more than once are ensured that all
instances have the same value:
```
[
(= [Foo Foo Foo] [foo foo foo]) ; => success
(= [Bar Bar Bar] [foo bar bar]) ; => [error nomatch]
]
```
### Vectors
What if we don't know the full length of the vector, but we only want to retrieve the first two values
from it?:
```
(= [Foo Bar -] [1 2 3 4 5])
```
The above would assign `1` to `Foo` and `2` to `Bar`. `-` is treated to mean "zero or more items in the sequence
that we don't care about".
What if you want to match on the tail end of the vector?:
```
(= [- Bar Foo] [1 2 3 4 5])
```
The above would assign `4` to Bar and `5` to `Foo`.
Can we do both?:
```
(= [Foo - Bar] [1 2 3 4 5])
```
The above would assign `1` to `Foo` and `5` to `Bar`.
#### Keeping the `-`
TODO
### Lists
Everything that applies to vectors applies to lists, as far as pattern matching goes:
```
(= (Foo Bar Baz -) (l (foo bar baz)))
```
In the above `foo` is assigned to `Foo`, `bar` to `Bar`, and `baz` to `Baz`.
Note that the right side needs the `l` literal wrapper to prevent evaluation, while
the left side does not. Nothing on the left will ever be evaluated, if you want it
to be dynamically defined you'll have to use a macro.
### Maps
Pattern matching can be performed on maps as well:
```
(= { Foo Bar } { a b }) ; => success
(= { Foo Bar } { a b c d }) ; => [error nomatch]
(= { Foo Bar - - } { a b c d }) ; => success
```
In the last case, the important thing is that the key is `-`. This tells the matcher
that you don't care about any other keys that may or may not exist. The value can be
anything, but `-` seems semantically more correct imo.
## Case
We can deconstruct and assign variables using `=`, and even test for their contents
to some degree since `=` returns either `success` or `[error nomatch]`. However
that's not very convenient. For this we have the `case` statement:
```
[
(= Foo bird)
(case Foo
bird (println "It's a bird!")
plane (println "It's a plane!")
_ (println "I don't know what it is!"))
]
```
In the above the output would be:
```
It's a bird!
```
`case` returns the result of whatever it matches:
```
[
(= Animal bird)
(case Animal
bird chirp
dog woof
lion roar) ; => chirp
]
```
`case` runs through all the different patterns in order, attempting to match on one
using an `=` on the backend. If none can be found it returns `[error nomatch]`. Since
`_` matches everything it can be used for any default statement.
Deconstruction works with `case` too:
```
(case [a b c]
[Foo c d] [foo Foo]
[Bar b c] [bar Bar]
_ baz) ; => [bar a]
```
### Guards
What's a case statement without guards? Nothing, that's what! Instead of evaluating the statement
following the pattern, if that statement is `when` followed by a statement returning a boolean,
finally followed by the actual statement, then boolean test must evaluate to `true` or the
case is skipped. The boolean test can use variables from the pattern that was matched:
```
(case [five 5]
(Name Num) when (> Num 2) (println Name "is greater than two")
(Name Num) (println Name "is less than or equal to two")
_ (println "wut?"))
```

View File

@ -1,187 +0,0 @@
# Runtime
Any ginger data-structure can be put into a ginger file. The data itself has no executional meaning on its own, but if
the data is properly formed it can be parsed by the ginger interpreter and a thread of execution can be started. For
example, if I put the following in a file called `test.gng`:
```
(1 2 3)
```
It doesn't have any meaning, it's just a list. However, if you put the following:
```
(add 1 2 3)
```
and run `ginger test.gng` then a program can be interpreted from the given data. This file describes how given data can
be formed into a valid program.
## Eval
Ginger evaluation is done the same was as other lisp languages: the first item in a list is the function name, the rest
of the items in the list are arguments to the function. In the example above, `add` is the function name, and `1`, `2`,
and `3` are arguments to the function.
Arguments to a function can be functions to be eval'd themselves. An equivalent to the example above would have been:
```
(add 1 2 (add 1 2))
```
## Doing multiple things
It's not very useful to only be able to do one thing. A vector of lists is interpreted into sequentially eval'ing each
item in the vector. For (a trivial) example:
```
[ (add 1 2)
(sub 4 (add 1 2))
(mul 8 0) ]
```
## Variables/Scope
The above example does a few things, but it repeats itself in the second part (with the `sub`). If we could save the
result of the addition to a variable that would be awesomesauce. The `=` function does this! It makes it so that the
first argument is equivalent to the evaluation of the second argument. Variables' can not be re-defined to be another
value, and their first letters must be upper-case, this is how they're differentiated from raw string literals.The above
could be re-written as:
```
[ (= AdditionResult (add 1 2))
(sub 4 AdditionResult)
(mul 8 0) ]
```
In the above example `AdditionResult` is a valid variable inside of the vector that contains its declaration, and any
vectors contained within that vector. For example:
```
[ (= AdditionResult (add 1 2))
[ (sub 4 AdditionResult) ;This works
(mul 8 0) ] ]
[ (add 4 AdditionResult) ] ;This does not work!
```
## Literals
We've determined that a list is interpreted as a function/arguments set, and an upper-case string is interpreted as a
variable name which will be de-referenced. What if we want to actually use these structures without eval'ing them? For
these cases we have the literal function `l`:
```
[ (concat (l (1 2 3)) (l (4 5 6))) ; => (1 2 3 4 5 6)
(println (l "I start with a capital letter and I DONT CARE!!!")) ]
```
## Functions
### Anonymous
Anonymous functions are declared using the `fn` function. The first argument is a vector of argument names (remember,
all upper-case!) and the second is a list/vector to be eval'd. Examples:
```
[ (= Add3 (fn [Num] (add Num 3)))
(Add3 4) ; => 7
(= Add3Sub1
(fn [Num] [
(= Added (add Num 3))
(sub Added 1)
]))
(Add3Sub1 4) ] ; => 6
```
`fn` returns a function, which can be passed around and assigned like any other value. In the above examples the
functions are assigned to the `Add3` and `Add3Sub1` variables. Functions can also be passed into other functions as
arguments:
```
;DoTwice takes a function Fun and a number Num. it will call Fun twice on Num and return the result.
[ (= DoTwice
(fn [Fun Num]
(Fun (Fun Num))))
(DoTwice (fn [Num] (add Num 1)) 3) ] ; => 5
```
### Defined
Defined functions attach the function definition to a string literal (lower-case) in the current scope. They are useful
as they support more features then an anonymous function, such as inline documentation. These extra features will be
documented elsewhere. To create a defined function use the `dfn` function:
```
[ (dfn add-four-sub-three [Num] [
(= A (add Num 4))
(sub A 3) ])
(add-four-sub-three 4) ] ; => 5
```
## Namespaces
Namespaces give names to defined scopes which can be referenced from elsewhere. The best way to show this is with an
example:
```
[
(ns circle [
(= Pi 3.14)
(dfn area [R] (mul R R Pi)) ])
(circle/area 5) ; => 78.5
]
```
### Embedded namespaces
Namespaces can be embedded into a tree-like structure:
```
[
(ns math [
(= Pi 3.14)
(ns circle
(dfn area [R] (mul R R Pi)))
(ns square
(dfn area [R] (mul R R)))
])
(math.circle/area 5) ; => 78.5
(math.square/area 5) ; => 25
]
```
In the above example `circle` and `square` are both sub-namespaces of the `math` namespace. In `circle` the variable
`Pi` is referenced. Ginger will look in the current scope for that variable, and when it's not found travel up the scope
tree. `Pi` is defined in `math`'s scope, so that value is used.
### Namespace resolution
Let's do a more complicated example to show how namespace resolution works:
```
[
(ns tlns [
(ns alpha
(= A "The first letter in the alphabet"))
(ns facts
(= BestLetter alpha/A))
])
(println tlns.facts/BestLetter)
]
```
In the above example `BestLetter` is defined inside `facts` to be the variable `A` which exists in the namespace `alpha`.
To resolve this variable ginger first looks inside `facts`'s scope for a namespace called `alpha`, doesn't find it, then
moves up to `tlns`'s scope, which does contain a namespace called `alpha`. Similaraly, to resolve `tlns.facts/BestLetter`
ginger first looks in that statements current scope for a namespace called `tlns`, which it finds, and looks in there
for `facts`, etc...
### Variable namespaces
A namespace is nothing more than a string literal. If a variable is used instead ginger will resolve the variable before
trying to resolve the namespace.
```
[
(ns tlns [
(ns alpha
(= A "The first letter in the alphabet"))])
(= NS1 tlns)
(= NS2 alpha)
(= NS NS1.NS2)
(println NS/A) ; This would print the message defined above
]
```

View File

@ -1,102 +0,0 @@
# Syntax
Ginger is a lisp language, so knowing the syntax is as simple as knowing the data structures.
## Strings
Strings are declared two different ways. The standard way, with double quotes:
```
"this is a string\n <- that was a newline, \t \r \0 \" \\ also work
literal whitespace characters are properly parsed as well
\s is a space"
```
The second way only works if your string contains exclusively the following characters:
`a-z A-Z 0-9 _ - ! ? . /` (spaces added for readability)
```
neat
this_works
so-does-this!
what-about-this?_YUP!
/directory/with/a/file.ext
```
## Integers
Integers are defined the standard way, a bunch of numbers with an optional negative. The only
interesting thing is that commas inside the number are ignored, so you can make your literals pretty:
```
0
1
-2
4,000,000
```
## Floats
Pretty much the same as integers, but with a period thrown in there. If there isn't a period, it's not
a float:
```
0.0
-1.5
-1,003.004,333,203
```
## Bytes
Singular unsigned bytes, are also supported. There are two ways to declare them. With a trailing `b`:
```
0b
10b
255b
```
and with a `'` followed by a character (or escaped character):
```
'c
'h
'\n
'\\
''
```
## Vectors
A vector is a sequence of elements wrapped in `[ ... ]` (no commas):
```
[ a b 0 1 2
[embedded also works]
]
```
## Lists
A list is a sequence of elements wrapped in `( ... )` (no commas):
```
( a b 0 1 2
[embedded also works]
(and mixed types)
)
```
## Maps
A map is a sequence of elements wrapped in `{ ... }`. There must be an even number of elements, and
there is no delimeter between the keys and values. Keys can be any non-sequence variable, values can
be anything at all:
```
{ a 1
b 2
c [1 2 3]
d (four five six)
e { 7 seven } }
```
## Comments
A semicolon delimits the beginning of a comment. Anything after the semicolon till the end of the line
is discarded by the parser:
```
;I'm a comment
"I'm a string" ;I'm another comment!
```

View File

@ -1,19 +0,0 @@
package main
import (
"os"
"bufio"
"fmt"
)
func main() {
file, err := os.Open("test.gng")
if err != nil { panic(err) }
rbuf := bufio.NewReader(file)
for {
el,err := PullElement(rbuf)
if err != nil { break }
fmt.Printf("%s\n",el)
}
}

338
parse.go
View File

@ -1,338 +0,0 @@
package main
import (
"bufio"
"fmt"
"strconv"
)
type CharComp interface {
includes(byte) bool
}
//A single character
type Char byte
func (c Char) includes(comp byte) bool { return byte(c) == comp }
//The starting and ending chars (inclusive) in a range of chars
type CharRange struct {
start byte
end byte
}
func (cr CharRange) includes (comp byte) bool {
return byte(cr.start) <= comp && byte(cr.end) >= comp
}
//A set of char comparables. Could contain other CharSets
type CharSet []CharComp
func (cs CharSet) includes (comp byte) bool {
for _,c := range cs {
if c.includes(comp) { return true }
}
return false
}
var ASCII = CharRange{0,127}
var Whitespace = CharSet{
CharRange{9,13}, //tab -> carriage return
Char(' '),
}
var Letters = CharSet{
CharRange{65,90}, //upper
CharRange{97,122}, //lower
}
var Numbers = CharRange{48,57}
//For the simple string syntax (no d-quotes)
var SimpleChars = CharSet{
Letters,
Numbers,
Char('-'),
Char('_'),
Char('!'),
Char('?'),
Char('.'),
Char('/'),
}
//For translating the special characters
var SpecialCharsMap = map[byte]byte{
'\\': '\\',
'"' : '"',
'\'': '\'',
'n' : '\n',
'r' : '\r',
't' : '\t',
's' : ' ',
'0' : 0,
}
//For knowing what the other end of the seq should be
var SeqMap = map[byte]byte{
'(': ')',
'[': ']',
'{': '}',
}
//For simple unsigned integers
var UintChars = CharSet{
Numbers,
Char(','),
}
func FirstByte(rbuf *bufio.Reader) (byte,error) {
b,err := rbuf.Peek(1)
if err != nil { return 0,err }
return b[0],err
}
//Looks at (but doesn't consume) the first uncommented byte in the buffer
func FirstUncommentedByte(rbuf *bufio.Reader) (byte,error) {
b,err := FirstByte(rbuf)
if err != nil {
return 0,err
} else if b == ';' {
rbuf.ReadLine()
return FirstUncommentedByte(rbuf)
}
return b,err
}
func PrettyChar(c byte) string {
switch c {
case '\n': return "newline"
case '\t': return "tab"
case '\r': return "carriage return"
case 0: return "null byte"
default: return string(c)
}
}
func ExpectedPanic(rbuf *bufio.Reader,givenErr error,expected string) {
if givenErr != nil {
panic("Expected "+expected+", but found:"+givenErr.Error())
}
b,err := FirstByte(rbuf)
if err != nil {
panic("Expected "+expected+", but found:"+err.Error())
} else {
panic("Expected "+expected+", but found:"+PrettyChar(b))
}
}
func ConcatByteSlices(a,b []byte) []byte {
c := make([]byte,len(a)+len(b))
copy(c,a)
copy(c[len(a):],b)
return c
}
func PullWhitespace(rbuf *bufio.Reader) (int,error) {
var i int
for i=0;;i++{
b,err := FirstUncommentedByte(rbuf)
if err != nil { return i,err }
if Whitespace.includes(b) != true { return i,nil }
rbuf.ReadByte()
}
return i,nil
}
func PullUint(rbuf *bufio.Reader) ([]byte,error) {
r := make([]byte,0,16)
for {
b,err := FirstUncommentedByte(rbuf)
if err != nil { return r,err }
if UintChars.includes(b) != true { return r,nil }
if b != ',' { r = append(r,b) }
rbuf.ReadByte()
}
}
func PullInteger(rbuf *bufio.Reader) ([]byte,error) {
neg := false
nb,err := FirstUncommentedByte(rbuf)
if err != nil { return nil,err }
if nb == '-' {
neg = true
rbuf.ReadByte()
}
ui,err := PullUint(rbuf)
if len(ui) == 0 || err != nil { return ui,err }
var r []byte
if neg {
r = ConcatByteSlices([]byte{'-'},ui)
} else {
r = ui
}
return r,nil
}
func PullFullString(rbuf *bufio.Reader) ([]byte,error) {
r := make([]byte,0,256)
//Make sure string starts with dquote
dq,err := FirstUncommentedByte(rbuf)
if err != nil { return nil,err }
if dq != '"' { return r,nil }
rbuf.ReadByte()
for {
b,err := FirstByte(rbuf)
if err != nil { return r,err }
if b == '"' {
rbuf.ReadByte()
return r,nil
} else if b == '\\' {
rbuf.ReadByte()
ec,err := FirstByte(rbuf)
if err != nil { return r,err }
rbuf.ReadByte()
if c,ok := SpecialCharsMap[ec]; ok {
r = append(r,c)
} else {
r = append(r,ec)
}
} else {
rbuf.ReadByte()
r = append(r,b)
}
}
}
func PullSimpleString(rbuf *bufio.Reader) ([]byte,error) {
r := make([]byte,0,16)
for {
b,err := FirstUncommentedByte(rbuf)
if err != nil { return r,err }
if SimpleChars.includes(b) {
rbuf.ReadByte()
r = append(r,b)
} else {
return r,nil
}
}
}
func PullByte(rbuf *bufio.Reader) ([]byte,error) {
sq,err := FirstUncommentedByte(rbuf)
if err != nil { return nil,err }
if sq != '\'' { return []byte{},nil }
rbuf.ReadByte()
var rb byte
b,err := FirstByte(rbuf)
if err != nil { return nil,err }
if b == '\\' {
rbuf.ReadByte()
ec,err := FirstByte(rbuf)
if err != nil { return nil,err }
if c,ok := SpecialCharsMap[ec]; ok {
rb = c
} else {
return []byte{},nil
}
} else if ASCII.includes(b) {
rb = b
} else {
return []byte{},nil
}
rbuf.ReadByte()
rstr := strconv.Itoa(int(rb))
return []byte(rstr),nil
}
func PullSeq(rbuf *bufio.Reader) ([]GngType,error) {
d,err := FirstUncommentedByte(rbuf)
if err != nil { return nil,err }
od,ok := SeqMap[d]
if !ok { return nil,fmt.Errorf("Unknown seq type") }
rbuf.ReadByte()
r := make([]GngType,0,16)
for {
b,err := FirstUncommentedByte(rbuf)
if err != nil {
return r,err
} else if (b == od) {
rbuf.ReadByte()
return r,nil
} else if Whitespace.includes(b) {
_,err := PullWhitespace(rbuf)
if err != nil { return r,err }
} else {
el,err := PullElement(rbuf)
if err != nil { return r,err }
r = append(r,el)
}
}
}
func PullElement(rbuf *bufio.Reader) (GngType,error) {
for {
b,err := FirstUncommentedByte(rbuf)
if err != nil {
return nil,err
} else if Whitespace.includes(b) {
rbuf.ReadByte() //ignore
} else if Numbers.includes(b) || b == '-' {
n,err := PullInteger(rbuf)
if len(n) == 0 || err != nil { ExpectedPanic(rbuf,err,"number") }
fb,err := FirstByte(rbuf)
if err != nil { break }
if fb == '.' {
rbuf.ReadByte()
ui,err := PullUint(rbuf)
if len(ui) == 0 || err != nil { ExpectedPanic(rbuf,err,"number") }
ui = ConcatByteSlices([]byte{'.'},ui)
n = ConcatByteSlices(n,ui)
return NewGngFloat(n),nil
} else if fb == 'b' {
rbuf.ReadByte()
return NewGngByte(n),nil
} else {
return NewGngInteger(n),nil
}
} else if b == '"' {
s,err := PullFullString(rbuf)
if len(s) == 0 || err != nil { ExpectedPanic(rbuf,err,"end of string") }
return NewGngString(s),nil
} else if b == '\'' {
b,err := PullByte(rbuf)
if len(b) == 0 || err != nil { ExpectedPanic(rbuf,err,"character") }
return NewGngByte(b),nil
} else if Letters.includes(b) {
s,err := PullSimpleString(rbuf)
if len(s) == 0 || err != nil { ExpectedPanic(rbuf,err,"end of string") }
return NewGngString(s),nil
} else if _,ok := SeqMap[b]; ok {
s,err := PullSeq(rbuf)
if err != nil { ExpectedPanic(rbuf,err,"end of seq") }
switch(b) {
case '(': return NewGngList(s)
case '[': return NewGngVector(s)
case '{': return NewGngMap(s)
default: panic("Unknown sequence type:"+string(b))
}
} else {
ExpectedPanic(rbuf,nil,"start of valid data structure")
}
}
return nil,nil
}

View File

@ -1,47 +0,0 @@
package main
import (
"strconv"
"fmt"
)
type GngType interface {}
type GngString string
func NewGngString(b []byte) GngString { return GngString(b) }
type GngByte byte
func NewGngByte(b []byte) GngByte {
i,err := strconv.Atoi(string(b))
if err != nil { panic(err) }
return GngByte(i)
}
type GngInteger int64
func NewGngInteger(b []byte) GngInteger {
i,err := strconv.Atoi(string(b))
if err != nil { panic(err) }
return GngInteger(i)
}
type GngFloat float64
func NewGngFloat(b []byte) GngFloat {
f,err := strconv.ParseFloat(string(b),64)
if err != nil { panic(err) }
return GngFloat(f)
}
type GngVector []GngType
func NewGngVector(e []GngType) (GngVector,error) { return GngVector(e),nil }
type GngList []GngType
func NewGngList(e []GngType) (GngList,error) { return GngList(e),nil }
type GngMap []GngType
func NewGngMap(e []GngType) (GngMap,error) {
if len(e)%2 != 0 {
return nil,fmt.Errorf("uneven number of elements in map literal")
} else {
return GngMap(e),nil
}
}