Skip to content

Commit

Permalink
lang: funcs: core: fmt: Add variant verb for printf
Browse files Browse the repository at this point in the history
There's no reason we can't support a %v variant verb. Of course it makes
type unification more difficult, and certain uses of this will produce
unsolvable situations, but it's useful for debugging, and fun to have.
  • Loading branch information
purpleidea committed Oct 11, 2021
1 parent 8851654 commit e9791ff
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 9 deletions.
10 changes: 10 additions & 0 deletions examples/lang/printf0.mcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import "fmt"

test "printf-a" {
anotherstr => fmt.printf("the %s is: %d", "answer", 42),
}

$format = "a %s is: %f"
test "printf-b" {
anotherstr => fmt.printf($format, "cool number", 3.14159),
}
4 changes: 2 additions & 2 deletions examples/lang/printf1.mcl
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import "fmt"

test "printf-a" {
anotherstr => fmt.printf("the %s is: %d", "answer", 42),
anotherstr => fmt.printf("the %v is: %v", "answer", 42),
}

$format = "a %s is: %f"
$format = "a %v is: %v"
test "printf-b" {
anotherstr => fmt.printf($format, "cool number", 3.14159),
}
39 changes: 32 additions & 7 deletions lang/funcs/core/fmt/printf_func.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,11 @@ func (obj *PrintfFunc) Unify(expr interfaces.Expr) ([]interfaces.Invariant, erro
// result. The golang printf does something similar
// when it can't catch things statically at compile
// time.
// XXX: In the above scenario, we'd have to also change
// the compileFormatToString function to handle a list
// of values with a badly matched string. Maybe best to
// just not allow this entirely? Or set this behaviour
// with a constant?

value, err := cfavInvar.Args[0].Value() // is it known?
if err != nil {
Expand Down Expand Up @@ -175,11 +180,14 @@ func (obj *PrintfFunc) Unify(expr interfaces.Expr) ([]interfaces.Invariant, erro
}

dummyArg := &interfaces.ExprAny{}
invar = &interfaces.EqualsInvariant{
Expr: dummyArg,
Type: x,
// if it's a variant, we can't add the invariant
if x != types.TypeVariant {
invar = &interfaces.EqualsInvariant{
Expr: dummyArg,
Type: x,
}
invariants = append(invariants, invar)
}
invariants = append(invariants, invar)

// add the relationships to the called args
invar = &interfaces.EqualityInvariant{
Expand Down Expand Up @@ -228,6 +236,8 @@ func (obj *PrintfFunc) Unify(expr interfaces.Expr) ([]interfaces.Invariant, erro
// precise type if it can be gleamed from the format argument. If it cannot, it
// is because either the format argument was not known statically, or because it
// had an invalid format string.
// XXX: This version of the function does not handle any variants returned from
// the parseFormatToTypeList helper function.
func (obj *PrintfFunc) Polymorphisms(partialType *types.Type, partialValues []types.Value) ([]*types.Type, error) {
if partialType == nil || len(partialValues) < 1 {
return nil, fmt.Errorf("first argument must be a static format string")
Expand Down Expand Up @@ -437,7 +447,8 @@ func valueToString(value types.Value) string {
}

// parseFormatToTypeList takes a format string and returns a list of types that
// it expects to use in the order found in the format string.
// it expects to use in the order found in the format string. This can also
// handle the %v special variant type in the format string.
// FIXME: add support for more types, and add tests!
func parseFormatToTypeList(format string) ([]*types.Type, error) {
typList := []*types.Type{}
Expand Down Expand Up @@ -476,6 +487,10 @@ func parseFormatToTypeList(format string) ([]*types.Type, error) {

// FIXME: add fancy types like: %[]s, %[]f, %{s:f}, etc...

// special!
case 'v':
typList = append(typList, types.TypeVariant)

default:
return nil, fmt.Errorf("invalid format string at %d", i)
}
Expand All @@ -486,7 +501,9 @@ func parseFormatToTypeList(format string) ([]*types.Type, error) {
}

// compileFormatToString takes a format string and a list of values and returns
// the compiled/templated output.
// the compiled/templated output. This can also handle the %v special variant
// type in the format string. Of course the corresponding value to those %v
// entries must have a static, fixed, precise type.
// FIXME: add support for more types, and add tests!
func compileFormatToString(format string, values []types.Value) (string, error) {
output := ""
Expand Down Expand Up @@ -529,12 +546,20 @@ func compileFormatToString(format string, values []types.Value) (string, error)

// FIXME: add fancy types like: %[]s, %[]f, %{s:f}, etc...

case 'v':
typ = types.TypeVariant

default:
return "", fmt.Errorf("invalid format string at %d", i)
}
inType = false // done

if err := typ.Cmp(values[ix].Type()); err != nil {
// check the type (if not a variant) matches what we have...
if typ == types.TypeVariant {
if values[ix].Type() == nil {
return "", fmt.Errorf("unexpected nil type")
}
} else if err := typ.Cmp(values[ix].Type()); err != nil {
return "", errwrap.Wrapf(err, "unexpected type")
}

Expand Down

0 comments on commit e9791ff

Please sign in to comment.