// Copyright 2014 Google Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package parser import ( "bytes" "reflect" "testing" "text/scanner" ) func mkpos(offset, line, column int) scanner.Position { return scanner.Position{ Offset: offset, Line: line, Column: column, } } var validParseTestCases = []struct { input string defs []Definition comments []Comment }{ {` foo {} `, []Definition{ &Module{ Type: Ident{"foo", mkpos(3, 2, 3)}, LbracePos: mkpos(7, 2, 7), RbracePos: mkpos(8, 2, 8), }, }, nil, }, {` foo { name: "abc", } `, []Definition{ &Module{ Type: Ident{"foo", mkpos(3, 2, 3)}, LbracePos: mkpos(7, 2, 7), RbracePos: mkpos(27, 4, 3), Properties: []*Property{ { Name: Ident{"name", mkpos(12, 3, 4)}, Pos: mkpos(16, 3, 8), Value: Value{ Type: String, Pos: mkpos(18, 3, 10), StringValue: "abc", }, }, }, }, }, nil, }, {` foo { isGood: true, } `, []Definition{ &Module{ Type: Ident{"foo", mkpos(3, 2, 3)}, LbracePos: mkpos(7, 2, 7), RbracePos: mkpos(28, 4, 3), Properties: []*Property{ { Name: Ident{"isGood", mkpos(12, 3, 4)}, Pos: mkpos(18, 3, 10), Value: Value{ Type: Bool, Pos: mkpos(20, 3, 12), BoolValue: true, }, }, }, }, }, nil, }, {` foo { stuff: ["asdf", "jkl;", "qwert", "uiop", "bnm,"] } `, []Definition{ &Module{ Type: Ident{"foo", mkpos(3, 2, 3)}, LbracePos: mkpos(7, 2, 7), RbracePos: mkpos(67, 5, 3), Properties: []*Property{ { Name: Ident{"stuff", mkpos(12, 3, 4)}, Pos: mkpos(17, 3, 9), Value: Value{ Type: List, Pos: mkpos(19, 3, 11), EndPos: mkpos(63, 4, 19), ListValue: []Value{ Value{ Type: String, Pos: mkpos(20, 3, 12), StringValue: "asdf", }, Value{ Type: String, Pos: mkpos(28, 3, 20), StringValue: "jkl;", }, Value{ Type: String, Pos: mkpos(36, 3, 28), StringValue: "qwert", }, Value{ Type: String, Pos: mkpos(49, 4, 5), StringValue: "uiop", }, Value{ Type: String, Pos: mkpos(57, 4, 13), StringValue: "bnm,", }, }, }, }, }, }, }, nil, }, {` foo { stuff: { isGood: true, name: "bar" } } `, []Definition{ &Module{ Type: Ident{"foo", mkpos(3, 2, 3)}, LbracePos: mkpos(7, 2, 7), RbracePos: mkpos(62, 7, 3), Properties: []*Property{ { Name: Ident{"stuff", mkpos(12, 3, 4)}, Pos: mkpos(17, 3, 9), Value: Value{ Type: Map, Pos: mkpos(19, 3, 11), EndPos: mkpos(58, 6, 4), MapValue: []*Property{ { Name: Ident{"isGood", mkpos(25, 4, 5)}, Pos: mkpos(31, 4, 11), Value: Value{ Type: Bool, Pos: mkpos(33, 4, 13), BoolValue: true, }, }, { Name: Ident{"name", mkpos(43, 5, 5)}, Pos: mkpos(47, 5, 9), Value: Value{ Type: String, Pos: mkpos(49, 5, 11), StringValue: "bar", }, }, }, }, }, }, }, }, nil, }, {` // comment1 foo { // comment2 isGood: true, // comment3 } `, []Definition{ &Module{ Type: Ident{"foo", mkpos(17, 3, 3)}, LbracePos: mkpos(21, 3, 7), RbracePos: mkpos(70, 6, 3), Properties: []*Property{ { Name: Ident{"isGood", mkpos(41, 5, 4)}, Pos: mkpos(47, 5, 10), Value: Value{ Type: Bool, Pos: mkpos(49, 5, 12), BoolValue: true, }, }, }, }, }, []Comment{ Comment{ Comment: []string{"// comment1"}, Pos: mkpos(3, 2, 3), }, Comment{ Comment: []string{"// comment2"}, Pos: mkpos(26, 4, 4), }, Comment{ Comment: []string{"// comment3"}, Pos: mkpos(56, 5, 19), }, }, }, {` foo { name: "abc", } bar { name: "def", } `, []Definition{ &Module{ Type: Ident{"foo", mkpos(3, 2, 3)}, LbracePos: mkpos(7, 2, 7), RbracePos: mkpos(27, 4, 3), Properties: []*Property{ { Name: Ident{"name", mkpos(12, 3, 4)}, Pos: mkpos(16, 3, 8), Value: Value{ Type: String, Pos: mkpos(18, 3, 10), StringValue: "abc", }, }, }, }, &Module{ Type: Ident{"bar", mkpos(32, 6, 3)}, LbracePos: mkpos(36, 6, 7), RbracePos: mkpos(56, 8, 3), Properties: []*Property{ { Name: Ident{"name", mkpos(41, 7, 4)}, Pos: mkpos(45, 7, 8), Value: Value{ Type: String, Pos: mkpos(47, 7, 10), StringValue: "def", }, }, }, }, }, nil, }, {` foo = "stuff" bar = foo baz = foo + bar boo = baz boo += foo `, []Definition{ &Assignment{ Name: Ident{"foo", mkpos(3, 2, 3)}, Pos: mkpos(7, 2, 7), Value: Value{ Type: String, Pos: mkpos(9, 2, 9), StringValue: "stuff", }, OrigValue: Value{ Type: String, Pos: mkpos(9, 2, 9), StringValue: "stuff", }, Assigner: "=", Referenced: true, }, &Assignment{ Name: Ident{"bar", mkpos(19, 3, 3)}, Pos: mkpos(23, 3, 7), Value: Value{ Type: String, Pos: mkpos(25, 3, 9), StringValue: "stuff", Variable: "foo", }, OrigValue: Value{ Type: String, Pos: mkpos(25, 3, 9), StringValue: "stuff", Variable: "foo", }, Assigner: "=", Referenced: true, }, &Assignment{ Name: Ident{"baz", mkpos(31, 4, 3)}, Pos: mkpos(35, 4, 7), Value: Value{ Type: String, Pos: mkpos(37, 4, 9), StringValue: "stuffstuff", Expression: &Expression{ Args: [2]Value{ { Type: String, Pos: mkpos(37, 4, 9), StringValue: "stuff", Variable: "foo", }, { Type: String, Pos: mkpos(43, 4, 15), StringValue: "stuff", Variable: "bar", }, }, Operator: '+', Pos: mkpos(41, 4, 13), }, }, OrigValue: Value{ Type: String, Pos: mkpos(37, 4, 9), StringValue: "stuffstuff", Expression: &Expression{ Args: [2]Value{ { Type: String, Pos: mkpos(37, 4, 9), StringValue: "stuff", Variable: "foo", }, { Type: String, Pos: mkpos(43, 4, 15), StringValue: "stuff", Variable: "bar", }, }, Operator: '+', Pos: mkpos(41, 4, 13), }, }, Assigner: "=", Referenced: true, }, &Assignment{ Name: Ident{"boo", mkpos(49, 5, 3)}, Pos: mkpos(53, 5, 7), Value: Value{ Type: String, Pos: mkpos(55, 5, 9), StringValue: "stuffstuffstuff", Expression: &Expression{ Args: [2]Value{ { Type: String, Pos: mkpos(55, 5, 9), StringValue: "stuffstuff", Variable: "baz", Expression: &Expression{ Args: [2]Value{ { Type: String, Pos: mkpos(37, 4, 9), StringValue: "stuff", Variable: "foo", }, { Type: String, Pos: mkpos(43, 4, 15), StringValue: "stuff", Variable: "bar", }, }, Operator: '+', Pos: mkpos(41, 4, 13), }, }, { Variable: "foo", Type: String, Pos: mkpos(68, 6, 10), StringValue: "stuff", }, }, Pos: mkpos(66, 6, 8), Operator: '+', }, }, OrigValue: Value{ Type: String, Pos: mkpos(55, 5, 9), StringValue: "stuffstuff", Variable: "baz", Expression: &Expression{ Args: [2]Value{ { Type: String, Pos: mkpos(37, 4, 9), StringValue: "stuff", Variable: "foo", }, { Type: String, Pos: mkpos(43, 4, 15), StringValue: "stuff", Variable: "bar", }, }, Operator: '+', Pos: mkpos(41, 4, 13), }, }, Assigner: "=", }, &Assignment{ Name: Ident{"boo", mkpos(61, 6, 3)}, Pos: mkpos(66, 6, 8), Value: Value{ Type: String, Pos: mkpos(68, 6, 10), StringValue: "stuff", Variable: "foo", }, OrigValue: Value{ Type: String, Pos: mkpos(68, 6, 10), StringValue: "stuff", Variable: "foo", }, Assigner: "+=", }, }, nil, }, } func TestParseValidInput(t *testing.T) { for _, testCase := range validParseTestCases { r := bytes.NewBufferString(testCase.input) file, errs := ParseAndEval("", r, NewScope(nil)) if len(errs) != 0 { t.Errorf("test case: %s", testCase.input) t.Errorf("unexpected errors:") for _, err := range errs { t.Errorf(" %s", err) } t.FailNow() } if len(file.Defs) == len(testCase.defs) { for i := range file.Defs { if !reflect.DeepEqual(file.Defs[i], testCase.defs[i]) { t.Errorf("test case: %s", testCase.input) t.Errorf("incorrect defintion %d:", i) t.Errorf(" expected: %s", testCase.defs[i]) t.Errorf(" got: %s", file.Defs[i]) } } } else { t.Errorf("test case: %s", testCase.input) t.Errorf("length mismatch, expected %d definitions, got %d", len(testCase.defs), len(file.Defs)) } if len(file.Comments) == len(testCase.comments) { for i := range file.Comments { if !reflect.DeepEqual(file.Comments, testCase.comments) { t.Errorf("test case: %s", testCase.input) t.Errorf("incorrect comment %d:", i) t.Errorf(" expected: %s", testCase.comments[i]) t.Errorf(" got: %s", file.Comments[i]) } } } else { t.Errorf("test case: %s", testCase.input) t.Errorf("length mismatch, expected %d comments, got %d", len(testCase.comments), len(file.Comments)) } } } // TODO: Test error strings