diff --git a/core/BUILD b/core/BUILD index ac58bc039..05c85c235 100644 --- a/core/BUILD +++ b/core/BUILD @@ -3,8 +3,9 @@ package(default_visibility = ["//visibility:public"]) cc_library( name = "common", hdrs = [ - "unicode.h", + "ast.h", "static_error.h", + "unicode.h", ], includes = ["."], ) @@ -21,29 +22,45 @@ cc_library( cc_test( name = "lexer_test", - srcs = ["lexer_test.cc"], + srcs = ["lexer_test.cpp"], deps = [ ":lexer", "//external:gtest_main", ], ) +cc_library( + name = "parser", + srcs = ["parser.cpp"], + hdrs = ["parser.h"], + deps = [ + ":common", + ":lexer", + ], +) + +cc_test( + name = "parser_test", + srcs = ["parser_test.cpp"], + deps = [ + ":parser", + "//external:gtest_main", + ], +) + cc_library( name = "jsonnet-common", srcs = [ "desugarer.cpp", "formatter.cpp", "libjsonnet.cpp", - "parser.cpp", "static_analysis.cpp", "string_utils.cpp", "vm.cpp", ], hdrs = [ - "ast.h", "desugarer.h", "formatter.h", - "parser.h", "state.h", "static_analysis.h", "string_utils.h", @@ -52,6 +69,7 @@ cc_library( deps = [ ":common", ":lexer", + ":parser", "//include:libjsonnet", "//stdlib:std", ], @@ -70,7 +88,7 @@ cc_library( cc_test( name = "libjsonnet_test", - srcs = ["libjsonnet_test.cc"], + srcs = ["libjsonnet_test.cpp"], deps = [ ":jsonnet-common", "//external:gtest_main", diff --git a/core/lexer_test.cc b/core/lexer_test.cc deleted file mode 100644 index 717c7ba23..000000000 --- a/core/lexer_test.cc +++ /dev/null @@ -1,284 +0,0 @@ -// Copyright 2016 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. - -#include "lexer.h" - -#include -#include "gtest/gtest.h" - -namespace { - -void TestLex(const char* name, - const char* input, - const std::list& tokens, - const std::string& error) { - std::list test_tokens(tokens); - test_tokens.push_back(Token(Token::Kind::END_OF_FILE, "")); - - try { - std::list lexed_tokens = jsonnet_lex(name, input); - ASSERT_EQ(test_tokens, lexed_tokens) - << "Test failed: " << name << std::endl; - } catch (StaticError& e) { - ASSERT_EQ(error, e.toString()); - } -} - -TEST(Lexer, TestWhitespace) { - TestLex("empty", "", {}, ""); - TestLex("whitespace", " \t\n\r\r\n", {}, ""); -} - -TEST(Lexer, TestOperators) { - TestLex("brace L", "{", {Token(Token::Kind::BRACE_L, "")}, ""); - TestLex("brace R", "}", {Token(Token::Kind::BRACE_R, "")}, ""); - TestLex("bracket L", "[", {Token(Token::Kind::BRACKET_L, "")}, ""); - TestLex("bracket R", "]", {Token(Token::Kind::BRACKET_R, "")}, ""); - TestLex("colon ", ":", {Token(Token::Kind::OPERATOR, ":")}, ""); - TestLex("colon 2", "::", {Token(Token::Kind::OPERATOR, "::")}, ""); - TestLex("colon 2", ":::", {Token(Token::Kind::OPERATOR, ":::")}, ""); - TestLex("arrow right", "->", {Token(Token::Kind::OPERATOR, "->")}, ""); - TestLex("less than minus", "<-", - {Token(Token::Kind::OPERATOR, "<"), - Token(Token::Kind::OPERATOR, "-")}, ""); - TestLex("comma", ",", {Token(Token::Kind::COMMA, "")}, ""); - TestLex("dollar", "$", {Token(Token::Kind::DOLLAR, "")}, ""); - TestLex("dot", ".", {Token(Token::Kind::DOT, "")}, ""); - TestLex("paren L", "(", {Token(Token::Kind::PAREN_L, "")}, ""); - TestLex("paren R", ")", {Token(Token::Kind::PAREN_R, "")}, ""); - TestLex("semicolon", ";", {Token(Token::Kind::SEMICOLON, "")}, ""); - - TestLex("not 1", "!", {Token(Token::Kind::OPERATOR, "!")}, ""); - TestLex("not 2", "! ", {Token(Token::Kind::OPERATOR, "!")}, ""); - TestLex("not equal", "!=", {Token(Token::Kind::OPERATOR, "!=")}, ""); - TestLex("tilde", "~", {Token(Token::Kind::OPERATOR, "~")}, ""); - TestLex("plus", "+", {Token(Token::Kind::OPERATOR, "+")}, ""); - TestLex("minus", "-", {Token(Token::Kind::OPERATOR, "-")}, ""); -} - -TEST(Lexer, TestMiscOperators) { - TestLex("op *", "*", {Token(Token::Kind::OPERATOR, "*")}, ""); - TestLex("op /", "/", {Token(Token::Kind::OPERATOR, "/")}, ""); - TestLex("op %", "%", {Token(Token::Kind::OPERATOR, "%")}, ""); - TestLex("op &", "&", {Token(Token::Kind::OPERATOR, "&")}, ""); - TestLex("op |", "|", {Token(Token::Kind::OPERATOR, "|")}, ""); - TestLex("op ^", "^", {Token(Token::Kind::OPERATOR, "^")}, ""); - TestLex("op =", "=", {Token(Token::Kind::OPERATOR, "=")}, ""); - TestLex("op <", "<", {Token(Token::Kind::OPERATOR, "<")}, ""); - TestLex("op >", ">", {Token(Token::Kind::OPERATOR, ">")}, ""); - TestLex("op >==|", ">==|", {Token(Token::Kind::OPERATOR, ">==|")}, ""); -} - -TEST(Lexer, TestNumbers) { - TestLex("number 0", "0", {Token(Token::Kind::NUMBER, "0")}, ""); - TestLex("number 1", "1", {Token(Token::Kind::NUMBER, "1")}, ""); - TestLex("number 1.0", "1.0", {Token(Token::Kind::NUMBER, "1.0")}, ""); - TestLex("number 0.10", "0.10", {Token(Token::Kind::NUMBER, "0.10")}, ""); - TestLex("number 0e100", "0e100", {Token(Token::Kind::NUMBER, "0e100")}, ""); - TestLex("number 1e100", "1e100", {Token(Token::Kind::NUMBER, "1e100")}, ""); - TestLex("number 1.1e100", "1.1e100", - {Token(Token::Kind::NUMBER, "1.1e100")}, ""); - TestLex("number 1.1e-100", "1.1e-100", - {Token(Token::Kind::NUMBER, "1.1e-100")}, ""); - TestLex("number 1.1e+100", "1.1e+100", - {Token(Token::Kind::NUMBER, "1.1e+100")}, ""); - TestLex("number 0100", "0100", - {Token(Token::Kind::NUMBER, "0"), Token(Token::Kind::NUMBER, "100")}, - ""); - TestLex("number 10+10", "10+10", - {Token(Token::Kind::NUMBER, "10"), - Token(Token::Kind::OPERATOR, "+"), - Token(Token::Kind::NUMBER, "10")}, ""); - TestLex("number 1.+3", "1.+3", {}, - "number 1.+3:1:1: Couldn't lex number, junk after decimal point: +"); - TestLex("number 1e!", "1e!", {}, - "number 1e!:1:1: Couldn't lex number, junk after 'E': !"); - TestLex("number 1e+!", "1e+!", {}, - "number 1e+!:1:1: Couldn't lex number, junk after exponent sign: !"); -} - -TEST(Lexer, TestDoubleStrings) { - TestLex("double string \"hi\"", - "\"hi\"", {Token(Token::Kind::STRING_DOUBLE, "hi")}, ""); - TestLex("double string \"hi nl\"", - "\"hi\n\"", {Token(Token::Kind::STRING_DOUBLE, "hi\n")}, ""); - TestLex("double string \"hi\\\"\"", - "\"hi\\\"\"", {Token(Token::Kind::STRING_DOUBLE, "hi\\\"")}, ""); - TestLex("double string \"hi\\nl\"", - "\"hi\\\n\"", {Token(Token::Kind::STRING_DOUBLE, "hi\\\n")}, ""); - TestLex("double string \"hi", - "\"hi", {}, "double string \"hi:1:1: Unterminated string"); -} - -TEST(Lexer, TestSingleStrings) { - TestLex("single string 'hi'", - "'hi'", {Token(Token::Kind::STRING_SINGLE, "hi")}, ""); - TestLex("single string 'hi nl'", - "'hi\n'", {Token(Token::Kind::STRING_SINGLE, "hi\n")}, ""); - TestLex("single string 'hi\\''", - "'hi\\''", {Token(Token::Kind::STRING_SINGLE, "hi\\'")}, ""); - TestLex("single string 'hi\\nl'", - "'hi\\\n'", {Token(Token::Kind::STRING_SINGLE, "hi\\\n")}, ""); - TestLex("single string 'hi", - "'hi", {}, "single string 'hi:1:1: Unterminated string"); -} - -TEST(Lexer, TestBlockStringSpaces) { - const char str[] = - "|||\n" - " test\n" - " more\n" - " |||\n" - " foo\n" - "|||"; - const Token token = Token( - Token::Kind::STRING_BLOCK, - {}, - "test\n more\n|||\n foo\n", - " ", - "", - {}); - TestLex("block string spaces", str, {token}, ""); -} - -TEST(Lexer, TestBlockStringTabs) { - const char str[] = - "|||\n" - "\ttest\n" - "\t more\n" - "\t|||\n" - "\t foo\n" - "|||"; - const Token token = Token( - Token::Kind::STRING_BLOCK, - {}, - "test\n more\n|||\n foo\n", - "\t", - "", - {}); - TestLex("block string tabs", str, {token}, ""); -} - -TEST(Lexer, TestBlockStringsMixed) { - const char str[] = - "|||\n" - "\t \ttest\n" - "\t \t more\n" - "\t \t|||\n" - "\t \t foo\n" - "|||"; - const Token token = Token( - Token::Kind::STRING_BLOCK, - {}, - "test\n more\n|||\n foo\n", - "\t \t", - "", - {}); - TestLex("block string mixed", str, {token}, ""); -} - -TEST(Lexer, TestBlockStringBlanks) { - const char str[] = - "|||\n\n" - " test\n\n\n" - " more\n" - " |||\n" - " foo\n" - "|||"; - const Token token = Token( - Token::Kind::STRING_BLOCK, - {}, - "\ntest\n\n\n more\n|||\n foo\n", - " ", - "", - {}); - TestLex("block string blanks", str, {token}, ""); -} - -TEST(Lexer, TestBlockStringBadIndent) { - const char str[] = - "|||\n" - " test\n" - " foo\n" - "|||"; - TestLex("block string bad indent", str, {}, - "block string bad indent:1:1: Text block not terminated with |||"); -} - -TEST(Lexer, TestBlockStringEof) { - const char str[] = - "|||\n" - " test"; - TestLex("block string eof", str, {}, "block string eof:1:1: Unexpected EOF"); -} - -TEST(Lexer, TestBlockStringNotTerm) { - const char str[] = - "|||\n" - " test\n"; - TestLex("block string not term", str, {}, - "block string not term:1:1: Text block not terminated with |||"); -} - -TEST(Lexer, TestBlockStringNoWs) { - const char str[] = - "|||\n" - "test\n" - "|||"; - TestLex("block string no ws", str, {}, - "block string no ws:1:1: Text block's first line must start with" - " whitespace."); -} - -TEST(Lexer, TestKeywords) { - TestLex("assert", "assert", {Token(Token::Kind::ASSERT, "assert")}, ""); - TestLex("else", "else", {Token(Token::Kind::ELSE, "else")}, ""); - TestLex("error", "error", {Token(Token::Kind::ERROR, "error")}, ""); - TestLex("false", "false", {Token(Token::Kind::FALSE, "false")}, ""); - TestLex("for", "for", {Token(Token::Kind::FOR, "for")}, ""); - TestLex("function", "function", - {Token(Token::Kind::FUNCTION, "function")}, ""); - TestLex("if", "if", {Token(Token::Kind::IF, "if")}, ""); - TestLex("import", "import", {Token(Token::Kind::IMPORT, "import")}, ""); - TestLex("importstr", "importstr", - {Token(Token::Kind::IMPORTSTR, "importstr")}, ""); - TestLex("in", "in", {Token(Token::Kind::IN, "in")}, ""); - TestLex("local", "local", {Token(Token::Kind::LOCAL, "local")}, ""); - TestLex("null", "null", {Token(Token::Kind::NULL_LIT, "null")}, ""); - TestLex("self", "self", {Token(Token::Kind::SELF, "self")}, ""); - TestLex("super", "super", {Token(Token::Kind::SUPER, "super")}, ""); - TestLex("tailstrict", "tailstrict", - {Token(Token::Kind::TAILSTRICT, "tailstrict")}, ""); - TestLex("then", "then", {Token(Token::Kind::THEN, "then")}, ""); - TestLex("true", "true", {Token(Token::Kind::TRUE, "true")}, ""); -} - -TEST(Lexer, TestIdentifier) { - TestLex("identifier", "foobar123", - {Token(Token::Kind::IDENTIFIER, "foobar123")}, ""); - TestLex("identifier", "foo bar123", - {Token(Token::Kind::IDENTIFIER, "foo"), - Token(Token::Kind::IDENTIFIER, "bar123")}, ""); -} - -TEST(Lexer, TestComments) { - // TODO(dzc): Test does not look at fodder yet. - TestLex("c++ comment", "// hi", {}, ""); - TestLex("hash comment", "# hi", {}, ""); - TestLex("c comment", "/* hi */", {}, ""); - TestLex("c comment no term", "/* hi", {}, - "c comment no term:1:1: Multi-line comment has no terminating */."); -} - -} // namespace diff --git a/core/lexer_test.cpp b/core/lexer_test.cpp new file mode 100644 index 000000000..271e2e4af --- /dev/null +++ b/core/lexer_test.cpp @@ -0,0 +1,297 @@ +/* +Copyright 2015 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. +*/ + +#include "lexer.h" + +#include +#include "gtest/gtest.h" + +namespace { + +void testLex(const char* name, + const char* input, + const std::list& tokens, + const std::string& error) +{ + std::list test_tokens(tokens); + test_tokens.push_back(Token(Token::Kind::END_OF_FILE, "")); + + try { + std::list lexed_tokens = jsonnet_lex(name, input); + ASSERT_EQ(test_tokens, lexed_tokens) + << "Test failed: " << name << std::endl; + } catch (StaticError& e) { + ASSERT_EQ(error, e.toString()); + } +} + +TEST(Lexer, TestWhitespace) +{ + testLex("empty", "", {}, ""); + testLex("whitespace", " \t\n\r\r\n", {}, ""); +} + +TEST(Lexer, TestOperators) +{ + testLex("brace L", "{", {Token(Token::Kind::BRACE_L, "")}, ""); + testLex("brace R", "}", {Token(Token::Kind::BRACE_R, "")}, ""); + testLex("bracket L", "[", {Token(Token::Kind::BRACKET_L, "")}, ""); + testLex("bracket R", "]", {Token(Token::Kind::BRACKET_R, "")}, ""); + testLex("colon ", ":", {Token(Token::Kind::OPERATOR, ":")}, ""); + testLex("colon 2", "::", {Token(Token::Kind::OPERATOR, "::")}, ""); + testLex("colon 2", ":::", {Token(Token::Kind::OPERATOR, ":::")}, ""); + testLex("arrow right", "->", {Token(Token::Kind::OPERATOR, "->")}, ""); + testLex("less than minus", "<-", + {Token(Token::Kind::OPERATOR, "<"), + Token(Token::Kind::OPERATOR, "-")}, ""); + testLex("comma", ",", {Token(Token::Kind::COMMA, "")}, ""); + testLex("dollar", "$", {Token(Token::Kind::DOLLAR, "")}, ""); + testLex("dot", ".", {Token(Token::Kind::DOT, "")}, ""); + testLex("paren L", "(", {Token(Token::Kind::PAREN_L, "")}, ""); + testLex("paren R", ")", {Token(Token::Kind::PAREN_R, "")}, ""); + testLex("semicolon", ";", {Token(Token::Kind::SEMICOLON, "")}, ""); + + testLex("not 1", "!", {Token(Token::Kind::OPERATOR, "!")}, ""); + testLex("not 2", "! ", {Token(Token::Kind::OPERATOR, "!")}, ""); + testLex("not equal", "!=", {Token(Token::Kind::OPERATOR, "!=")}, ""); + testLex("tilde", "~", {Token(Token::Kind::OPERATOR, "~")}, ""); + testLex("plus", "+", {Token(Token::Kind::OPERATOR, "+")}, ""); + testLex("minus", "-", {Token(Token::Kind::OPERATOR, "-")}, ""); +} + +TEST(Lexer, TestMiscOperators) +{ + testLex("op *", "*", {Token(Token::Kind::OPERATOR, "*")}, ""); + testLex("op /", "/", {Token(Token::Kind::OPERATOR, "/")}, ""); + testLex("op %", "%", {Token(Token::Kind::OPERATOR, "%")}, ""); + testLex("op &", "&", {Token(Token::Kind::OPERATOR, "&")}, ""); + testLex("op |", "|", {Token(Token::Kind::OPERATOR, "|")}, ""); + testLex("op ^", "^", {Token(Token::Kind::OPERATOR, "^")}, ""); + testLex("op =", "=", {Token(Token::Kind::OPERATOR, "=")}, ""); + testLex("op <", "<", {Token(Token::Kind::OPERATOR, "<")}, ""); + testLex("op >", ">", {Token(Token::Kind::OPERATOR, ">")}, ""); + testLex("op >==|", ">==|", {Token(Token::Kind::OPERATOR, ">==|")}, ""); +} + +TEST(Lexer, TestNumbers) +{ + testLex("number 0", "0", {Token(Token::Kind::NUMBER, "0")}, ""); + testLex("number 1", "1", {Token(Token::Kind::NUMBER, "1")}, ""); + testLex("number 1.0", "1.0", {Token(Token::Kind::NUMBER, "1.0")}, ""); + testLex("number 0.10", "0.10", {Token(Token::Kind::NUMBER, "0.10")}, ""); + testLex("number 0e100", "0e100", {Token(Token::Kind::NUMBER, "0e100")}, ""); + testLex("number 1e100", "1e100", {Token(Token::Kind::NUMBER, "1e100")}, ""); + testLex("number 1.1e100", "1.1e100", {Token(Token::Kind::NUMBER, "1.1e100")}, ""); + testLex("number 1.1e-100", "1.1e-100", {Token(Token::Kind::NUMBER, "1.1e-100")}, ""); + testLex("number 1.1e+100", "1.1e+100", {Token(Token::Kind::NUMBER, "1.1e+100")}, ""); + testLex("number 0100", "0100", + {Token(Token::Kind::NUMBER, "0"), Token(Token::Kind::NUMBER, "100")}, + ""); + testLex("number 10+10", "10+10", + {Token(Token::Kind::NUMBER, "10"), + Token(Token::Kind::OPERATOR, "+"), + Token(Token::Kind::NUMBER, "10")}, ""); + testLex("number 1.+3", "1.+3", {}, + "number 1.+3:1:1: Couldn't lex number, junk after decimal point: +"); + testLex("number 1e!", "1e!", {}, + "number 1e!:1:1: Couldn't lex number, junk after 'E': !"); + testLex("number 1e+!", "1e+!", {}, + "number 1e+!:1:1: Couldn't lex number, junk after exponent sign: !"); +} + +TEST(Lexer, TestDoubleStrings) +{ + testLex("double string \"hi\"", + "\"hi\"", {Token(Token::Kind::STRING_DOUBLE, "hi")}, ""); + testLex("double string \"hi nl\"", + "\"hi\n\"", {Token(Token::Kind::STRING_DOUBLE, "hi\n")}, ""); + testLex("double string \"hi\\\"\"", + "\"hi\\\"\"", {Token(Token::Kind::STRING_DOUBLE, "hi\\\"")}, ""); + testLex("double string \"hi\\nl\"", + "\"hi\\\n\"", {Token(Token::Kind::STRING_DOUBLE, "hi\\\n")}, ""); + testLex("double string \"hi", + "\"hi", {}, "double string \"hi:1:1: Unterminated string"); +} + +TEST(Lexer, TestSingleStrings) +{ + testLex("single string 'hi'", + "'hi'", {Token(Token::Kind::STRING_SINGLE, "hi")}, ""); + testLex("single string 'hi nl'", + "'hi\n'", {Token(Token::Kind::STRING_SINGLE, "hi\n")}, ""); + testLex("single string 'hi\\''", + "'hi\\''", {Token(Token::Kind::STRING_SINGLE, "hi\\'")}, ""); + testLex("single string 'hi\\nl'", + "'hi\\\n'", {Token(Token::Kind::STRING_SINGLE, "hi\\\n")}, ""); + testLex("single string 'hi", + "'hi", {}, "single string 'hi:1:1: Unterminated string"); +} + +TEST(Lexer, TestBlockStringSpaces) +{ + const char str[] = + "|||\n" + " test\n" + " more\n" + " |||\n" + " foo\n" + "|||"; + const Token token = Token( + Token::Kind::STRING_BLOCK, + {}, + "test\n more\n|||\n foo\n", + " ", + "", + {}); + testLex("block string spaces", str, {token}, ""); +} + +TEST(Lexer, TestBlockStringTabs) +{ + const char str[] = + "|||\n" + "\ttest\n" + "\t more\n" + "\t|||\n" + "\t foo\n" + "|||"; + const Token token = Token( + Token::Kind::STRING_BLOCK, + {}, + "test\n more\n|||\n foo\n", + "\t", + "", + {}); + testLex("block string tabs", str, {token}, ""); +} + +TEST(Lexer, TestBlockStringsMixed) +{ + const char str[] = + "|||\n" + "\t \ttest\n" + "\t \t more\n" + "\t \t|||\n" + "\t \t foo\n" + "|||"; + const Token token = Token( + Token::Kind::STRING_BLOCK, + {}, + "test\n more\n|||\n foo\n", + "\t \t", + "", + {}); + testLex("block string mixed", str, {token}, ""); +} + +TEST(Lexer, TestBlockStringBlanks) +{ + const char str[] = + "|||\n\n" + " test\n\n\n" + " more\n" + " |||\n" + " foo\n" + "|||"; + const Token token = Token( + Token::Kind::STRING_BLOCK, + {}, + "\ntest\n\n\n more\n|||\n foo\n", + " ", + "", + {}); + testLex("block string blanks", str, {token}, ""); +} + +TEST(Lexer, TestBlockStringBadIndent) +{ + const char str[] = + "|||\n" + " test\n" + " foo\n" + "|||"; + testLex("block string bad indent", str, {}, + "block string bad indent:1:1: Text block not terminated with |||"); +} + +TEST(Lexer, TestBlockStringEof) +{ + const char str[] = + "|||\n" + " test"; + testLex("block string eof", str, {}, "block string eof:1:1: Unexpected EOF"); +} + +TEST(Lexer, TestBlockStringNotTerm) +{ + const char str[] = + "|||\n" + " test\n"; + testLex("block string not term", str, {}, + "block string not term:1:1: Text block not terminated with |||"); +} + +TEST(Lexer, TestBlockStringNoWs) +{ + const char str[] = + "|||\n" + "test\n" + "|||"; + testLex("block string no ws", str, {}, + "block string no ws:1:1: Text block's first line must start with" + " whitespace."); +} + +TEST(Lexer, TestKeywords) +{ + testLex("assert", "assert", {Token(Token::Kind::ASSERT, "assert")}, ""); + testLex("else", "else", {Token(Token::Kind::ELSE, "else")}, ""); + testLex("error", "error", {Token(Token::Kind::ERROR, "error")}, ""); + testLex("false", "false", {Token(Token::Kind::FALSE, "false")}, ""); + testLex("for", "for", {Token(Token::Kind::FOR, "for")}, ""); + testLex("function", "function", {Token(Token::Kind::FUNCTION, "function")}, ""); + testLex("if", "if", {Token(Token::Kind::IF, "if")}, ""); + testLex("import", "import", {Token(Token::Kind::IMPORT, "import")}, ""); + testLex("importstr", "importstr", {Token(Token::Kind::IMPORTSTR, "importstr")}, ""); + testLex("in", "in", {Token(Token::Kind::IN, "in")}, ""); + testLex("local", "local", {Token(Token::Kind::LOCAL, "local")}, ""); + testLex("null", "null", {Token(Token::Kind::NULL_LIT, "null")}, ""); + testLex("self", "self", {Token(Token::Kind::SELF, "self")}, ""); + testLex("super", "super", {Token(Token::Kind::SUPER, "super")}, ""); + testLex("tailstrict", "tailstrict", {Token(Token::Kind::TAILSTRICT, "tailstrict")}, ""); + testLex("then", "then", {Token(Token::Kind::THEN, "then")}, ""); + testLex("true", "true", {Token(Token::Kind::TRUE, "true")}, ""); +} + +TEST(Lexer, TestIdentifier) +{ + testLex("identifier", "foobar123", {Token(Token::Kind::IDENTIFIER, "foobar123")}, ""); + testLex("identifier", "foo bar123", + {Token(Token::Kind::IDENTIFIER, "foo"), + Token(Token::Kind::IDENTIFIER, "bar123")}, ""); +} + +TEST(Lexer, TestComments) +{ + // TODO(dzc): Test does not look at fodder yet. + testLex("c++ comment", "// hi", {}, ""); + testLex("hash comment", "# hi", {}, ""); + testLex("c comment", "/* hi */", {}, ""); + testLex("c comment no term", "/* hi", {}, + "c comment no term:1:1: Multi-line comment has no terminating */."); +} + +} // namespace diff --git a/core/libjsonnet_test.cc b/core/libjsonnet_test.cc deleted file mode 100644 index ef39b1770..000000000 --- a/core/libjsonnet_test.cc +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2016 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. - -extern "C" { -#include "libjsonnet.h" -} - -#include "gtest/gtest.h" - -TEST(JsonnetTest, TestEvaluateSnippet) { - const char* snippet = "std.assertEqual(({ x: 1, y: self.x } { x: 2 }).y, 2)"; - struct JsonnetVm* vm = jsonnet_make(); - ASSERT_FALSE(vm == nullptr); - int error = 0; - char* output = jsonnet_evaluate_snippet(vm, "snippet", snippet, &error); - EXPECT_EQ(0, error); - jsonnet_realloc(vm, output, 0); - jsonnet_destroy(vm); -} diff --git a/core/libjsonnet_test.cpp b/core/libjsonnet_test.cpp new file mode 100644 index 000000000..051ffd8f1 --- /dev/null +++ b/core/libjsonnet_test.cpp @@ -0,0 +1,33 @@ +/* +Copyright 2015 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. +*/ + +extern "C" { + #include "libjsonnet.h" +} + +#include "gtest/gtest.h" + +TEST(JsonnetTest, TestEvaluateSnippet) +{ + const char* snippet = "std.assertEqual(({ x: 1, y: self.x } { x: 2 }).y, 2)"; + struct JsonnetVm* vm = jsonnet_make(); + ASSERT_FALSE(vm == nullptr); + int error = 0; + char* output = jsonnet_evaluate_snippet(vm, "snippet", snippet, &error); + EXPECT_EQ(0, error); + jsonnet_realloc(vm, output, 0); + jsonnet_destroy(vm); +} diff --git a/core/parser_test.cpp b/core/parser_test.cpp new file mode 100644 index 000000000..9d8a6ccc6 --- /dev/null +++ b/core/parser_test.cpp @@ -0,0 +1,398 @@ +/* +Copyright 2015 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. +*/ + +#include "parser.h" + +#include +#include "ast.h" +#include "lexer.h" +#include "gtest/gtest.h" + +namespace { + +// Checks whether the provided snippet parses successfully. +// TODO(dzc): Update this test to check the parsed AST against an expected AST. +void testParse(const char* snippet) +{ + try { + std::list tokens = jsonnet_lex("test", snippet); + Allocator allocator; + AST* ast = jsonnet_parse(&allocator, tokens); + (void)ast; + } catch (StaticError& e) { + ASSERT_TRUE(false) + << "Static error: " << e.toString() << std::endl + << "Snippet:" << std::endl + << snippet << std::endl; + } +} + +TEST(Parser, TestLiterals) +{ + testParse("true"); + testParse("1"); + testParse("1.2e3"); + testParse("!true"); + testParse("null"); + testParse(R"("world")"); + testParse(R"("world")"); + testParse("|||\n world\n|||"); +} + +TEST(Parser, TestExpressions) +{ + testParse("$.foo.bar"); + testParse("self.foo.bar"); + testParse("super.foo.bar"); + testParse("super[1]"); + testParse(R"(error "Error!")"); + + testParse("foo(bar)"); + testParse("foo(bar) tailstrict"); + testParse("foo.bar"); + testParse("foo[bar]"); + + testParse("true || false"); + testParse("0 && 1 || 0"); + testParse("0 && (1 || 0)"); +} + +TEST(Parser, TestLocal) +{ + testParse(R"(local foo = "bar"; foo)"); + testParse("local foo(bar) = bar; foo(1)"); + testParse(R"({ local foo = "bar", baz: 1})"); + testParse("{ local foo(bar) = bar, baz: foo(1)}"); +} + +TEST(Parser, TestArray) +{ + testParse("[]"); + testParse("[a, b, c]"); + testParse("[x for x in [1,2,3] ]"); + testParse("[x for x in [1,2,3] if x <= 2]"); + testParse("[x+y for x in [1,2,3] if x <= 2 for y in [4, 5, 6]]"); +} + +TEST(Parser, TestTuple) +{ + testParse("{ foo(bar, baz): bar+baz }"); + + testParse(R"({ ["foo" + "bar"]: 3 })"); + testParse(R"({ ["field" + x]: x for x in [1, 2, 3] })"); + testParse(R"({ local y = x, ["field" + x]: x for x in [1, 2, 3] })"); + testParse(R"({ ["field" + x]: x for x in [1, 2, 3] if x <= 2 })"); + testParse(R"({ ["field" + x + y]:)" + R"( x + y for x in [1, 2, 3] if x <= 2 for y in [4, 5, 6]})"); + + testParse("{}"); + testParse(R"({ hello: "world" })"); + testParse(R"({ hello +: "world" })"); + testParse(R"({ + hello: "world", + "name":: joe, + 'mood'::: "happy", + ||| + key type +|||: "block", +})"); + + testParse("assert true: 'woah!'; true"); + testParse("{ assert true: 'woah!', foo: bar }"); + + testParse("if n > 1 then 'foos' else 'foo'"); + + testParse("local foo = function(x) x + 1; true"); + + testParse("import 'foo.jsonnet'"); + testParse("importstr 'foo.text'"); + + testParse("{a: b} + {c: d}"); + testParse("{a: b}{c: d}"); +} + +void testParseError(const char* snippet, const std::string& expectedError) +{ + try { + std::list tokens = jsonnet_lex("test", snippet); + Allocator allocator; + AST* ast = jsonnet_parse(&allocator, tokens); + (void)ast; + } catch (StaticError& e) { + ASSERT_EQ(expectedError, e.toString()) + << "Snippet:" << std::endl + << snippet << std::endl; + } +} + +TEST(Parser, TestInvalidFunctionCall) +{ + testParseError( + "function(a, b c)", + "test:1:15: Expected a comma before next function parameter."); + testParseError( + "function(a, 1)", + "test:1:13: Expected an identifier but got a complex expression."); + testParseError("a b", R"(test:1:3: Did not expect: (IDENTIFIER, "b"))"); + testParseError( + "foo(a, bar(a b))", + "test:1:14: Expected a comma before next function argument."); +} + +TEST(Parser, TestInvalidLocal) +{ + testParseError("local", "test:1:6: Expected token IDENTIFIER but got end of file"); + testParseError( + "local foo = 1, foo = 2; true", + "test:1:16-18: Duplicate local var: foo"); + testParseError( + "local foo(a b) = a; true", + "test:1:13: Expected a comma before next function parameter."); + testParseError( + "local foo(a): a; true", + "test:1:13: Expected operator = but got :"); + testParseError( + "local foo(a) = bar(a b); true", + "test:1:22: Expected a comma before next function argument."); + testParseError( + "local foo: 1; true", + "test:1:10: Expected operator = but got :"); + testParseError( + "local foo = bar(a b); true", + "test:1:19: Expected a comma before next function argument."); + + testParseError( + "local a = b ()", + "test:1:15: Expected , or ; but got end of file"); + testParseError( + "local a = b; (a b)", + R"_(test:1:17: Expected token ")" but got (IDENTIFIER, "b"))_"); +} + +TEST(Parser, TestInvalidTuple) +{ + testParseError( + "{a b}", + R"(test:1:4: Expected token OPERATOR but got (IDENTIFIER, "b"))"); + testParseError( + "{a = b}", + "test:1:2: Expected one of :, ::, :::, +:, +::, +:::, got: ="); + testParseError( + "{a :::: b}", + "test:1:2: Expected one of :, ::, :::, +:, +::, +:::, got: ::::"); +} + +TEST(Parser, TestInvalidComprehension) +{ + testParseError( + "{assert x for x in [1, 2, 3]}", + "test:1:11-13: Object comprehension cannot have asserts."); + testParseError( + "{['foo' + x]: true, [x]: x for x in [1, 2, 3]}", + "test:1:28-30: Object comprehension can only have one field."); + testParseError( + "{foo: x for x in [1, 2, 3]}", + "test:1:9-11: Object comprehensions can only have [e] fields."); + testParseError( + "{[x]:: true for x in [1, 2, 3]}", + "test:1:13-15: Object comprehensions cannot have hidden fields."); + testParseError( + "{[x]: true for 1 in [1, 2, 3]}", + R"(test:1:16: Expected token IDENTIFIER but got (NUMBER, "1"))"); + testParseError( + "{[x]: true for x at [1, 2, 3]}", + R"(test:1:18-19: Expected token in but got (IDENTIFIER, "at"))"); + testParseError( + "{[x]: true for x in [1, 2 3]}", + "test:1:27: Expected a comma before next array element."); + testParseError( + "{[x]: true for x in [1, 2, 3] if (a b)}", + R"_(test:1:37: Expected token ")" but got (IDENTIFIER, "b"))_"); + testParseError( + "{[x]: true for x in [1, 2, 3] if a b}", + R"(test:1:36: Expected for, if or "}" after for clause,)" + R"( got: (IDENTIFIER, "b"))"); +} + +TEST(Parser, TestInvalidNoComma) +{ + testParseError("{a: b c:d}", "test:1:7: Expected a comma before next field."); +} + +TEST(Parser, TestInvalidArrayKey) +{ + testParseError("{[(x y)]: z}", R"_(test:1:6: Expected token ")" but got (IDENTIFIER, "y"))_"); + testParseError("{[x y]: z}", R"(test:1:5: Expected token "]" but got (IDENTIFIER, "y"))"); +} + +TEST(Parser, TestInvalidFields) +{ + testParseError("{foo(x y): z}", "test:1:8: Expected a comma before next method parameter."); + testParseError("{foo(x)+: z}", "test:1:2-4: Cannot use +: syntax sugar in a method: foo"); + testParseError("{foo: 1, foo: 2}", "test:1:10-12: Duplicate field: foo"); + testParseError("{foo: (1 2)}", R"_(test:1:10: Expected token ")" but got (NUMBER, "2"))_"); +} + +TEST(Parser, TestInvalidLocalInTuple) +{ + testParseError( + "{local 1 = 3, true}", + R"(test:1:8: Expected token IDENTIFIER but got (NUMBER, "1"))"); + testParseError( + "{local foo = 1, local foo = 2, true}", + "test:1:23-25: Duplicate local var: foo"); + testParseError( + "{local foo(a b) = 1, a: true}", + "test:1:14: Expected a comma before next function parameter."); + testParseError( + "{local foo(a): 1, a: true}", + "test:1:14: Expected operator = but got :"); + testParseError( + "{local foo(a) = (a b), a: true}", + R"_(test:1:20: Expected token ")" but got (IDENTIFIER, "b"))_"); +} + +TEST(Parser, TestInvalidAssertInTuple) +{ + testParseError( + "{assert (a b), a: true}", + R"_(test:1:12: Expected token ")" but got (IDENTIFIER, "b"))_"); + testParseError( + "{assert a: (a b), a: true}", + R"_(test:1:15: Expected token ")" but got (IDENTIFIER, "b"))_"); +} + +TEST(Parser, TestInvalidUnexpectedFunction) +{ + // TODO(jsonnet-team): The following error output differs from the Go + // implementation, which is: + // test:1:2-10 Unexpected: (function, "function") while parsing field + // definition. + testParseError( + "{function(a, b) a+b: true}", + "test:1:2-9: Unexpected: function while parsing field definition"); +} + +TEST(Parser, TestInvalidArray) +{ + testParseError( + "[(a b), 2, 3]", + R"_(test:1:5: Expected token ")" but got (IDENTIFIER, "b"))_"); + testParseError( + "[1, (a b), 2, 3]", + R"_(test:1:8: Expected token ")" but got (IDENTIFIER, "b"))_"); + testParseError( + "[a for b in [1 2 3]]", + "test:1:16: Expected a comma before next array element."); +} + +TEST(Parser, TestInvalidExpression) +{ + // TODO(jsonnet-team): The error output of the following differs from the Go + // implementation, which is: + // test:1:1-4 Unexpected: (for, "for") while parsing terminal) + testParseError("for", "test:1:1-3: Unexpected: for while parsing terminal"); + testParseError("", "test:1:1: Unexpected end of file."); + testParseError("((a b))", R"_(test:1:5: Expected token ")" but got (IDENTIFIER, "b"))_"); + testParseError("a.1", R"(test:1:3: Expected token IDENTIFIER but got (NUMBER, "1"))"); + testParseError("super.1", R"(test:1:7: Expected token IDENTIFIER but got (NUMBER, "1"))"); + testParseError("super[(a b)]", R"_(test:1:10: Expected token ")" but got (IDENTIFIER, "b"))_"); + testParseError("super[a b]", R"(test:1:9: Expected token "]" but got (IDENTIFIER, "b"))"); + testParseError("super", "test:1:1-5: Expected . or [ after super."); +} + +TEST(Parser, TestInvalidAssert) +{ + testParseError( + "assert (a b); true", + R"_(test:1:11: Expected token ")" but got (IDENTIFIER, "b"))_"); + testParseError( + "assert a: (a b); true", + R"_(test:1:14: Expected token ")" but got (IDENTIFIER, "b"))_"); + // TODO(jsonnet-team): The error output of this differs from the Go + // implementation, which is: + // test:1:16: Expected token ";" but got (",", ",") + testParseError( + "assert a: 'foo', true", + R"(test:1:16: Expected token ";" but got ",")"); + testParseError( + "assert a: 'foo'; (a b)", + R"_(test:1:21: Expected token ")" but got (IDENTIFIER, "b"))_"); +} + +TEST(Parser, TestInvalidError) +{ + testParseError("error (a b)", R"_(test:1:10: Expected token ")" but got (IDENTIFIER, "b"))_"); +} + +TEST(Parser, TestInvalidIf) +{ + testParseError( + "if (a b) then c", + R"_(test:1:7: Expected token ")" but got (IDENTIFIER, "b"))_"); + testParseError( + "if a b c", + R"(test:1:6: Expected token then but got (IDENTIFIER, "b"))"); + testParseError( + "if a then (b c)", + R"_(test:1:14: Expected token ")" but got (IDENTIFIER, "c"))_"); + testParseError( + "if a then b else (c d)", + R"_(test:1:21: Expected token ")" but got (IDENTIFIER, "d"))_"); +} + +TEST(Parser, TestInvalidFunction) +{ + testParseError( + "function(a) (a b)", + R"_(test:1:16: Expected token ")" but got (IDENTIFIER, "b"))_"); + testParseError("function a a", R"(test:1:10: Expected ( but got (IDENTIFIER, "a"))"); +} + +TEST(Parser, TestInvalidImport) +{ + testParseError("import (a b)", R"_(test:1:11: Expected token ")" but got (IDENTIFIER, "b"))_"); + testParseError("import (a+b)", "test:1:8-12: Computed imports are not allowed."); + testParseError( + "importstr (a b)", + R"_(test:1:14: Expected token ")" but got (IDENTIFIER, "b"))_"); + testParseError( + "importstr (a+b)", + "test:1:11-15: Computed imports are not allowed."); +} + +TEST(Parser, TestInvalidOperator) +{ + testParseError("1+ <<", "test:1:4-5: Not a unary operator: <<"); + testParseError("-(a b)", R"_(test:1:5: Expected token ")" but got (IDENTIFIER, "b"))_"); + testParseError("1~2", "test:1:2: Not a binary operator: ~"); +} + +TEST(Parser, TestInvalidArrayAccess) +{ + testParseError("a[(b c)]", R"_(test:1:6: Expected token ")" but got (IDENTIFIER, "c"))_"); + // TODO(jsonnet-team): The error output of this differs from the Go + // implementation, which is: + // test:1:5: Expected token "]" but got (IDENTIFIER, "c") + testParseError("a[b c]", "test:1:5: Unexpected: IDENTIFIER while parsing slice"); +} + +TEST(Parser, TestInvalidOverride) +{ + testParseError( "a{b c}", R"(test:1:5: Expected token OPERATOR but got (IDENTIFIER, "c"))"); +} + +} // namespace diff --git a/cpp/BUILD b/cpp/BUILD index ad4bd159d..c86403cf5 100644 --- a/cpp/BUILD +++ b/cpp/BUILD @@ -2,7 +2,7 @@ package(default_visibility = ["//visibility:public"]) cc_library( name = "libjsonnet++", - srcs = ["libjsonnet++.cc"], + srcs = ["libjsonnet++.cpp"], deps = [ "//core:libjsonnet", "//include:libjsonnet++", @@ -11,7 +11,7 @@ cc_library( cc_test( name = "libjsonnet++_test", - srcs = ["libjsonnet++_test.cc"], + srcs = ["libjsonnet++_test.cpp"], data = ["//cpp/testdata"], deps = [ ":libjsonnet++", diff --git a/cpp/libjsonnet++.cc b/cpp/libjsonnet++.cc deleted file mode 100644 index 1ffe15791..000000000 --- a/cpp/libjsonnet++.cc +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright 2015 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. - -#include "libjsonnet++.h" - -namespace jsonnet { - -Jsonnet::Jsonnet() {} -Jsonnet::~Jsonnet() { - if (vm_ != nullptr) { - ::jsonnet_destroy(vm_); - } -} - -/* static */ -std::string Jsonnet::Version() { - return ::jsonnet_version(); -} - -bool Jsonnet::Init() { - vm_ = static_cast(::jsonnet_make()); - return vm_ != nullptr; -} - -void Jsonnet::SetMaxStack(uint32_t depth) { - ::jsonnet_max_stack(vm_, static_cast(depth)); -} - -void Jsonnet::SetGcMinObjects(uint32_t objects) { - ::jsonnet_gc_min_objects(vm_, static_cast(objects)); -} - -void Jsonnet::SetGcGrowthTrigger(double growth) { - ::jsonnet_gc_growth_trigger(vm_, growth); -} - -void Jsonnet::SetStringOutput(bool string_output) { - ::jsonnet_string_output(vm_, string_output); -} - -void Jsonnet::AddImportPath(const std::string& path) { - ::jsonnet_jpath_add(vm_, path.c_str()); -} - -void Jsonnet::SetMaxTrace(uint32_t lines) { - ::jsonnet_max_trace(vm_, static_cast(lines)); -} - -void Jsonnet::BindExtVar(const std::string& key, const std::string& value) { - ::jsonnet_ext_var(vm_, key.c_str(), value.c_str()); -} - -void Jsonnet::BindExtCodeVar(const std::string& key, - const std::string& value) { - ::jsonnet_ext_code(vm_, key.c_str(), value.c_str()); -} - -bool Jsonnet::EvaluateFile(const std::string& filename, std::string* output) { - if (output == nullptr) { - return false; - } - int error = 0; - const char* jsonnet_output = - ::jsonnet_evaluate_file(vm_, filename.c_str(), &error); - if (error != 0) { - last_error_.assign(jsonnet_output); - return false; - } - output->assign(jsonnet_output); - return true; -} - -bool Jsonnet::EvaluateSnippet(const std::string& filename, - const std::string& snippet, - std::string* output) { - if (output == nullptr) { - return false; - } - int error = 0; - const char* jsonnet_output = ::jsonnet_evaluate_snippet( - vm_, filename.c_str(), snippet.c_str(), &error); - if (error != 0) { - last_error_.assign(jsonnet_output); - return false; - } - output->assign(jsonnet_output); - return true; -} - -namespace { -void ParseMultiOutput(const char* jsonnet_output, - std::map* outputs) { - for (const char* c = jsonnet_output; *c != '\0'; ) { - const char *filename = c; - const char *c2 = c; - while (*c2 != '\0') ++c2; - ++c2; - const char *json = c2; - while (*c2 != '\0') ++c2; - ++c2; - c = c2; - outputs->insert(std::make_pair(filename, json)); - } -} -} // namespace - -bool Jsonnet::EvaluateFileMulti( - const std::string& filename, - std::map* outputs) { - if (outputs == nullptr) { - return false; - } - int error = 0; - const char* jsonnet_output = - ::jsonnet_evaluate_file_multi(vm_, filename.c_str(), &error); - if (error != 0) { - last_error_.assign(jsonnet_output); - return false; - } - ParseMultiOutput(jsonnet_output, outputs); - return true; -} - -bool Jsonnet::EvaluateSnippetMulti( - const std::string& filename, - const std::string& snippet, - std::map* outputs) { - if (outputs == nullptr) { - return false; - } - int error = 0; - const char* jsonnet_output = ::jsonnet_evaluate_snippet_multi( - vm_, filename.c_str(), snippet.c_str(), &error); - if (error != 0) { - last_error_.assign(jsonnet_output); - return false; - } - ParseMultiOutput(jsonnet_output, outputs); - return true; -} - -std::string Jsonnet::LastError() const { - return last_error_; -} - -} // namespace jsonnet diff --git a/cpp/libjsonnet++.cpp b/cpp/libjsonnet++.cpp new file mode 100644 index 000000000..ed7d80d24 --- /dev/null +++ b/cpp/libjsonnet++.cpp @@ -0,0 +1,173 @@ +/* +Copyright 2015 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. +*/ + +#include "libjsonnet++.h" + +namespace jsonnet { + +Jsonnet::Jsonnet() +{} + +Jsonnet::~Jsonnet() +{ + if (vm_ != nullptr) { + ::jsonnet_destroy(vm_); + } +} + +/* static */ +std::string Jsonnet::version() +{ + return ::jsonnet_version(); +} + +bool Jsonnet::init() { + vm_ = static_cast(::jsonnet_make()); + return vm_ != nullptr; +} + +void Jsonnet::setMaxStack(uint32_t depth) +{ + ::jsonnet_max_stack(vm_, static_cast(depth)); +} + +void Jsonnet::setGcMinObjects(uint32_t objects) +{ + ::jsonnet_gc_min_objects(vm_, static_cast(objects)); +} + +void Jsonnet::setGcGrowthTrigger(double growth) +{ + ::jsonnet_gc_growth_trigger(vm_, growth); +} + +void Jsonnet::setStringOutput(bool string_output) +{ + ::jsonnet_string_output(vm_, string_output); +} + +void Jsonnet::addImportPath(const std::string& path) +{ + ::jsonnet_jpath_add(vm_, path.c_str()); +} + +void Jsonnet::setMaxTrace(uint32_t lines) +{ + ::jsonnet_max_trace(vm_, static_cast(lines)); +} + +void Jsonnet::bindExtVar(const std::string& key, const std::string& value) +{ + ::jsonnet_ext_var(vm_, key.c_str(), value.c_str()); +} + +void Jsonnet::bindExtCodeVar(const std::string& key, const std::string& value) +{ + ::jsonnet_ext_code(vm_, key.c_str(), value.c_str()); +} + +bool Jsonnet::evaluateFile(const std::string& filename, std::string* output) +{ + if (output == nullptr) { + return false; + } + int error = 0; + const char* jsonnet_output = ::jsonnet_evaluate_file(vm_, filename.c_str(), &error); + if (error != 0) { + last_error_.assign(jsonnet_output); + return false; + } + output->assign(jsonnet_output); + return true; +} + +bool Jsonnet::evaluateSnippet(const std::string& filename, + const std::string& snippet, + std::string* output) +{ + if (output == nullptr) { + return false; + } + int error = 0; + const char* jsonnet_output = ::jsonnet_evaluate_snippet( + vm_, filename.c_str(), snippet.c_str(), &error); + if (error != 0) { + last_error_.assign(jsonnet_output); + return false; + } + output->assign(jsonnet_output); + return true; +} + +namespace { +void parseMultiOutput(const char* jsonnet_output, + std::map* outputs) +{ + for (const char* c = jsonnet_output; *c != '\0'; ) { + const char *filename = c; + const char *c2 = c; + while (*c2 != '\0') ++c2; + ++c2; + const char *json = c2; + while (*c2 != '\0') ++c2; + ++c2; + c = c2; + outputs->insert(std::make_pair(filename, json)); + } +} +} // namespace + +bool Jsonnet::evaluateFileMulti(const std::string& filename, + std::map* outputs) +{ + if (outputs == nullptr) { + return false; + } + int error = 0; + const char* jsonnet_output = + ::jsonnet_evaluate_file_multi(vm_, filename.c_str(), &error); + if (error != 0) { + last_error_.assign(jsonnet_output); + return false; + } + parseMultiOutput(jsonnet_output, outputs); + return true; +} + +bool Jsonnet::evaluateSnippetMulti(const std::string& filename, + const std::string& snippet, + std::map* outputs) +{ + if (outputs == nullptr) { + return false; + } + int error = 0; + const char* jsonnet_output = ::jsonnet_evaluate_snippet_multi( + vm_, filename.c_str(), snippet.c_str(), &error); + if (error != 0) { + last_error_.assign(jsonnet_output); + return false; + } + parseMultiOutput(jsonnet_output, outputs); + return true; +} + +std::string Jsonnet::lastError() const +{ + return last_error_; +} + +} // namespace jsonnet diff --git a/cpp/libjsonnet++_test.cc b/cpp/libjsonnet++_test.cc deleted file mode 100644 index 8048a9d1f..000000000 --- a/cpp/libjsonnet++_test.cc +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2016 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. - -#include "libjsonnet++.h" - -#include -#include -#include - -#include "gtest/gtest.h" - -namespace jsonnet { - -std::string ReadFile(const std::string& filename) { - std::ifstream in(filename); - return std::string(std::istreambuf_iterator(in), - std::istreambuf_iterator()); -} - -TEST(JsonnetTest, TestEvaluateSnippet) { - const std::string input = ReadFile("cpp/testdata/example.jsonnet"); - const std::string expected = ReadFile("cpp/testdata/example_golden.json"); - - Jsonnet jsonnet; - ASSERT_TRUE(jsonnet.Init()); - std::string output; - EXPECT_TRUE(jsonnet.EvaluateSnippet("snippet", input, &output)); - EXPECT_EQ(expected, output); - EXPECT_EQ("", jsonnet.LastError()); -} - -TEST(JsonnetTest, TestEvaluateInvalidSnippet) { - const std::string input = ReadFile("cpp/testdata/invalid.jsonnet"); - const std::string error = ReadFile("cpp/testdata/invalid.out"); - - Jsonnet jsonnet; - ASSERT_TRUE(jsonnet.Init()); - std::string output; - EXPECT_FALSE(jsonnet.EvaluateSnippet("cpp/testdata/invalid.jsonnet", - input, &output)); - EXPECT_EQ("", output); - EXPECT_EQ(error, jsonnet.LastError()); -} - -TEST(JsonnetTest, TestEvaluateFile) { - const std::string expected = ReadFile("cpp/testdata/example_golden.json"); - - Jsonnet jsonnet; - ASSERT_TRUE(jsonnet.Init()); - std::string output; - EXPECT_TRUE(jsonnet.EvaluateFile("cpp/testdata/example.jsonnet", &output)); - EXPECT_EQ(expected, output); - EXPECT_EQ("", jsonnet.LastError()); -} - -TEST(JsonnetTest, TestEvaluateInvalidFile) { - const std::string expected = ReadFile("cpp/testdata/invalid.out"); - - Jsonnet jsonnet; - ASSERT_TRUE(jsonnet.Init()); - std::string output; - EXPECT_FALSE(jsonnet.EvaluateFile("cpp/testdata/invalid.jsonnet", &output)); - EXPECT_EQ("", output); - EXPECT_EQ(expected, jsonnet.LastError()); -} - -TEST(JsonnetTest, TestAddImportPath) { - const std::string expected = ReadFile("cpp/testdata/importing_golden.json"); - - Jsonnet jsonnet; - ASSERT_TRUE(jsonnet.Init()); - jsonnet.AddImportPath("cpp/testdata"); - std::string output; - EXPECT_TRUE(jsonnet.EvaluateFile("cpp/testdata/importing.jsonnet", &output)); - EXPECT_EQ(expected, output); - EXPECT_EQ("", jsonnet.LastError()); -} - -} // namespace jsonnet diff --git a/cpp/libjsonnet++_test.cpp b/cpp/libjsonnet++_test.cpp new file mode 100644 index 000000000..bc02665d1 --- /dev/null +++ b/cpp/libjsonnet++_test.cpp @@ -0,0 +1,97 @@ +/* +Copyright 2015 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. +*/ + +#include "libjsonnet++.h" + +#include +#include +#include + +#include "gtest/gtest.h" + +namespace jsonnet { + +std::string readFile(const std::string& filename) +{ + std::ifstream in(filename); + return std::string(std::istreambuf_iterator(in), std::istreambuf_iterator()); +} + +TEST(JsonnetTest, TestEvaluateSnippet) +{ + const std::string input = readFile("cpp/testdata/example.jsonnet"); + const std::string expected = readFile("cpp/testdata/example_golden.json"); + + Jsonnet jsonnet; + ASSERT_TRUE(jsonnet.init()); + std::string output; + EXPECT_TRUE(jsonnet.evaluateSnippet("snippet", input, &output)); + EXPECT_EQ(expected, output); + EXPECT_EQ("", jsonnet.lastError()); +} + +TEST(JsonnetTest, TestEvaluateInvalidSnippet) +{ + const std::string input = readFile("cpp/testdata/invalid.jsonnet"); + const std::string error = readFile("cpp/testdata/invalid.out"); + + Jsonnet jsonnet; + ASSERT_TRUE(jsonnet.init()); + std::string output; + EXPECT_FALSE(jsonnet.evaluateSnippet("cpp/testdata/invalid.jsonnet", + input, &output)); + EXPECT_EQ("", output); + EXPECT_EQ(error, jsonnet.lastError()); +} + +TEST(JsonnetTest, TestEvaluateFile) +{ + const std::string expected = readFile("cpp/testdata/example_golden.json"); + + Jsonnet jsonnet; + ASSERT_TRUE(jsonnet.init()); + std::string output; + EXPECT_TRUE(jsonnet.evaluateFile("cpp/testdata/example.jsonnet", &output)); + EXPECT_EQ(expected, output); + EXPECT_EQ("", jsonnet.lastError()); +} + +TEST(JsonnetTest, TestEvaluateInvalidFile) +{ + const std::string expected = readFile("cpp/testdata/invalid.out"); + + Jsonnet jsonnet; + ASSERT_TRUE(jsonnet.init()); + std::string output; + EXPECT_FALSE(jsonnet.evaluateFile("cpp/testdata/invalid.jsonnet", &output)); + EXPECT_EQ("", output); + EXPECT_EQ(expected, jsonnet.lastError()); +} + +TEST(JsonnetTest, TestAddImportPath) +{ + const std::string expected = readFile("cpp/testdata/importing_golden.json"); + + Jsonnet jsonnet; + ASSERT_TRUE(jsonnet.init()); + jsonnet.addImportPath("cpp/testdata"); + std::string output; + EXPECT_TRUE(jsonnet.evaluateFile("cpp/testdata/importing.jsonnet", &output)); + EXPECT_EQ(expected, output); + EXPECT_EQ("", jsonnet.lastError()); +} + +} // namespace jsonnet diff --git a/include/libjsonnet++.h b/include/libjsonnet++.h index 8e50620d0..4cd6da19d 100644 --- a/include/libjsonnet++.h +++ b/include/libjsonnet++.h @@ -1,16 +1,18 @@ -// Copyright 2015 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. +/* +Copyright 2015 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. +*/ #ifndef CPP_JSONNET_H_ #define CPP_JSONNET_H_ @@ -21,120 +23,120 @@ #include extern "C" { - #include "libjsonnet.h" + #include "libjsonnet.h" } namespace jsonnet { class Jsonnet { - public: - Jsonnet(); - ~Jsonnet(); - - /// Return the version string of the Jsonnet interpreter. Conforms to - /// semantic versioning http://semver.org/. If this does not match - /// LIB_JSONNET_VERSION then there is a mismatch between header and compiled - /// library. - static std::string Version(); - - /// Initializes the Jsonnet VM. This method must be called before calling any - /// of the other methods. - /// - /// @return true if the Jsonnet VM was successfully initialized, false - /// otherwise. - bool Init(); - - /// Sets the maximum stack depth. - void SetMaxStack(uint32_t depth); - - /// Sets the number of objects required before a carbage collection cycle is - /// allowed. - void SetGcMinObjects(uint32_t objects); - - /// Run the garbage collector after this amount of growth in the number of - /// objects. - void SetGcGrowthTrigger(double growth); - - /// Set whether to expect a string as output and don't JSON encode it. - void SetStringOutput(bool string_output); - - /// Set the number of lines of stack trace to display (0 to display all). - void SetMaxTrace(uint32_t lines); - - /// Add to the default import callback's library search path. - void AddImportPath(const std::string& path); - - /// Bind a Jsonnet external variable to the given value. - /// - /// Argument values are copied so memory should be managed by caller. - void BindExtVar(const std::string& key, const std::string& value); - - /// Bind a Jsonnet external code variable to the given value. - /// - /// Argument values are copied so memory should be managed by caller. - void BindExtCodeVar(const std::string& key, const std::string& value); - - /// Evaluate a file containing Jsonnet code to return a JSON string. - /// - /// This method returns true if the Jsonnet code is successfully evaluated. - /// Otherwise, it returns false, and the error output can be returned by - /// calling LastError(); - /// - /// @param filename Path to a file containing Jsonnet code. - /// @param output Pointer to string to contain the output. - /// @return true if the Jsonnet code was successfully evaluated, false - /// otherwise. - bool EvaluateFile(const std::string& filename, std::string* output); - - /// Evaluate a string containing Jsonnet code to return a JSON string. - /// - /// This method returns true if the Jsonnet code is successfully evaluated. - /// Otherwise, it returns false, and the error output can be returned by - /// calling LastError(); - /// - /// @param filename Path to a file (used in error message). - /// @param snippet Jsonnet code to execute. - /// @param output Pointer to string to contain the output. - /// @return true if the Jsonnet code was successfully evaluated, false - /// otherwise. - bool EvaluateSnippet(const std::string& filename, - const std::string& snippet, - std::string* output); - - /// Evaluate a file containing Jsonnet code, return a number of JSON files. - /// - /// This method returns true if the Jsonnet code is successfully evaluated. - /// Otherwise, it returns false, and the error output can be returned by - /// calling LastError(); - /// - /// @param filename Path to a file containing Jsonnet code. - /// @param outputs Pointer to map which will store the output map of filename - /// to JSON string. - bool EvaluateFileMulti(const std::string& filename, - std::map* outputs); - - /// Evaluate a string containing Jsonnet code, return a number of JSON files. - /// - /// This method returns true if the Jsonnet code is successfully evaluated. - /// Otherwise, it returns false, and the error output can be returned by - /// calling LastError(); - /// - /// @param filename Path to a file containing Jsonnet code. - /// @param snippet Jsonnet code to execute. - /// @param outputs Pointer to map which will store the output map of filename - /// to JSON string. - /// @return true if the Jsonnet code was successfully evaluated, false - /// otherwise. - bool EvaluateSnippetMulti(const std::string& filename, - const std::string& snippet, - std::map* outputs); - - /// Returns the last error raised by Jsonnet. - std::string LastError() const; - - private: - struct JsonnetVm* vm_; - std::string last_error_; + public: + Jsonnet(); + ~Jsonnet(); + + /// Return the version string of the Jsonnet interpreter. Conforms to + /// semantic versioning http://semver.org/. If this does not match + /// LIB_JSONNET_VERSION then there is a mismatch between header and compiled + /// library. + static std::string version(); + + /// Initializes the Jsonnet VM. This method must be called before calling any + /// of the other methods. + /// + /// @return true if the Jsonnet VM was successfully initialized, false + /// otherwise. + bool init(); + + /// Sets the maximum stack depth. + void setMaxStack(uint32_t depth); + + /// Sets the number of objects required before a carbage collection cycle is + /// allowed. + void setGcMinObjects(uint32_t objects); + + /// Run the garbage collector after this amount of growth in the number of + /// objects. + void setGcGrowthTrigger(double growth); + + /// Set whether to expect a string as output and don't JSON encode it. + void setStringOutput(bool string_output); + + /// Set the number of lines of stack trace to display (0 to display all). + void setMaxTrace(uint32_t lines); + + /// Add to the default import callback's library search path. + void addImportPath(const std::string& path); + + /// Bind a Jsonnet external variable to the given value. + /// + /// Argument values are copied so memory should be managed by caller. + void bindExtVar(const std::string& key, const std::string& value); + + /// Bind a Jsonnet external code variable to the given value. + /// + /// Argument values are copied so memory should be managed by caller. + void bindExtCodeVar(const std::string& key, const std::string& value); + + /// Evaluate a file containing Jsonnet code to return a JSON string. + /// + /// This method returns true if the Jsonnet code is successfully evaluated. + /// Otherwise, it returns false, and the error output can be returned by + /// calling LastError(); + /// + /// @param filename Path to a file containing Jsonnet code. + /// @param output Pointer to string to contain the output. + /// @return true if the Jsonnet code was successfully evaluated, false + /// otherwise. + bool evaluateFile(const std::string& filename, std::string* output); + + /// Evaluate a string containing Jsonnet code to return a JSON string. + /// + /// This method returns true if the Jsonnet code is successfully evaluated. + /// Otherwise, it returns false, and the error output can be returned by + /// calling LastError(); + /// + /// @param filename Path to a file (used in error message). + /// @param snippet Jsonnet code to execute. + /// @param output Pointer to string to contain the output. + /// @return true if the Jsonnet code was successfully evaluated, false + /// otherwise. + bool evaluateSnippet(const std::string& filename, + const std::string& snippet, + std::string* output); + + /// Evaluate a file containing Jsonnet code, return a number of JSON files. + /// + /// This method returns true if the Jsonnet code is successfully evaluated. + /// Otherwise, it returns false, and the error output can be returned by + /// calling LastError(); + /// + /// @param filename Path to a file containing Jsonnet code. + /// @param outputs Pointer to map which will store the output map of filename + /// to JSON string. + bool evaluateFileMulti(const std::string& filename, + std::map* outputs); + + /// Evaluate a string containing Jsonnet code, return a number of JSON files. + /// + /// This method returns true if the Jsonnet code is successfully evaluated. + /// Otherwise, it returns false, and the error output can be returned by + /// calling LastError(); + /// + /// @param filename Path to a file containing Jsonnet code. + /// @param snippet Jsonnet code to execute. + /// @param outputs Pointer to map which will store the output map of filename + /// to JSON string. + /// @return true if the Jsonnet code was successfully evaluated, false + /// otherwise. + bool evaluateSnippetMulti(const std::string& filename, + const std::string& snippet, + std::map* outputs); + + /// Returns the last error raised by Jsonnet. + std::string lastError() const; + + private: + struct JsonnetVm* vm_; + std::string last_error_; }; } // namespace jsonnet