Skip to content

Commit

Permalink
Merge pull request #67 from skx/66-formatting
Browse files Browse the repository at this point in the history
Allow the use of real/typed format strings
  • Loading branch information
skx authored Oct 27, 2022
2 parents 8253a3c + 718ab74 commit 78bdd79
Show file tree
Hide file tree
Showing 17 changed files with 168 additions and 57 deletions.
2 changes: 1 addition & 1 deletion args.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
;;

;; Show the count.
(print "I received %s command-line arguments." (length os.args))
(print "I received %d command-line arguments." (length os.args))

;; Show the actual arguments
(print "Args: %s" os.args)
Expand Down
29 changes: 19 additions & 10 deletions builtins/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,6 @@ func charEqualsFn(env *env.Environment, args []primitive.Primitive) primitive.Pr
return ret
}


// charLtFn implements (char<)
func charLtFn(env *env.Environment, args []primitive.Primitive) primitive.Primitive {
if len(args) != 2 {
Expand All @@ -249,7 +248,7 @@ func charLtFn(env *env.Environment, args []primitive.Primitive) primitive.Primit
return primitive.Error("argument not a character")
}

b, ok2 := args[1].(primitive.Character);
b, ok2 := args[1].(primitive.Character)
if !ok2 {
return primitive.Error("argument not a character")
}
Expand Down Expand Up @@ -1252,11 +1251,16 @@ func printFn(env *env.Environment, args []primitive.Primitive) primitive.Primiti
frmt := expandStr(args[0].ToString())
parm := []any{}

for i, a := range args {
if i == 0 {
continue
for _, a := range args[1:] {

// If we can use ToNative then do that,
// otherwise fall back to outputting a string.
native, ok := a.(primitive.ToNative)
if ok {
parm = append(parm, native.ToInterface())
} else {
parm = append(parm, a.ToString())
}
parm = append(parm, a.ToString())
}

out := fmt.Sprintf(frmt, parm...)
Expand Down Expand Up @@ -1425,11 +1429,16 @@ func sprintfFn(env *env.Environment, args []primitive.Primitive) primitive.Primi
frmt := expandStr(args[0].ToString())
parm := []any{}

for i, a := range args {
if i == 0 {
continue
for _, a := range args[1:] {

// If we can use ToNative then do that,
// otherwise fall back to outputting a string.
native, ok := a.(primitive.ToNative)
if ok {
parm = append(parm, native.ToInterface())
} else {
parm = append(parm, a.ToString())
}
parm = append(parm, a.ToString())
}

out := fmt.Sprintf(frmt, parm...)
Expand Down
38 changes: 35 additions & 3 deletions builtins/builtins_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2717,18 +2717,36 @@ func TestPrint(t *testing.T) {
}

// Two argument
out = printFn(ENV, []primitive.Primitive{
primitive.String("Hello %d!"),
primitive.Number(3),
})

e2, ok2 = out.(primitive.String)
if !ok2 {
t.Fatalf("expected string, got %v", out)
}
if e2 != "Hello 3!" {
t.Fatalf("got string, but wrong one %v", e2)
}

// Two argument with a type that can't be native-converated
out = printFn(ENV, []primitive.Primitive{
primitive.String("Hello %s!"),
primitive.String("Steve"),
primitive.List{
primitive.Number(3),
primitive.Number(4),
},
})

e2, ok2 = out.(primitive.String)
if !ok2 {
t.Fatalf("expected string, got %v", out)
}
if e2 != "Hello Steve!" {
t.Fatalf("got error, but wrong one %v", e2)
if e2 != "Hello (3 4)!" {
t.Fatalf("got string, but wrong one %v", e2)
}

}

// TestRandom tests (random)
Expand Down Expand Up @@ -3008,6 +3026,20 @@ func TestSprintf(t *testing.T) {
if e2 != "Hello\t\"world\"\n\r!" {
t.Fatalf("got wrong result %v", e2)
}

// Two arguments with a native mapping
out = sprintfFn(ENV, []primitive.Primitive{
primitive.String("Hello %d!"),
primitive.Number(3),
})

e2, ok2 = out.(primitive.String)
if !ok2 {
t.Fatalf("expected string, got %v", out)
}
if e2 != "Hello 3!" {
t.Fatalf("got string, but wrong one %v", e2)
}
}

func TestStr(t *testing.T) {
Expand Down
16 changes: 16 additions & 0 deletions builtins/help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,14 @@ print

print is used to output text to the console. It can be called with either an object/string to print, or a format-string and list of parameters.

When a format string is used it can contain the following strings:

%c -> output a character value.
%d -> output an integer.
%f -> output a floating-point number.
%s -> output a string.
%t -> output a boolean value.

See also: sprintf
Example: (print "Hello, world")
Example: (print "Hello user %s you are %d" (getenv "USER") 32)
Expand Down Expand Up @@ -303,6 +311,14 @@ sprintf

sprintf allows formating values with a simple format-string.

When a format string is used it can contain the following strings:

%c -> output a character value.
%d -> output an integer.
%f -> output a floating-point number.
%s -> output a string.
%t -> output a boolean value.

See also: print
Example: (sprintf "Today is %s" (weekday))
%%
Expand Down
10 changes: 5 additions & 5 deletions fibonacci.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@
(set! add-numeric-suffix (fn* (n)
"Add a trailing suffix to make a number readable."
(cond
(match "(^|[^1]+)1$" n) (sprintf "%sst" n)
(match "(^|[^1]+)2$" n) (sprintf "%snd" n)
(match "(^|[^1]+)3$" n) (sprintf "%srd" n)
true (sprintf "%sth" n)
(match "(^|[^1]+)1$" n) (sprintf "%dst" n)
(match "(^|[^1]+)2$" n) (sprintf "%dnd" n)
(match "(^|[^1]+)3$" n) (sprintf "%drd" n)
true (sprintf "%dth" n)
)))

;; Fibonacci function
Expand All @@ -37,5 +37,5 @@
;; Now call our function in a loop, twenty times.
(let* (n 1)
(while (<= n 25)
(print "%s fibonacci number is %s" (add-numeric-suffix n) (fibonacci n))
(print "%s fibonacci number is %d" (add-numeric-suffix n) (fibonacci n))
(set! n (+ n 1) true)))
2 changes: 1 addition & 1 deletion fizzbuzz.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ and 'fizzbuzz' when divisible by both."
(= 0 (% n 15)) "fizzbuzz"
(= 0 (% n 3)) "fizz"
(= 0 (% n 5)) "buzz"
true n) )))
true (str n)) )))


;; As you can see the function above contains some help-text, or overview.
Expand Down
2 changes: 1 addition & 1 deletion hash.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

;; This function is used as a callback by apply-hash.
(set! hash-element (fn* (key val)
(print "KEY:%s VAL:%s" key val)))
(print "KEY:%s VAL:%s" key (str val))))

;; The `apply-hash` function will trigger a callback for each key and value
;; within a hash.
Expand Down
5 changes: 5 additions & 0 deletions primitive/bool.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ package primitive
// Bool is our wrapping of bool
type Bool bool

// ToInterface converts this object to a golang value
func (b Bool) ToInterface() any {
return bool(b)
}

// ToString converts this object to a string.
func (b Bool) ToString() string {
if b {
Expand Down
8 changes: 8 additions & 0 deletions primitive/character.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ package primitive
// Character holds a string value.
type Character string

// ToInterface converts this object to a golang value
func (c Character) ToInterface() any {
if len(c) > 0 {
return c[0]
}
return ""
}

// ToString converts this object to a string.
func (c Character) ToString() string {
return string(c)
Expand Down
7 changes: 7 additions & 0 deletions primitive/error.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
package primitive

import "fmt"

// Error holds an error message.
type Error string

// ToInterface converts this object to a golang value
func (e Error) ToInterface() any {
return fmt.Errorf(string(e))
}

// ToString converts this object to a string.
func (e Error) ToString() string {
return "ERROR{" + string(e) + "}"
Expand Down
5 changes: 5 additions & 0 deletions primitive/nil.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ package primitive
// Nil type holds the undefined value
type Nil struct{}

// ToInterface converts this object to a golang value
func (n Nil) ToInterface() any {
return nil
}

// ToString converts this object to a string.
func (n Nil) ToString() string {
return "nil"
Expand Down
12 changes: 12 additions & 0 deletions primitive/number.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ import (
// Number type holds numbers.
type Number float64

// ToInterface converts this object to a golang value
func (n Number) ToInterface() any {

// int?
if float64(n) == float64(int(n)) {
return int(n)
}

// float
return float64(n)
}

// ToString converts this object to a string.
func (n Number) ToString() string {

Expand Down
11 changes: 11 additions & 0 deletions primitive/primitive.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ type Primitive interface {
Type() string
}

// ToNative is an optional interface that some of our primitive
// types might choose to implement.
//
// If available this allows a YAL object to be converted to a
// suitable Golang equivalent type/value.
type ToNative interface {

// ToInterface converts to a native golang type.
ToInterface() interface{}
}

// IsNil tests whether an expression is nil.
func IsNil(e Primitive) bool {
var n Nil
Expand Down
5 changes: 5 additions & 0 deletions primitive/symbol.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ package primitive
// Symbol is the type for our symbols.
type Symbol string

// ToInterface converts this object to a golang value
func (s Symbol) ToInterface() any {
return s.ToString()
}

// ToString converts this object to a string.
func (s Symbol) ToString() string {
return string(s)
Expand Down
29 changes: 15 additions & 14 deletions stdlib/stdlib/time.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,33 @@
;; parts of the time, as well as some aliases for ease of typing.

(set! time:hour (fn* ()
"Return the current hour, as an integer."
(nth (time) 0)))
"Return the current hour, as an integer."
(nth (time) 0)))

(set! time:minute (fn* ()
"Return the current minute, as an integer."
(nth (time) 1)))
"Return the current minute, as an integer."
(nth (time) 1)))

(set! time:second (fn* ()
"Return the current seconds, as an integer."
(nth (time) 2)))
"Return the current seconds, as an integer."
(nth (time) 2)))

;; define legacy aliases
(alias hour time:hour)
(alias minute time:minute)
(alias second time:second)
(alias hour time:hour
minute time:minute
second time:second)

(set! zero-pad-single-number (fn* (num)
"Prefix the given number with zero, if the number is less than ten.
(set! zero-pad-single-number (fn* (num:number)
"Return the given number, padded to two digits, as a string.
i.e. Add a '0' prefix to the specified number, for values less than ten.
This is designed to pad the hours, minutes, and seconds in (hms)."
(if (< num 10)
(sprintf "0%s" num)
num)))
(sprintf "0%d" num)
(str num))))

(set! time:hms (fn* ()
"Return the current time, formatted as 'HH:MM:SS', as a string."
"Return the current time as a string, formatted as 'HH:MM:SS'."
(sprintf "%s:%s:%s"
(zero-pad-single-number (hour))
(zero-pad-single-number (minute))
Expand Down
Loading

0 comments on commit 78bdd79

Please sign in to comment.