Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow the use of real/typed format strings #67

Merged
merged 10 commits into from
Oct 27, 2022
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