From c5e7303c68530199c9ad9b5e5ddf53266c7e744a Mon Sep 17 00:00:00 2001 From: Steve Kemp Date: Sun, 12 Feb 2023 09:21:10 +0200 Subject: [PATCH 1/2] Add md5/sha1/sha256 digest functions. As inspired by Owl. This is part of #115. --- PRIMITIVES.md | 6 +++++ builtins/builtins.go | 48 +++++++++++++++++++++++++++++++++++++++ builtins/builtins_test.go | 41 +++++++++++++++++++++++++++++++++ builtins/help.txt | 24 ++++++++++++++++++++ 4 files changed, 119 insertions(+) diff --git a/PRIMITIVES.md b/PRIMITIVES.md index dc259a7..d782a42 100644 --- a/PRIMITIVES.md +++ b/PRIMITIVES.md @@ -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?` @@ -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` diff --git a/builtins/builtins.go b/builtins/builtins.go index 3ccb3e6..e82ab42 100644 --- a/builtins/builtins.go +++ b/builtins/builtins.go @@ -8,6 +8,9 @@ package builtins import ( "bufio" "bytes" + "crypto/md5" + "crypto/sha1" + "crypto/sha256" "fmt" "math" "math/rand" @@ -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"]}) @@ -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")}}) @@ -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)) @@ -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 { diff --git a/builtins/builtins_test.go b/builtins/builtins_test.go index 744913e..c827d6c 100644 --- a/builtins/builtins_test.go +++ b/builtins/builtins_test.go @@ -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 diff --git a/builtins/help.txt b/builtins/help.txt index dc3c0b4..6fe9386 100644 --- a/builtins/help.txt +++ b/builtins/help.txt @@ -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. @@ -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. From 76c7efec38983770af946bc922799ca4d2fa52ec Mon Sep 17 00:00:00 2001 From: Steve Kemp Date: Sun, 12 Feb 2023 09:24:49 +0200 Subject: [PATCH 2/2] Avoid calling rand.Seed() - depreciated since go 1.20 --- main.go | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/main.go b/main.go index b72e5f4..0be025b 100644 --- a/main.go +++ b/main.go @@ -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" @@ -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.") @@ -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 @@ -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()