diff --git a/README.md b/README.md deleted file mode 100644 index 50c6225..0000000 --- a/README.md +++ /dev/null @@ -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. diff --git a/doc/pattern.md b/doc/pattern.md deleted file mode 100644 index 61d6bef..0000000 --- a/doc/pattern.md +++ /dev/null @@ -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?")) -``` diff --git a/doc/runtime.md b/doc/runtime.md deleted file mode 100644 index 24d1dca..0000000 --- a/doc/runtime.md +++ /dev/null @@ -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 -] -``` diff --git a/doc/syntax.md b/doc/syntax.md deleted file mode 100644 index 9fc355e..0000000 --- a/doc/syntax.md +++ /dev/null @@ -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! -``` diff --git a/ginger.go b/ginger.go deleted file mode 100644 index 64cc5b2..0000000 --- a/ginger.go +++ /dev/null @@ -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) - } -} diff --git a/parse.go b/parse.go deleted file mode 100644 index 522c988..0000000 --- a/parse.go +++ /dev/null @@ -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 -} diff --git a/types.go b/types.go deleted file mode 100644 index 55f3d60..0000000 --- a/types.go +++ /dev/null @@ -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 - } -}