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