Skip to content

Commit

Permalink
Merge pull request #126 from skx/115-hash
Browse files Browse the repository at this point in the history
Add md5/sha1/sha256 digest functions.
  • Loading branch information
skx authored Feb 12, 2023
2 parents 63b58b3 + 76c7efe commit 0f8f01a
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 8 deletions.
6 changes: 6 additions & 0 deletions PRIMITIVES.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ Things you'll find here include:
* Create a new list.
* `match`
* Perform a regular expression test.
* `md5`
* Return the MD5 digest of the given string.
* `ms`
* Return the time, in milliseconds.
* `nil?`
Expand All @@ -215,6 +217,10 @@ Things you'll find here include:
* Output the specified string, or format string + values.
* `set`
* Update the value of the specified hash-key.
* `sha1`
* Return the SHA1 digest of the given string.
* `sha256`
* Return the SHA256 digest of the given string.
* `shell`
* Run a command via the shell, and return STDOUT and STDERR it generated.
* `sin`
Expand Down
48 changes: 48 additions & 0 deletions builtins/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ package builtins
import (
"bufio"
"bytes"
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"fmt"
"math"
"math/rand"
Expand Down Expand Up @@ -135,6 +138,7 @@ func PopulateEnvironment(env *env.Environment) {
env.Set("keys", &primitive.Procedure{F: keysFn, Help: helpMap["keys"], Args: []primitive.Symbol{primitive.Symbol("hash")}})
env.Set("list", &primitive.Procedure{F: listFn, Help: helpMap["list"], Args: []primitive.Symbol{primitive.Symbol("arg1"), primitive.Symbol("arg...")}})
env.Set("match", &primitive.Procedure{F: matchFn, Help: helpMap["match"], Args: []primitive.Symbol{primitive.Symbol("regexp"), primitive.Symbol("str")}})
env.Set("md5", &primitive.Procedure{F: md5Fn, Help: helpMap["md5"], Args: []primitive.Symbol{primitive.Symbol("string")}})
env.Set("ms", &primitive.Procedure{F: msFn, Help: helpMap["ms"]})
env.Set("nil?", &primitive.Procedure{F: nilFn, Help: helpMap["nil?"], Args: []primitive.Symbol{primitive.Symbol("object")}})
env.Set("now", &primitive.Procedure{F: nowFn, Help: helpMap["now"]})
Expand All @@ -145,6 +149,8 @@ func PopulateEnvironment(env *env.Environment) {
env.Set("print", &primitive.Procedure{F: printFn, Help: helpMap["print"], Args: []primitive.Symbol{primitive.Symbol("arg1..argN")}})
env.Set("random", &primitive.Procedure{F: randomFn, Help: helpMap["random"], Args: []primitive.Symbol{primitive.Symbol("max")}})
env.Set("set", &primitive.Procedure{F: setFn, Help: helpMap["set"], Args: []primitive.Symbol{primitive.Symbol("hash"), primitive.Symbol("key"), primitive.Symbol("val")}})
env.Set("sha1", &primitive.Procedure{F: sha1Fn, Help: helpMap["sha1"], Args: []primitive.Symbol{primitive.Symbol("string")}})
env.Set("sha256", &primitive.Procedure{F: sha256Fn, Help: helpMap["sha256"], Args: []primitive.Symbol{primitive.Symbol("string")}})
env.Set("shell", &primitive.Procedure{F: shellFn, Help: helpMap["shell"], Args: []primitive.Symbol{primitive.Symbol("list")}})
env.Set("sin", &primitive.Procedure{F: sinFn, Help: helpMap["sin"], Args: []primitive.Symbol{primitive.Symbol("n")}})
env.Set("sinh", &primitive.Procedure{F: sinhFn, Help: helpMap["sinh"], Args: []primitive.Symbol{primitive.Symbol("n")}})
Expand Down Expand Up @@ -1277,6 +1283,20 @@ func modFn(env *env.Environment, args []primitive.Primitive) primitive.Primitive
return primitive.Number(a % b)
}

// md5Fn is the implementation of `(md5)`
func md5Fn(env *env.Environment, args []primitive.Primitive) primitive.Primitive {
// We need one argument
if len(args) != 1 {
return primitive.ArityError()
}

// The argument must be a string
str := args[0].ToString()

// Get the output
return primitive.String(fmt.Sprintf("%X", md5.Sum([]byte(str))))
}

// msFn is the implementation of `(ms)`
func msFn(env *env.Environment, args []primitive.Primitive) primitive.Primitive {
return primitive.Number(time.Now().UnixNano() / int64(time.Millisecond))
Expand Down Expand Up @@ -1545,6 +1565,34 @@ func setFn(env *env.Environment, args []primitive.Primitive) primitive.Primitive
return args[2]
}

// sha1Fn runs a SHA1 hash
func sha1Fn(env *env.Environment, args []primitive.Primitive) primitive.Primitive {
// We need one argument
if len(args) != 1 {
return primitive.ArityError()
}

// The argument must be a string
str := args[0].ToString()

// Get the output
return primitive.String(fmt.Sprintf("%X", sha1.Sum([]byte(str))))
}

// sha256Fn runs a SHA256 hash
func sha256Fn(env *env.Environment, args []primitive.Primitive) primitive.Primitive {
// We need one argument
if len(args) != 1 {
return primitive.ArityError()
}

// The argument must be a string
str := args[0].ToString()

// Get the output
return primitive.String(fmt.Sprintf("%X", sha256.Sum256([]byte(str))))
}

// shellFn runs a command via the shell
func shellFn(env *env.Environment, args []primitive.Primitive) primitive.Primitive {

Expand Down
41 changes: 41 additions & 0 deletions builtins/builtins_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1907,6 +1907,47 @@ func TestGlob(t *testing.T) {

}

// TestHash tests our hash functions
func TestHash(t *testing.T) {

funs := []primitive.GolangPrimitiveFn{
md5Fn,
sha1Fn,
sha256Fn,
}

for _, fn := range funs {

// No eargs
out := fn(nil, []primitive.Primitive{})

// Will lead to an error
e, ok := out.(primitive.Error)
if !ok {
t.Fatalf("expected error, got %v", out)
}
if e != primitive.ArityError() {
t.Fatalf("got error, but wrong one")

}

// Hash a string
out = fn(ENV, []primitive.Primitive{
primitive.String("foo"),
})

// Will lead to an error
r, ok2 := out.(primitive.String)
if !ok2 {
t.Fatalf("expected string, got %v", r)
}

if len(r) < 15 {
t.Fatalf("result '%s' was the wrong length", r)
}
}
}

// TestHelp tests help
func TestHelp(t *testing.T) {
// no arguments
Expand Down
24 changes: 24 additions & 0 deletions builtins/help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,14 @@ Any matches found will be returned as a list, with nil being returned on no matc

Example: (print (match "c.ke$" "cake"))
%%
md5

md5 returns the calculated MD5 digest of the provived string

See also: sha1, sha256

Example: (print (md5 "steve"))
%%
ms

ms returns the current time as a number of milliseconds, it is useful for benchmarking.
Expand Down Expand Up @@ -331,6 +339,22 @@ See also: get
Example: (set! person {:name "Steve"})
(set person :name "Bobby")
%%
sha1

sha1 returns the calculated SHA1 digest of the provived string

See also: md5sum, sha256

Example: (print (sha1 "steve"))
%%
sha256

sha256 returns the calculated SHA256 digest of the provived string

See also: md5sum, sha1

Example: (print (sha256 "steve"))
%%
shell

shell allows you to run a command, via the shell.
Expand Down
9 changes: 1 addition & 8 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,11 @@ import (
"bufio"
"flag"
"fmt"
"math/rand"
"os"
"path"
"regexp"
"sort"
"strings"
"time"

"github.com/skx/yal/builtins"
"github.com/skx/yal/config"
Expand Down Expand Up @@ -197,9 +195,6 @@ func help(show []string) {

func main() {

// (gensym) needs a decent random seed, as does (random).
rand.Seed(time.Now().UnixNano())

// Parse our command-line flags
exp := flag.String("e", "", "A string to evaluate.")
hlp := flag.Bool("h", false, "Show help information and exit.")
Expand Down Expand Up @@ -230,7 +225,6 @@ func main() {
//
create()


//
// By default we have no STDERR handler wired up, but if we set the
// debug flag we'll send that to the actual console's STDERR stream
Expand All @@ -240,13 +234,12 @@ func main() {
iohelper := ENV.GetIOConfig()

// Setup a destination for STDERR
iohelper.STDERR =os.Stderr
iohelper.STDERR = os.Stderr

// Update
ENV.SetIOConfig(iohelper)
}


// LSP?
if *lsp {
lspStart()
Expand Down

0 comments on commit 0f8f01a

Please sign in to comment.