starting over
This commit is contained in:
parent
0eda1e5681
commit
98c2218239
16
README.md
16
README.md
@ -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.
|
|
207
doc/pattern.md
207
doc/pattern.md
@ -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?"))
|
|
||||||
```
|
|
187
doc/runtime.md
187
doc/runtime.md
@ -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
|
|
||||||
]
|
|
||||||
```
|
|
102
doc/syntax.md
102
doc/syntax.md
@ -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!
|
|
||||||
```
|
|
19
ginger.go
19
ginger.go
@ -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
338
parse.go
@ -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
|
|
||||||
}
|
|
47
types.go
47
types.go
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user