Skip to content

Commit

Permalink
Make "this" the implicit context object
Browse files Browse the repository at this point in the history
Summary:
Today `.` is used as the implicit context object. But there's a problem...

An expression like `(func .)` has the intent of calling `func` with the value of the implicit context object. However, the parser thinks that `.` is a separator for variable lookup. The grammar is just ambiguous here.

The fix is to take a keyword we've already reserved, `this`, and make it also refer to the implicit context object.

Reviewed By: yoney

Differential Revision: D68005178

fbshipit-source-id: d34cf65e350ca70ae6dc061bda7c033bc041d78b
  • Loading branch information
praihan authored and facebook-github-bot committed Jan 10, 2025
1 parent 838a170 commit f16fdb5
Show file tree
Hide file tree
Showing 4 changed files with 17 additions and 6 deletions.
2 changes: 1 addition & 1 deletion thrift/compiler/whisker/ast.cc
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ std::string to_joined_string(
std::string variable_lookup::chain_string() const {
return detail::variant_match(
chain,
[](this_ref) -> std::string { return "."; },
[](this_ref) -> std::string { return "this"; },
[](const std::vector<identifier>& ids) -> std::string {
return to_joined_string(
ids, '.', [](const identifier& id) -> const std::string& {
Expand Down
5 changes: 3 additions & 2 deletions thrift/compiler/whisker/parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -918,12 +918,13 @@ class parser {
scan};
}

// variable-lookup → { "." | (identifier ~ ("." ~ identifier)*) }
// variable-lookup → { "." | "this" | (identifier ~ ("." ~ identifier)*) }
parse_result<ast::variable_lookup> parse_variable_lookup(
parser_scan_window scan) {
assert(scan.empty());
const auto scan_start = scan.start;
if (try_consume_token(&scan, tok::dot)) {
if (try_consume_token(&scan, tok::dot) ||
try_consume_token(&scan, tok::kw_this)) {
return {
ast::variable_lookup{
scan.with_start(scan_start).range(),
Expand Down
12 changes: 11 additions & 1 deletion thrift/compiler/whisker/test/parser_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ TEST_F(ParserTest, variable_is_dot) {
EXPECT_EQ(
to_string(*ast),
"root [path/to/test-1.whisker]\n"
"|- interpolation <line:1:1, col:7> '.'\n");
"|- interpolation <line:1:1, col:7> 'this'\n");
}

TEST_F(ParserTest, variable_starts_with_dot) {
Expand Down Expand Up @@ -803,6 +803,16 @@ TEST_F(ParserTest, let_statement) {
"| `- expression <line:1:14, col:30> '(not true_value)'\n");
}

TEST_F(ParserTest, let_statement_with_implicit_context) {
auto ast = parse_ast("{{#let foo = (a.b.c this)}}");
EXPECT_EQ(
to_string(*ast),
"root [path/to/test-1.whisker]\n"
"|- let-statement <line:1:1, col:28>\n"
"| `- identifier 'foo'\n"
"| `- expression <line:1:14, col:26> '(a.b.c this)'\n");
}

TEST_F(ParserTest, let_statement_dotted_name) {
auto ast = parse_ast("{{#let foo.bar = (not true_value)}}");
EXPECT_THAT(
Expand Down
4 changes: 2 additions & 2 deletions thrift/doc/contributions/whisker.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ There are three types of `expression`s in Whisker:
* `boolean-literal` — either `true` or `false`.
* `null-literal`*exactly* `null`.
* ***Variable*** — values interpolated from the *context*, like `{{ person.name }}`. Variables allow scoped access into the context using a series of *identifiers* separated by dots (`.`).
* Additionally, the special *implicit context* variable, `{{ . }}`, refers to the *context* object itself.
* Additionally, the special *implicit context* variable, `{{ . }}` or `{{ this }}`, refers to the *context* object itself.
* ***Function call***[lisp-like](https://en.wikipedia.org/wiki/Lisp_(programming_language)#Syntax_and_semantics) syntax for invoking functions, often referred to as [S-expressions](https://en.wikipedia.org/wiki/S-expression). Functions are defined in the [*data model*](#data-model). Examples:
* `{{ (uppercase "hello") }}`
* `{{ (concat (uppercase person.firstName) " " (uppercase person.lastName)) }}`
Expand Down Expand Up @@ -216,7 +216,7 @@ interpolation → { "{{" ~ expression ~ "}}" }
expression → { literal | variable | function-call }
literal → { string-literal | i64-literal | boolean-literal | null-literal }
variable → { "." | (identifier ~ ("." ~ identifier)*) }
variable → { "." | "this" | (identifier ~ ("." ~ identifier)*) }
function-call → { "(" ~ (builtin-call | user-defined-call) ~ ")" }
string-literal → { <see above> }
Expand Down

0 comments on commit f16fdb5

Please sign in to comment.