blob: 749a663e17c369414b19574db4675678c38a6aff [file] [log] [blame]
// Copyright 2017-2020 The Verible Authors.
//
// 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.
// verilog_lexical_context.cc implements LexicalContext.
#include "verilog/parser/verilog_lexical_context.h"
#include <iostream>
#include <set>
#include <stack>
#include <vector>
#include "common/text/token_info.h"
#include "common/util/logging.h"
#include "verilog/parser/verilog_token_enum.h"
namespace verilog {
using verible::TokenInfo;
using verible::WithReason;
// Returns true for begin/end-like tokens that can be followed with an optional
// label.
// TODO(fangism): move this to verilog_token_classifications.cc
static bool KeywordAcceptsOptionalLabel(int token_enum) {
static const auto* keywords = new std::set<int>(
{// begin-like keywords
TK_begin, TK_fork, TK_generate,
// end-like keywords
TK_end, TK_endgenerate, TK_endcase, TK_endconfig, TK_endfunction,
TK_endmodule, TK_endprimitive, TK_endspecify, TK_endtable, TK_endtask,
TK_endclass, TK_endclocking, TK_endgroup, TK_endinterface, TK_endpackage,
TK_endprogram, TK_endproperty, TK_endsequence, TK_endchecker,
TK_endconnectrules, TK_enddiscipline, TK_endnature, TK_endparamset,
TK_join, TK_join_any, TK_join_none});
return keywords->find(token_enum) != keywords->end();
}
void _KeywordLabelStateMachine::UpdateState(int token_enum) {
// In any state, reset on encountering keyword.
if (KeywordAcceptsOptionalLabel(token_enum)) {
state_ = kGotLabelableKeyword;
return;
}
// Scan for optional : label.
switch (state_) {
case kItemStart:
state_ = kItemMiddle;
break;
case kItemMiddle:
break;
case kGotLabelableKeyword:
if (token_enum == ':') {
state_ = kGotColonExpectingLabel;
} else {
state_ = kItemStart;
}
break;
case kGotColonExpectingLabel:
// Expect a SymbolIdentifier as a label, but don't really care if it
// actually is or not.
state_ = kItemStart;
break;
}
}
std::ostream& _ConstraintBlockStateMachine::Dump(std::ostream& os) const {
os << '[' << states_.size() << ']';
if (!states_.empty()) {
os << ": top:" << int(states_.top());
}
return os;
}
void _ConstraintBlockStateMachine::DeferInvalidToken(int token_enum) {
// On invalid syntax, defer handling of token to previous state on the
// stack. If stack is empty, exit the state machine entirely.
states_.pop();
if (IsActive()) UpdateState(token_enum);
}
void _ConstraintBlockStateMachine::UpdateState(int token_enum) {
if (!IsActive()) {
if (token_enum == '{') {
// Activate this state machine.
states_.push(kBeginningOfBlockItemOrExpression);
}
return;
}
// In verilog.y grammar:
// see constraint_block, constraint_block_item, constraint_expression rules.
State& top(states_.top());
switch (top) {
case kBeginningOfBlockItemOrExpression: {
// Depending on the next token, push into next state, so that
// after each list item 'pops', it returns to this state.
switch (token_enum) {
case TK_soft: // fall-through
case TK_unique: // fall-through
case TK_disable: // fall-through
case TK_solve:
states_.push(kIgnoreUntilSemicolon);
break;
case TK_if:
states_.push(kGotIf);
break;
case TK_else:
states_.push(kExpectingConstraintSet); // the else-clause
break;
case TK_foreach:
states_.push(kGotForeach);
break;
case '(':
states_.push(kExpectingExpressionOrImplication);
states_.push(kInParenExpression);
break;
case '{':
states_.push(kExpectingExpressionOrImplication);
states_.push(kInBraceExpression);
break;
case '}':
states_.pop();
break; // de-activates if this is the last level
default:
states_.push(kExpectingExpressionOrImplication);
}
break;
}
case kInParenExpression: {
switch (token_enum) {
case '(':
states_.push(kInParenExpression);
break;
case ')':
states_.pop();
break;
case '{':
states_.push(kInBraceExpression);
break;
default: // ignore everything else
break;
}
break;
}
case kInBraceExpression: {
switch (token_enum) {
case '{':
states_.push(kInBraceExpression);
break;
case '}':
states_.pop();
break;
case '(':
states_.push(kInParenExpression);
break;
default: // ignore everything else
break;
}
break;
}
case kExpectingExpressionOrImplication: {
switch (token_enum) {
case '{':
states_.push(kInBraceExpression);
break;
case '(':
states_.push(kInParenExpression);
break;
case '}':
// Invalid in this state, but possibly valid in parent state.
DeferInvalidToken(token_enum);
break;
case _TK_RARROW: // fall-through (before interpretation)
case TK_CONSTRAINT_IMPLIES: // (after interpretation)
top = kExpectingConstraintSet; // constraint implication RHS
break;
case ';':
states_.pop();
break;
default: // ignore everything else
break;
}
break;
}
case kIgnoreUntilSemicolon: {
switch (token_enum) {
case '(':
states_.push(kInParenExpression);
break;
case '{':
states_.push(kInBraceExpression);
break;
case ')': // fall-through
case '}':
// Invalid syntax (unbalanced).
DeferInvalidToken(token_enum);
break;
case ';':
// Reset to expect constraint_block_item or constraint_expression.
states_.pop();
break;
default: // no change
break;
}
break;
}
case kGotIf: {
switch (token_enum) {
case '(':
// After () predicate, expect a constraint_set clause.
top = kExpectingConstraintSet; // the if-clause
states_.push(kInParenExpression);
break;
default:
DeferInvalidToken(token_enum); // Invalid syntax.
}
break;
}
case kGotForeach: {
switch (token_enum) {
case '(':
// After () variable list, expect a constraint_set clause.
top = kExpectingConstraintSet; // the foreach body
states_.push(kInParenExpression);
break;
default:
DeferInvalidToken(token_enum); // Invalid syntax.
}
break;
}
// A constraint_set is either a {} block or a single constraint_expression.
case kExpectingConstraintSet: {
switch (token_enum) {
case '{':
// By assigning top instead of pushing, once the block is balanced,
// it will pop back to the previous state before the construct that
// ends with a constraint_set.
top = kBeginningOfBlockItemOrExpression;
break;
default:
// goto main handler state, which will re-write the top-of-stack.
states_.pop();
UpdateState(token_enum);
}
break;
}
} // switch (top)
}
int _ConstraintBlockStateMachine::InterpretToken(int token_enum) const {
if (!IsActive() || token_enum != _TK_RARROW) return token_enum;
// The only token re-interpreted by this state machine is "->".
switch (states_.top()) {
case kExpectingExpressionOrImplication:
return TK_CONSTRAINT_IMPLIES;
default:
return TK_LOGICAL_IMPLIES;
}
}
void _RandomizeCallStateMachine::UpdateState(int token_enum) {
// EBNF for randomize_call:
// 'randomize' { attribute_instance }
// [ '(' [ variable_identifier_list | 'null' ] ')' ]
// [ 'with' [ '(' [ identifier_list ] ')' ] constraint_block ]
switch (state_) {
case kNone:
if (token_enum == TK_randomize) { // activate
state_ = kGotRandomizeKeyword;
}
break;
case kGotRandomizeKeyword:
switch (token_enum) {
case '(':
state_ = kOpenedVariableList;
break;
case TK_with:
state_ = kGotWithKeyword;
break;
default: // anything else ends the randomize_call
state_ = kNone;
}
break;
case kOpenedVariableList:
switch (token_enum) {
case ')':
state_ = kClosedVariableList;
break;
default:
break; // no state change
}
break;
case kClosedVariableList:
switch (token_enum) {
case TK_with:
state_ = kGotWithKeyword;
break;
default: // anything else ends the randomize_call
state_ = kNone;
}
break;
case kGotWithKeyword:
switch (token_enum) {
case '(':
state_ = kInsideWithIdentifierList;
break;
case '{':
state_ = kInsideConstraintBlock;
constraint_block_tracker_.UpdateState(token_enum);
break;
default:
break; // no state change
}
break;
case kInsideWithIdentifierList:
switch (token_enum) {
case ')':
state_ = kExpectConstraintBlock;
break;
default:
break; // no state change
}
break;
case kExpectConstraintBlock:
switch (token_enum) {
case '{':
state_ = kInsideConstraintBlock;
constraint_block_tracker_.UpdateState(token_enum);
break;
default: // anything else ends the randomize_call
state_ = kNone;
break;
}
break;
case kInsideConstraintBlock: {
constraint_block_tracker_.UpdateState(token_enum);
if (!constraint_block_tracker_.IsActive())
state_ = kNone; // end of randomize_call
// otherwise no state change
break;
}
}
}
int _RandomizeCallStateMachine::InterpretToken(int token_enum) const {
switch (state_) {
case kInsideConstraintBlock:
return constraint_block_tracker_.InterpretToken(token_enum);
default:
break;
}
return token_enum; // no change
}
void _ConstraintDeclarationStateMachine::UpdateState(int token_enum) {
switch (state_) {
case kNone:
switch (token_enum) {
case TK_constraint:
state_ = kGotConstraintKeyword;
break;
default:
break; // no change
}
break;
case kGotConstraintKeyword:
switch (token_enum) {
case SymbolIdentifier:
state_ = kGotConstraintIdentifier;
break;
default:
state_ = kNone; // reset
}
break;
case kGotConstraintIdentifier:
switch (token_enum) {
case '{':
state_ = kInsideConstraintBlock;
constraint_block_tracker_.UpdateState(token_enum);
break;
default:
state_ = kNone; // reset
}
break;
case kInsideConstraintBlock:
constraint_block_tracker_.UpdateState(token_enum);
if (!constraint_block_tracker_.IsActive()) {
state_ = kNone;
}
break;
}
}
int _ConstraintDeclarationStateMachine::InterpretToken(int token_enum) const {
switch (state_) {
case kInsideConstraintBlock:
return constraint_block_tracker_.InterpretToken(token_enum);
default:
break;
}
return token_enum; // no change
}
void _LastSemicolonStateMachine::UpdateState(verible::TokenInfo* token) {
switch (state_) {
case kNone:
if (token->token_enum() == trigger_token_enum_) {
state_ = kActive;
} // else remain dormant
break;
case kActive:
if (token->token_enum() == ';') {
// Bookmark this token, so that it may be re-enumerated later.
semicolons_.push(token);
} else if (token->token_enum() == finish_token_enum_) {
// Replace the desired ';' and return to dormant state.
if (previous_token_ != nullptr &&
previous_token_->token_enum() == ';') {
// Discard the optional ';' right before the end-keyword.
// <jedi>This is not the semicolon you are looking for.</jedi>
semicolons_.pop();
}
if (!semicolons_.empty()) {
// Re-enumerate this ';'
semicolons_.top()->set_token_enum(semicolon_replacement_);
}
// Reset state machine.
while (!semicolons_.empty()) { // why is there no stack::clear()??
semicolons_.pop();
}
state_ = kNone;
}
break;
}
previous_token_ = token;
}
LexicalContext::LexicalContext()
: property_declaration_tracker_(
TK_property, TK_endproperty,
SemicolonEndOfAssertionVariableDeclarations),
sequence_declaration_tracker_(
TK_sequence, TK_endsequence,
SemicolonEndOfAssertionVariableDeclarations) {}
void LexicalContext::_AdvanceToken(TokenInfo* token) {
// Note: It might not always be possible to mutate a token as it is
// encountered; it may have to be bookmarked to be returned to later after
// looking ahead.
_MutateToken(token); // only modifies token, not *this
_UpdateState(*token); // only modifies *this, not token
// The following state machines require a mutable token reference:
property_declaration_tracker_.UpdateState(token);
sequence_declaration_tracker_.UpdateState(token);
// Maintain one token look-back.
previous_token_ = token;
}
void LexicalContext::_UpdateState(const TokenInfo& token) {
// Forward tokens to concurrent sub-state-machines.
{
// Handle begin/end-like keywords with optional labels.
keyword_label_tracker_.UpdateState(token.token_enum());
// Parse randomize_call.
randomize_call_tracker_.UpdateState(token.token_enum());
// Parse constraint declarations (but not extern prototypes).
if (!in_extern_declaration_) {
constraint_declaration_tracker_.UpdateState(token.token_enum());
}
}
// Update this state machine given current token.
previous_token_finished_header_ = false;
switch (token.token_enum()) {
case '(':
balance_stack_.push_back(&token);
break;
case MacroCallCloseToEndLine: // fall-through: this is also a ')'
case ')': {
if (!balance_stack_.empty() &&
balance_stack_.back()->token_enum() == '(') {
balance_stack_.pop_back();
// Detect ')' that exits the end of a flow-control header.
// e.g. after "if (...)", "for (...)", "case (...)"
if (balance_stack_.empty() && InFlowControlHeader()) {
flow_control_stack_.back().in_body = true;
previous_token_finished_header_ = true;
}
}
break;
} // case ')'
case '{':
balance_stack_.push_back(&token);
break;
case '}': {
if (!balance_stack_.empty() &&
balance_stack_.back()->token_enum() == '{') {
balance_stack_.pop_back();
}
break;
} // case '}'
case TK_begin:
block_stack_.push_back(&token);
break;
case TK_end: {
if (!block_stack_.empty()) {
block_stack_.pop_back();
if (block_stack_.empty()) {
// Detect the 'end' of a procedural construct statement block.
// e.g. after "initial begin ... end"
if (in_initial_always_final_construct_) {
in_initial_always_final_construct_ = false;
}
}
}
break;
} // case TK_end
case ';': {
// The first ';' encountered in a function_declaration or
// task_declaration or module_declaration marks the start of the body.
// For extern declarations, however, there is no body that follows the
// header, so ';' ends the declaration.
if (in_module_declaration_) {
if (in_extern_declaration_) {
in_module_declaration_ = false;
in_extern_declaration_ = false;
} else {
in_module_body_ = true;
}
previous_token_finished_header_ = true;
}
if (in_function_declaration_) {
if (in_extern_declaration_) {
in_function_declaration_ = false;
in_extern_declaration_ = false;
} else {
in_function_body_ = true;
}
previous_token_finished_header_ = true;
} else if (in_task_declaration_) {
if (in_extern_declaration_) {
in_task_declaration_ = false;
in_extern_declaration_ = false;
} else {
in_task_body_ = true;
}
previous_token_finished_header_ = true;
}
if (in_initial_always_final_construct_) {
// Exit construct for single-statement bodies.
// e.g. initial $foo();
seen_delay_value_in_initial_always_final_construct_context = false;
if (block_stack_.empty()) {
in_initial_always_final_construct_ = false;
}
}
break;
} // case ';'
// Start of flow-control block:
case TK_if: // fall-through
case TK_for: // fall-through
case TK_case: // fall-through
case TK_casex: // fall-through
case TK_casez:
flow_control_stack_.push_back(FlowControlState(&token));
break;
// Procedural control blocks:
case TK_initial: // fall-through
case TK_always: // fall-through
case TK_always_comb: // fall-through
case TK_always_ff: // fall-through
case TK_always_latch: // fall-through
case TK_final: {
if (in_module_body_) {
in_initial_always_final_construct_ = true;
}
break;
}
// Declarations (non-nestable):
case TK_extern:
in_extern_declaration_ = true;
break;
case TK_module:
in_module_declaration_ = true;
break;
case TK_endmodule:
in_module_declaration_ = false;
in_module_body_ = false;
break;
case TK_function:
in_function_declaration_ = true;
break;
case TK_endfunction:
in_function_declaration_ = false;
in_function_body_ = false;
break;
case TK_task:
in_task_declaration_ = true;
break;
case TK_endtask:
in_task_declaration_ = false;
in_task_body_ = false;
break;
case TK_constraint:
if (in_extern_declaration_) {
in_extern_declaration_ = false; // reset
}
break;
case '#':
if (in_initial_always_final_construct_) {
seen_delay_value_in_initial_always_final_construct_context = true;
}
// std::cout << "Found delay" << std::endl;
break;
default:
break;
} // switch (token.token_enum)
}
int LexicalContext::_InterpretToken(int token_enum) const {
// Every top-level case of this switch is a token enumeration (_TK_*)
// that must be transformed into a disambiguated enumeration (TK_*).
// All other tokens pass through unmodified.
switch (token_enum) {
// '->' can be interpreted as logical implication, constraint implication,
// or event-trigger.
case _TK_RARROW: {
if (randomize_call_tracker_.IsActive()) {
// e.g.
// randomize() with {
// x -> y;
// }
return randomize_call_tracker_.InterpretToken(token_enum);
} else if (constraint_declaration_tracker_.IsActive()) {
// e.g.
// constraint c {
// x -> y;
// }
return constraint_declaration_tracker_.InterpretToken(token_enum);
} else if (ExpectingStatement()) {
// e.g.
// task foo();
// ...
// -> x;
// ...
// endtask
return TK_TRIGGER;
} else {
// Everywhere where right-arrow can appear should be interpreted
// as the 'implies' binary operator for expressions.
// e.g.
// if (a -> b) ...
return TK_LOGICAL_IMPLIES;
}
break;
}
// TODO(b/129204554): disambiguate '<='
default:
break;
}
return token_enum;
}
bool LexicalContext::InFlowControlHeader() const {
if (flow_control_stack_.empty()) return false;
return !flow_control_stack_.back().in_body;
}
bool LexicalContext::InAnyDeclaration() const {
return in_function_declaration_ || in_task_declaration_ ||
in_module_declaration_;
// TODO(fangism): handle {interface,class} declarations
}
bool LexicalContext::InAnyDeclarationHeader() const {
return InFunctionDeclarationHeader() || InTaskDeclarationHeader() ||
InModuleDeclarationHeader();
// TODO(fangism): handle {interface,class} declarations
}
bool LexicalContext::ExpectingStatement() const {
if (InStatementContext()) {
// Exclude states that are partially into a statement.
const auto state = ExpectingBodyItemStart();
VLOG(2) << __FUNCTION__ << ": " << state.value << ", " << state.reason;
return state.value;
}
// TODO(fangism): There are many more contexts that expect statements, add
// them as they are needed. In verilog.y (grammar), see statement_or_null.
return false;
}
WithReason<bool> LexicalContext::ExpectingBodyItemStart() const {
// True when immediately entering a body section.
// Usually false immediately after a keyword that starts a body item.
// Usually false inside header sections of most declarations.
// Usually false inside any () [] or {}
// Usually true immediately after a ';' or end-like tokens.
if (InFlowControlHeader()) return {false, "in flow control header"};
if (InAnyDeclarationHeader()) return {false, "in other declaration header"};
if (!balance_stack_.empty()) return {false, "balance stack not empty"};
if (previous_token_ == nullptr) {
// First token should be start of a description/package item.
return {true, "first token"};
}
if (InAnyDeclaration() && previous_token_finished_header_) {
return {true, "inside declaration, and reached end of header"};
}
switch (previous_token_->token_enum()) {
case ';':
return {true, "immediately following ';'"};
// Procedural control blocks:
case TK_initial: // fall-through
case TK_always: // fall-through
case TK_always_comb: // fall-through
case TK_always_ff: // fall-through
case TK_always_latch: // fall-through
case TK_final:
return {true, "immediately following 'always/initial/final'"};
default:
break;
}
// if (InStatementContext()) {
if (keyword_label_tracker_.ItemMayStart()) {
return {true, "item may start"};
}
// return {true, "inside 'always/initial/final'"};
// }
if (seen_delay_value_in_initial_always_final_construct_context) {
return {true, "seen a delay value, expecting another statement"};
}
return {false, "all other cases (default)"};
}
} // namespace verilog