From c4b97fadcc48feb5eef9373c464d2aed384a1fa0 Mon Sep 17 00:00:00 2001 From: James Shubin Date: Mon, 18 Jun 2018 15:53:35 -0400 Subject: [PATCH] lang: Update map type definition to include a prefix It turns out that some planned additions to the parser make it so that the map type definition can be ambiguous. As a result, this patch updates the definition so that the map definition is not confused with an open curly bracket anywhere. Thanks to pestle and stbenjamin for their help understanding yacc! --- lang/funcs/core/exchange_polyfunc.go | 2 +- lang/funcs/core/kvlookup_func.go | 2 +- lang/funcs/simple/env_func.go | 4 ++-- lang/funcs/simplepoly/len_polyfunc.go | 2 +- lang/lexer.nex | 5 +++++ lang/lexparse_test.go | 4 ++-- lang/parser.y | 8 +++---- lang/types/type.go | 6 +++--- lang/types/type_test.go | 30 +++++++++++++-------------- lang/types/value.go | 2 +- lang/types/value_test.go | 16 +++++++------- lang/unification_test.go | 2 +- test/test-examples.sh | 2 ++ 13 files changed, 46 insertions(+), 39 deletions(-) diff --git a/lang/funcs/core/exchange_polyfunc.go b/lang/funcs/core/exchange_polyfunc.go index 06584c93..7a9848c2 100644 --- a/lang/funcs/core/exchange_polyfunc.go +++ b/lang/funcs/core/exchange_polyfunc.go @@ -60,7 +60,7 @@ func (obj *ExchangeFunc) Info() *interfaces.Info { // TODO: do we want to allow this to be statically polymorphic, // and have value be any type we might want? // output is map of: hostname => value - Sig: types.NewType("func(namespace str, value str) {str: str}"), + Sig: types.NewType("func(namespace str, value str) map{str: str}"), Err: obj.Validate(), } } diff --git a/lang/funcs/core/kvlookup_func.go b/lang/funcs/core/kvlookup_func.go index b4f77d5b..f595fedf 100644 --- a/lang/funcs/core/kvlookup_func.go +++ b/lang/funcs/core/kvlookup_func.go @@ -58,7 +58,7 @@ func (obj *KVLookupFunc) Info() *interfaces.Info { Pure: false, // definitely false Memo: false, // output is map of: hostname => value - Sig: types.NewType("func(namespace str) {str: str}"), + Sig: types.NewType("func(namespace str) map{str: str}"), Err: obj.Validate(), } } diff --git a/lang/funcs/simple/env_func.go b/lang/funcs/simple/env_func.go index 55ffa3c1..34d6a117 100644 --- a/lang/funcs/simple/env_func.go +++ b/lang/funcs/simple/env_func.go @@ -38,7 +38,7 @@ func init() { V: HasEnv, }) Register("env", &types.FuncValue{ - T: types.NewType("func() {str: str}"), + T: types.NewType("func() map{str: str}"), V: Env, }) } @@ -78,7 +78,7 @@ func Env(input []types.Value) (types.Value, error) { } } return &types.MapValue{ - T: types.NewType("{str: str}"), + T: types.NewType("map{str: str}"), V: environ, }, nil } diff --git a/lang/funcs/simplepoly/len_polyfunc.go b/lang/funcs/simplepoly/len_polyfunc.go index ebdcc65a..59632a07 100644 --- a/lang/funcs/simplepoly/len_polyfunc.go +++ b/lang/funcs/simplepoly/len_polyfunc.go @@ -30,7 +30,7 @@ func init() { V: Len, }, { - T: types.NewType("func({variant: variant}) int"), + T: types.NewType("func(map{variant: variant}) int"), V: Len, }, // TODO: should we add support for struct or func lengths? diff --git a/lang/lexer.nex b/lang/lexer.nex index bef890cd..51b91b5a 100644 --- a/lang/lexer.nex +++ b/lang/lexer.nex @@ -169,6 +169,11 @@ lval.str = yylex.Text() return FLOAT_IDENTIFIER } +/map/ { + yylex.pos(lval) // our pos + lval.str = yylex.Text() + return MAP_IDENTIFIER + } /struct/ { yylex.pos(lval) // our pos lval.str = yylex.Text() diff --git a/lang/lexparse_test.go b/lang/lexparse_test.go index d8c5fde0..6dd0886b 100644 --- a/lang/lexparse_test.go +++ b/lang/lexparse_test.go @@ -242,11 +242,11 @@ func TestLexParse0(t *testing.T) { values = append(values, test{ name: "maps and lists", code: ` - $strmap {str: int} = { + $strmap map{str: int} = { "key1" => 42, "key2" => -13, } - $mapstrintlist {str: []int} = { + $mapstrintlist map{str: []int} = { "key1" => [42, 44,], "key2" => [], "key3" => [-13,], diff --git a/lang/parser.y b/lang/parser.y index 556104ac..2dfec3bc 100644 --- a/lang/parser.y +++ b/lang/parser.y @@ -83,7 +83,7 @@ func init() { %token COMMA COLON SEMICOLON %token ELVIS ROCKET ARROW DOT %token STR_IDENTIFIER BOOL_IDENTIFIER INT_IDENTIFIER FLOAT_IDENTIFIER -%token STRUCT_IDENTIFIER VARIANT_IDENTIFIER VAR_IDENTIFIER IDENTIFIER +%token MAP_IDENTIFIER STRUCT_IDENTIFIER VARIANT_IDENTIFIER VAR_IDENTIFIER IDENTIFIER %token VAR_IDENTIFIER_HX CAPITALIZED_IDENTIFIER %token CLASS_IDENTIFIER INCLUDE_IDENTIFIER %token COMMENT ERROR @@ -910,11 +910,11 @@ type: posLast(yylex, yyDollar) // our pos $$.typ = types.NewType("[]" + $3.typ.String()) } -| OPEN_CURLY type COLON type CLOSE_CURLY - // map: {str: int} or {str: []int} +| MAP_IDENTIFIER OPEN_CURLY type COLON type CLOSE_CURLY + // map: map{str: int} or map{str: []int} { posLast(yylex, yyDollar) // our pos - $$.typ = types.NewType(fmt.Sprintf("{%s: %s}", $2.typ.String(), $4.typ.String())) + $$.typ = types.NewType(fmt.Sprintf("map{%s: %s}", $3.typ.String(), $5.typ.String())) } | STRUCT_IDENTIFIER OPEN_CURLY type_struct_fields CLOSE_CURLY // struct: struct{} or struct{a bool} or struct{a bool; bb int} diff --git a/lang/types/type.go b/lang/types/type.go index 89b6e75c..03ab5605 100644 --- a/lang/types/type.go +++ b/lang/types/type.go @@ -236,8 +236,8 @@ func NewType(s string) *Type { } // KindMap - if strings.HasPrefix(s, "{") && strings.HasSuffix(s, "}") { - s := s[1 : len(s)-1] + if strings.HasPrefix(s, "map{") && strings.HasSuffix(s, "}") { + s := s[len("map{") : len(s)-1] if s == "" { // it is empty return nil } @@ -502,7 +502,7 @@ func (obj *Type) String() string { if obj.Key == nil || obj.Val == nil { panic("malformed map type") } - return fmt.Sprintf("{%s: %s}", obj.Key.String(), obj.Val.String()) + return fmt.Sprintf("map{%s: %s}", obj.Key.String(), obj.Val.String()) case KindStruct: // {a bool; b int} if obj.Map == nil { diff --git a/lang/types/type_test.go b/lang/types/type_test.go index 9e06edde..752e9ffc 100644 --- a/lang/types/type_test.go +++ b/lang/types/type_test.go @@ -122,8 +122,8 @@ func TestType1(t *testing.T) { }, // maps - "{}": nil, // invalid - "{str: str}": { + "map{}": nil, // invalid + "map{str: str}": { Kind: KindMap, Key: &Type{ Kind: KindStr, @@ -132,7 +132,7 @@ func TestType1(t *testing.T) { Kind: KindStr, }, }, - "{str: int}": { + "map{str: int}": { Kind: KindMap, Key: &Type{ Kind: KindStr, @@ -141,7 +141,7 @@ func TestType1(t *testing.T) { Kind: KindInt, }, }, - "{str: variant}": { + "map{str: variant}": { Kind: KindMap, Key: &Type{ Kind: KindStr, @@ -150,7 +150,7 @@ func TestType1(t *testing.T) { Kind: KindVariant, }, }, - "{variant: int}": { + "map{variant: int}": { Kind: KindMap, Key: &Type{ Kind: KindVariant, @@ -159,7 +159,7 @@ func TestType1(t *testing.T) { Kind: KindInt, }, }, - "{variant: variant}": { + "map{variant: variant}": { Kind: KindMap, Key: &Type{ Kind: KindVariant, @@ -170,7 +170,7 @@ func TestType1(t *testing.T) { }, // nested maps - "{str: {int: bool}}": { + "map{str: map{int: bool}}": { Kind: KindMap, Key: &Type{ Kind: KindStr, @@ -185,7 +185,7 @@ func TestType1(t *testing.T) { }, }, }, - "{{int: bool}: str}": { + "map{map{int: bool}: str}": { Kind: KindMap, Key: &Type{ Kind: KindMap, @@ -200,7 +200,7 @@ func TestType1(t *testing.T) { Kind: KindStr, }, }, - "{{str: int}: {int: bool}}": { + "map{map{str: int}: map{int: bool}}": { Kind: KindMap, Key: &Type{ Kind: KindMap, @@ -221,7 +221,7 @@ func TestType1(t *testing.T) { }, }, }, - "{str: {int: {int: bool}}}": { + "map{str: map{int: map{int: bool}}}": { Kind: KindMap, Key: &Type{ Kind: KindStr, @@ -394,7 +394,7 @@ func TestType1(t *testing.T) { }, // mixed nesting - "{str: []struct{a bool; int []bool}}": { + "map{str: []struct{a bool; int []bool}}": { Kind: KindMap, Key: &Type{ Kind: KindStr, @@ -421,7 +421,7 @@ func TestType1(t *testing.T) { }, }, }, - "struct{a {str: {struct{deeply int; nested bool}: {int: bool}}}; bb struct{z bool; yy int}; ccc str}": { + "struct{a map{str: map{struct{deeply int; nested bool}: map{int: bool}}}; bb struct{z bool; yy int}; ccc str}": { Kind: KindStruct, Ord: []string{ "a", @@ -558,7 +558,7 @@ func TestType1(t *testing.T) { Kind: KindBool, }, }, - "func({str: int}) bool": { + "func(map{str: int}) bool": { Kind: KindFunc, // key names are arbitrary... Map: map[string]*Type{ @@ -579,7 +579,7 @@ func TestType1(t *testing.T) { Kind: KindBool, }, }, - "func(bool, {str: int}) bool": { + "func(bool, map{str: int}) bool": { Kind: KindFunc, // key names are arbitrary... Map: map[string]*Type{ @@ -1243,7 +1243,7 @@ func TestType3(t *testing.T) { Kind: KindBool, }, }, - "func(aaa {str: int}) bool": { + "func(aaa map{str: int}) bool": { Kind: KindFunc, // key names are arbitrary... Map: map[string]*Type{ diff --git a/lang/types/value.go b/lang/types/value.go index 61197417..637284fc 100644 --- a/lang/types/value.go +++ b/lang/types/value.go @@ -125,7 +125,7 @@ func ValueOf(v reflect.Value) (Value, error) { } return &MapValue{ - T: NewType(fmt.Sprintf("{%s: %s}", kt.String(), vt.String())), + T: NewType(fmt.Sprintf("map{%s: %s}", kt.String(), vt.String())), V: m, }, nil diff --git a/lang/types/value_test.go b/lang/types/value_test.go index 6b73c37d..bc3d82ab 100644 --- a/lang/types/value_test.go +++ b/lang/types/value_test.go @@ -81,14 +81,14 @@ func TestPrint1(t *testing.T) { }}: `[["a", "bb", "ccc"], ["d", "ee", "fff"], ["g", "hh", "iii"]]`, } - d0 := NewMap(NewType("{str: int}")) + d0 := NewMap(NewType("map{str: int}")) values[d0] = `{}` - d1 := NewMap(NewType("{str: int}")) + d1 := NewMap(NewType("map{str: int}")) d1.Add(&StrValue{V: "answer"}, &IntValue{V: 42}) values[d1] = `{"answer": 42}` - d2 := NewMap(NewType("{str: int}")) + d2 := NewMap(NewType("map{str: int}")) d2.Add(&StrValue{V: "answer"}, &IntValue{V: 42}) d2.Add(&StrValue{V: "hello"}, &IntValue{V: 13}) values[d2] = `{"answer": 42, "hello": 13}` @@ -196,10 +196,10 @@ func TestReflectValue1(t *testing.T) { }: `[[a bb ccc] [d ee fff] [g hh iii]]`, } - d0 := NewMap(NewType("{str: int}")) + d0 := NewMap(NewType("map{str: int}")) values[d0] = `map[]` - d1 := NewMap(NewType("{str: int}")) + d1 := NewMap(NewType("map{str: int}")) d1.Add(&StrValue{V: "answer"}, &IntValue{V: 42}) values[d1] = `map[answer:42]` @@ -464,7 +464,7 @@ func TestSort1(t *testing.T) { } func TestMapReflectValue1(t *testing.T) { - d := NewMap(NewType("{str: int}")) + d := NewMap(NewType("map{str: int}")) d.Add(&StrValue{V: "answer"}, &IntValue{V: 42}) d.Add(&StrValue{V: "hello"}, &IntValue{V: 13}) // both are valid, since map's aren't sorted @@ -480,7 +480,7 @@ func TestMapReflectValue1(t *testing.T) { t.Errorf("did not match expected: `%s`", exp2) } - d2 := NewMap(NewType("{str: str}")) + d2 := NewMap(NewType("map{str: str}")) d2.Add(&StrValue{V: "answer"}, &StrValue{V: "42 hello:13"}) val2 := d2.Value() @@ -512,7 +512,7 @@ func TestList1(t *testing.T) { } func TestMapLookup1(t *testing.T) { - d := NewMap(NewType("{str: int}")) + d := NewMap(NewType("map{str: int}")) k := &StrValue{V: "answer"} v := &IntValue{V: 42} if err := d.Add(k, v); err != nil { diff --git a/lang/unification_test.go b/lang/unification_test.go index 0d4f6378..6381319f 100644 --- a/lang/unification_test.go +++ b/lang/unification_test.go @@ -147,7 +147,7 @@ func TestUnification1(t *testing.T) { v1: types.TypeFloat, v2: types.TypeFloat, v3: types.TypeFloat, - expr: types.NewType("{int: float}"), + expr: types.NewType("map{int: float}"), }, }) } diff --git a/test/test-examples.sh b/test/test-examples.sh index 07724c05..6cbf954e 100755 --- a/test/test-examples.sh +++ b/test/test-examples.sh @@ -11,6 +11,8 @@ cd "${ROOT}" failures='' +# TODO: test examples/lang/ directory to see if the .mcl files compile correctly + buildout='test-examples.out' # make symlink to outside of package linkto="`pwd`/examples/lib/"