Skip to content

Commit

Permalink
Merge pull request #65 from skx/64-characters
Browse files Browse the repository at this point in the history
64 characters
  • Loading branch information
skx authored Oct 27, 2022
2 parents f7e77a4 + da91707 commit 8253a3c
Show file tree
Hide file tree
Showing 13 changed files with 541 additions and 35 deletions.
26 changes: 23 additions & 3 deletions PRIMITIVES.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ the help file, to see further details. This is just intended as a summary.
## Symbols

The only notable special symbols are the following strings which
represent the nil value. and ourboolean values.
represent the nil value. and our boolean values.

* `nil`
* The nil value.
Expand All @@ -28,7 +28,15 @@ represent the nil value. and ourboolean values.
* `#f`
* `false` is also available as an alias.

In the future we _might_ support characters, via \#A, etc.
Characters are specified via the `#\X` syntax, for escaped characters you just need to add the escape:

* `#\a` -> "a"
* `#\b` -> "b"
* ..
* `#\X` -> "X"
* `#\\n` -> newline
* `#\\t` -> tab




Expand Down Expand Up @@ -101,6 +109,16 @@ Things you'll find here include:
* Return the first item of a list.
* `cdr`
* Return all items of the list, except the first.
* `char=`
* Return true if the supplied values are characters, equal in value.
* `char<`
* Return true if the first character is less than the second.
* `char<=`
* Return true if the first character is less than, or equal to the second.
* `char>`
* Return true if the first character is greater than the second.
* `char>=`
* Return true if the first character is greater than, or equal to the second.
* `chr`
* Return the ASCII character of the given number.
* `cons`
Expand All @@ -120,6 +138,8 @@ Things you'll find here include:
* Return an error.
* `exists?`
* Does the given path exist?
* `explode`
* Convert the supplied string to a list of characters.
* `file?`
* Does the given path exist, and is it not a directory?
* `file:lines`
Expand Down Expand Up @@ -156,7 +176,7 @@ Things you'll find here include:
* `now`
* Return the number of seconds past the Unix Epoch.
* `ord`
* Return the ASCII code of the specified character.
* Return the ASCII code of the specified character, or the first character of the supplied string.
* `os`
* Return a string describing the current operating-system.
* `print`
Expand Down
104 changes: 101 additions & 3 deletions builtins/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ func PopulateEnvironment(env *env.Environment) {
env.Set("arch", &primitive.Procedure{F: archFn, Help: helpMap["arch"]})
env.Set("car", &primitive.Procedure{F: carFn, Help: helpMap["car"], Args: []primitive.Symbol{primitive.Symbol("list")}})
env.Set("cdr", &primitive.Procedure{F: cdrFn, Help: helpMap["cdr"], Args: []primitive.Symbol{primitive.Symbol("list")}})
env.Set("char=", &primitive.Procedure{F: charEqualsFn, Help: helpMap["char="], Args: []primitive.Symbol{primitive.Symbol("a"), primitive.Symbol("b")}})
env.Set("char<", &primitive.Procedure{F: charLtFn, Help: helpMap["char<"], Args: []primitive.Symbol{primitive.Symbol("a"), primitive.Symbol("b")}})
env.Set("chr", &primitive.Procedure{F: chrFn, Help: helpMap["chr"], Args: []primitive.Symbol{primitive.Symbol("num")}})
env.Set("cons", &primitive.Procedure{F: consFn, Help: helpMap["cons"], Args: []primitive.Symbol{primitive.Symbol("a"), primitive.Symbol("b")}})
env.Set("contains?", &primitive.Procedure{F: containsFn, Help: helpMap["contains?"], Args: []primitive.Symbol{primitive.Symbol("hash"), primitive.Symbol("key")}})
Expand All @@ -111,6 +113,7 @@ func PopulateEnvironment(env *env.Environment) {
env.Set("eq", &primitive.Procedure{F: eqFn, Help: helpMap["eq"], Args: []primitive.Symbol{primitive.Symbol("a"), primitive.Symbol("b")}})
env.Set("error", &primitive.Procedure{F: errorFn, Help: helpMap["error"], Args: []primitive.Symbol{primitive.Symbol("message")}})
env.Set("exists?", &primitive.Procedure{F: existsFn, Help: helpMap["exists?"], Args: []primitive.Symbol{primitive.Symbol("path")}})
env.Set("explode", &primitive.Procedure{F: explodeFn, Help: helpMap["explode"], Args: []primitive.Symbol{primitive.Symbol("string")}})
env.Set("file:lines", &primitive.Procedure{F: fileLinesFn, Help: helpMap["file:lines"], Args: []primitive.Symbol{primitive.Symbol("path")}})
env.Set("file:read", &primitive.Procedure{F: fileReadFn, Help: helpMap["file:read"], Args: []primitive.Symbol{primitive.Symbol("path")}})
env.Set("file:stat", &primitive.Procedure{F: fileStatFn, Help: helpMap["file:stat"], Args: []primitive.Symbol{primitive.Symbol("path")}})
Expand Down Expand Up @@ -191,6 +194,68 @@ func cdrFn(env *env.Environment, args []primitive.Primitive) primitive.Primitive
return primitive.Nil{}
}

// charEqualsFn implements "char="
func charEqualsFn(env *env.Environment, args []primitive.Primitive) primitive.Primitive {

// We need at least two arguments
if len(args) < 2 {
return primitive.ArityError()
}

// First argument must be a character
nA, ok := args[0].(primitive.Character)
if !ok {
return primitive.Error("argument was not a character")
}

// Now we'll loop over all other arguments
//
// If we got something that was NOT the same as our
// initial value we can terminate early but we don't
// because it is important to also report on failures to
// validate types - which we can't do if we bail.
//
ret := primitive.Bool(true)

for _, i := range args[1:] {

// check we have a character
nB, ok2 := i.(primitive.Character)

if !ok2 {
return primitive.Error("argument was not a character")
}

// Record our failure, but keep testing in case
// we have a type violation to report in a later
// argument.
if nB != nA {
ret = primitive.Bool(false)
}
}

return ret
}


// charLtFn implements (char<)
func charLtFn(env *env.Environment, args []primitive.Primitive) primitive.Primitive {
if len(args) != 2 {
return primitive.ArityError()
}

a, ok1 := args[0].(primitive.Character)
if !ok1 {
return primitive.Error("argument not a character")
}

b, ok2 := args[1].(primitive.Character);
if !ok2 {
return primitive.Error("argument not a character")
}
return primitive.Bool(a < b)
}

// chrFn is the implementation of (chr ..)
func chrFn(env *env.Environment, args []primitive.Primitive) primitive.Primitive {

Expand All @@ -205,7 +270,7 @@ func chrFn(env *env.Environment, args []primitive.Primitive) primitive.Primitive
i := args[0].(primitive.Number)
rune := rune(i)

return primitive.String(rune)
return primitive.Character(rune)
}

// consFn implements (cons).
Expand Down Expand Up @@ -508,6 +573,33 @@ func expandStr(input string) string {
return out
}

// explodeFn splits a string into a list of characters
func explodeFn(env *env.Environment, args []primitive.Primitive) primitive.Primitive {

// We only need a single argument
if len(args) != 1 {
return primitive.ArityError()
}

// Which is a string
str, ok := args[0].(primitive.String)
if !ok {
return primitive.Error("argument not a string")
}

// Split it
out := strings.Split(str.ToString(), "")

// return a list of characters
var c primitive.List

for _, x := range out {
c = append(c, primitive.Character(x))
}

return c
}

// expnFn implements "#"
func expnFn(env *env.Environment, args []primitive.Primitive) primitive.Primitive {
if len(args) != 2 {
Expand Down Expand Up @@ -1085,8 +1177,14 @@ func ordFn(env *env.Environment, args []primitive.Primitive) primitive.Primitive
return primitive.ArityError()
}

if _, ok := args[0].(primitive.String); !ok {
return primitive.Error("argument not a string")
// We work on strings, or characters
switch args[0].Type() {
case "character":
// nop
case "string":
// nop
default:
return primitive.Error(fmt.Sprintf("argument not a character/string, got %v", args[0].Type()))
}

// We convert this to an array of runes because we
Expand Down
Loading

0 comments on commit 8253a3c

Please sign in to comment.