def parse(cls, ctx, tokens, _breakstack): """ Consume a parenthetical group of arguments from `tokens` and return the parse subtree rooted at this group. `argstack` contains a stack of all early break conditions that are currently "opened". """ assert tokens[0].type == lexer.TokenType.LEFT_PAREN tree = TreeNode(NodeType.PARENGROUP) lparen = TreeNode(NodeType.LPAREN) lparen.children.append(tokens.pop(0)) tree.children.append(lparen) subtree = ConditionalGroupNode.parse(ctx, tokens, [ParenBreaker()]) tree.children.append(subtree) if tokens[0].type != lexer.TokenType.RIGHT_PAREN: raise ValueError( "Unexpected {} token at {}, expecting r-paren, got {}" .format(tokens[0].type.name, tokens[0].get_location(), tokens[0].content)) rparen = TreeNode(NodeType.RPAREN) rparen.children.append(tokens.pop(0)) tree.children.append(rparen) # NOTE(josh): parenthetical groups can have trailing comments because # they have closing punctuation CommentNode.consume_trailing(ctx, tokens, tree) return tree
def parse(cls, ctx, tokens, flags, breakstack): """ Parse a continuous sequence of flags """ # TODO(josh): use a bespoke FLAGGROUP? tree = cls() while tokens: # Break if the next token belongs to a parent parser, i.e. if it # matches a keyword argument of something higher in the stack, or if # it closes a parent group. if should_break(tokens[0], breakstack): break # If it is a whitespace token then put it directly in the parse tree at # the current depth if tokens[0].type in WHITESPACE_TOKENS: tree.children.append(tokens.pop(0)) continue # Break if the next token is not a known flag if tokens[0].spelling.upper() not in flags: break # Otherwise is it is a flag, so add it to the tree as such child = TreeNode(NodeType.FLAG) child.children.append(tokens.pop(0)) CommentNode.consume_trailing(ctx, tokens, child) tree.children.append(child) return tree
def parse_add_library_object(ctx, tokens, breakstack): """ :: add_library(<name> OBJECT <src>...) :see: https://cmake.org/cmake/help/latest/command/add_library.html#object-libraries """ tree = TreeNode(NodeType.ARGGROUP) # If it is a whitespace token then put it directly in the parse tree at # the current depth while tokens and tokens[0].type in WHITESPACE_TOKENS: tree.children.append(tokens.pop(0)) continue ntokens = len(tokens) subtree = PositionalGroupNode.parse(ctx, tokens, 2, ["OBJECT"], breakstack) assert len(tokens) < ntokens tree.children.append(subtree) while tokens: # Break if the next token belongs to a parent parser, i.e. if it # matches a keyword argument of something higher in the stack, or if # it closes a parent group. if should_break(tokens[0], breakstack): break # If it is a whitespace token then put it directly in the parse tree at # the current depth if tokens[0].type in WHITESPACE_TOKENS: tree.children.append(tokens.pop(0)) continue # If it's a comment, then add it at the current depth if tokens[0].type in (TokenType.COMMENT, TokenType.BRACKET_COMMENT): if not get_tag(tokens[0]) in ("sort", "sortable"): child = TreeNode(NodeType.COMMENT) tree.children.append(child) child.children.append(tokens.pop(0)) continue ntokens = len(tokens) subtree = PositionalGroupNode.parse(ctx, tokens, '+', [], breakstack) assert len(tokens) < ntokens tree.children.append(subtree) return tree
def parse_file_write(ctx, tokens, breakstack): """ :: file(WRITE <filename> <content>...) file(APPEND <filename> <content>...) :see: https://cmake.org/cmake/help/v3.14/command/file.html#writing """ tree = TreeNode(NodeType.ARGGROUP) consume_whitespace_and_comments(ctx, tokens, tree) tree.children.append( PositionalGroupNode.parse(ctx, tokens, 2, ["WRITE", "APPEND"], breakstack)) consume_whitespace_and_comments(ctx, tokens, tree) tree.children.append( PositionalGroupNode.parse(ctx, tokens, '+', [], breakstack)) return tree
def parse_empty(ctx, tokens, breakstack): """ :: break() continue() enable_testing() :see: https://cmake.org/cmake/help/latest/command/break.html :see: https://cmake.org/cmake/help/latest/command/continue.html :see: https://cmake.org/cmake/help/latest/command/enable_testing.html :see: https://cmake.org/cmake/help/latest/command/return.html """ tree = ArgGroupNode() while tokens: # Break if the next token belongs to a parent parser, i.e. if it # matches a keyword argument of something higher in the stack, or if # it closes a parent group. if should_break(tokens[0], breakstack): break # If it is a whitespace token then put it directly in the parse tree at # the current depth if tokens[0].type in WHITESPACE_TOKENS: tree.children.append(tokens.pop(0)) continue # If it's a comment, then add it at the current depth if tokens[0].type in (lexer.TokenType.COMMENT, lexer.TokenType.BRACKET_COMMENT): child = TreeNode(NodeType.COMMENT) tree.children.append(child) child.children.append(tokens.pop(0)) continue ctx.lint_ctx.record_lint("E1121", location=tokens[0].get_location()) ntokens = len(tokens) subtree = PositionalGroupNode.parse(ctx, tokens, "+", [], breakstack) assert len(tokens) < ntokens tree.children.append(subtree) return tree
def consume(cls, ctx, tokens): """ Consume a complete statement, removing tokens from the input list and returning a STATEMENT node. """ node = cls() # Consume the function name fnname = tokens[0].spelling.lower() node.funnode = funnode = FunctionNameNode.parse(ctx, tokens) node.children.append(funnode) # Consume whitespace up to the parenthesis while tokens and tokens[0].type in WHITESPACE_TOKENS: node.children.append(tokens.pop(0)) # TODO(josh): should the parens belong to the statement node or the # group node? if tokens[0].type != lexer.TokenType.LEFT_PAREN: raise ValueError( "Unexpected {} token at {}, expecting l-paren, got {}".format( tokens[0].type.name, tokens[0].get_location(), repr(tokens[0].content))) lparen = TreeNode(NodeType.LPAREN) lparen.children.append(tokens.pop(0)) node.children.append(lparen) while tokens and tokens[0].type in WHITESPACE_TOKENS: node.children.append(tokens.pop(0)) continue breakstack = [ParenBreaker()] parse_fun = ctx.parse_db.get(fnname, None) if parse_fun is None: # If the parse_db provides a "_default" then use that. Otherwise use the # standard parser with no kwargs or flags. parse_fun = ctx.parse_db.get("_default", StandardParser()) node.argtree = subtree = parse_fun(ctx, tokens, breakstack) node.children.append(subtree) # NOTE(josh): technically we may have a statement specification with # an exact number of arguments. At this point we have broken out of that # statement but we might have some comments or whitespace to consume while tokens and tokens[0].type != lexer.TokenType.RIGHT_PAREN: if tokens[0].type in WHITESPACE_TOKENS: node.children.append(tokens.pop(0)) continue if tokens[0].type in COMMENT_TOKENS: cnode = CommentNode.consume(ctx, tokens) node.children.append(cnode) continue raise UserError( "Unexpected {} token at {}, expecting r-paren, got {}".format( tokens[0].type.name, tokens[0].get_location(), repr(tokens[0].content))) if not tokens: raise UserError( "Unexpected end of token stream while parsing statement:\n {}". format(tree_string([node]))) if tokens[0].type != lexer.TokenType.RIGHT_PAREN: raise UserError( "Unexpected {} token at {}, expecting r-paren, got {}".format( tokens[0].type.name, tokens[0].get_location(), repr(tokens[0].content))) rparen = TreeNode(NodeType.RPAREN) rparen.children.append(tokens.pop(0)) node.children.append(rparen) CommentNode.consume_trailing(ctx, tokens, node) return node
def parse(cls, ctx, tokens, npargs, kwargs, flags, breakstack): """ Standard parser for the commands in the form of:: command_name(parg1 parg2 parg3... KEYWORD1 kwarg1 kwarg2... KEYWORD2 kwarg3 kwarg4... FLAG1 FLAG2 FLAG3) The parser starts off as a positional parser. If a keyword or flag is encountered the positional parser is popped off the parse stack. If it was a keyword then the keyword parser is pushed on the parse stack. If it was a flag than a new flag parser is pushed onto the stack. """ tree = cls() # If it is a whitespace token then put it directly in the parse tree at # the current depth while tokens and tokens[0].type in WHITESPACE_TOKENS: tree.children.append(tokens.pop(0)) continue flags = [flag.upper() for flag in flags] kwarg_breakstack = breakstack + [KwargBreaker(list(kwargs.keys()) + flags)] positional_breakstack = breakstack + [KwargBreaker(list(kwargs.keys()))] while tokens: # Break if the next token belongs to a parent parser, i.e. if it # matches a keyword argument of something higher in the stack, or if # it closes a parent group. if should_break(tokens[0], breakstack): break # If it is a whitespace token then put it directly in the parse tree at # the current depth if tokens[0].type in WHITESPACE_TOKENS: tree.children.append(tokens.pop(0)) continue # If it's a comment, then add it at the current depth if tokens[0].type in (lexer.TokenType.COMMENT, lexer.TokenType.BRACKET_COMMENT): child = TreeNode(NodeType.COMMENT) tree.children.append(child) child.children.append(tokens.pop(0)) continue ntokens = len(tokens) # NOTE(josh): each flag is also stored in kwargs as with a positional # parser of size zero. This is a legacy thing that should be removed, but # for now just make sure we check flags first. word = get_normalized_kwarg(tokens[0]) if word in kwargs: subtree = KeywordGroupNode.parse( ctx, tokens, word, kwargs[word], kwarg_breakstack) tree.kwarg_groups.append(subtree) else: subtree = PositionalGroupNode.parse( ctx, tokens, npargs, flags, positional_breakstack) tree.parg_groups.append(subtree) assert len(tokens) < ntokens, "parsed an empty subtree" tree.children.append(subtree) return tree
def parse(cls, ctx, tokens, breakstack): """ Parser for the commands that take conditional arguments. Similar to the standard parser but it understands parentheses and can generate parenthentical groups:: while(CONDITION1 AND (CONDITION2 OR CONDITION3) OR (CONDITION3 AND (CONDITION4 AND CONDITION5) OR CONDITION6) """ kwargs = { 'AND': cls.parse, 'OR': cls.parse } flags = list(CONDITIONAL_FLAGS) tree = cls() # If it is a whitespace token then put it directly in the parse tree at # the current depth while tokens and tokens[0].type in WHITESPACE_TOKENS: tree.children.append(tokens.pop(0)) continue flags = [flag.upper() for flag in flags] breaker = KwargBreaker(list(kwargs.keys())) child_breakstack = breakstack + [breaker] while tokens: # Break if the next token belongs to a parent parser, i.e. if it # matches a keyword argument of something higher in the stack, or if # it closes a parent group. if should_break(tokens[0], breakstack): break # If it is a whitespace token then put it directly in the parse tree at # the current depth if tokens[0].type in WHITESPACE_TOKENS: tree.children.append(tokens.pop(0)) continue # If it's a comment, then add it at the current depth if tokens[0].type in (lexer.TokenType.COMMENT, lexer.TokenType.BRACKET_COMMENT): child = TreeNode(NodeType.COMMENT) tree.children.append(child) child.children.append(tokens.pop(0)) continue # If this is the start of a parenthetical group, then parse the group if tokens[0].type == lexer.TokenType.LEFT_PAREN: subtree = ParenGroupNode.parse(ctx, tokens, breakstack) tree.children.append(subtree) continue ntokens = len(tokens) word = get_normalized_kwarg(tokens[0]) if word in kwargs: subtree = KeywordGroupNode.parse( ctx, tokens, word, kwargs[word], child_breakstack) assert len(tokens) < ntokens, "parsed an empty subtree" tree.children.append(subtree) continue # Otherwise is it is a positional argument, so add it to the tree as such child = PositionalGroupNode.parse( ctx, tokens, '+', flags, child_breakstack) # token = tokens.pop(0) # if get_normalized_kwarg(token) in flags: # child = TreeNode(NodeType.FLAG) # else: # child = TreeNode(NodeType.ARGUMENT) # child.children.append(token) # consume_trailing_comment(child, tokens) tree.children.append(child) return tree
def parse(cls, ctx, tokens, npargs, flags, breakstack, sortable=False): """ Parse a continuous sequence of `npargs` positional arguments. If npargs is an integer we will consume exactly that many arguments. If it is not an integer then it is a string meaning: * "?": zero or one * "*": zero or more * "+": one or more """ tree = cls(sortable=sortable) tree.spec = PositionalSpec(npargs, flags) nconsumed = 0 # Strip off any preceeding whitespace (note that in most cases this has # already been done but in some cases (such ask kwarg subparser) where # it hasn't while tokens and tokens[0].type in WHITESPACE_TOKENS: tree.children.append(tokens.pop(0)) # If the first non-whitespace token is a cmake-format tag annotating # sortability, then parse it out here and record the annotation if tokens and get_tag(tokens[0]) in ("sortable", "sort"): tree.sortable = True elif tokens and get_tag(tokens[0]) in ("unsortable", "unsort"): tree.sortable = False while tokens: # Break if we have consumed enough positional arguments if pargs_are_full(npargs, nconsumed): break # Break if the next token belongs to a parent parser, i.e. if it # matches a keyword argument of something higher in the stack, or if # it closes a parent group. if should_break(tokens[0], breakstack): # NOTE(josh): if npargs is an exact number of arguments, then we # shouldn't break on kwarg match from a parent parser. Instead, we # should consume the token. This is a hack to deal with # ```install(RUNTIME COMPONENT runtime)``. In this case the second # occurance of "runtime" should not match the ``RUNTIME`` keyword # and should not break the positional parser. # TODO(josh): this is kind of hacky because it will force the positional # parser to consume a right parenthesis and will lead to parse errors # in the event of a missing positional argument. Such errors will be # difficult to debug for the user. if not npargs_is_exact(npargs): break if tokens[0].type == lexer.TokenType.RIGHT_PAREN: break # If this is the start of a parenthetical group, then parse the group # NOTE(josh): syntatically this probably shouldn't be allowed here, but # cmake seems to accept it so we probably should too. if tokens[0].type == lexer.TokenType.LEFT_PAREN: subtree = ParenGroupNode.parse(ctx, tokens, breakstack) tree.children.append(subtree) continue # If it is a whitespace token then put it directly in the parse tree at # the current depth if tokens[0].type in WHITESPACE_TOKENS: tree.children.append(tokens.pop(0)) continue # If it's a comment token not associated with an argument, then put it # directly into the parse tree at the current depth if tokens[0].type in (lexer.TokenType.COMMENT, lexer.TokenType.BRACKET_COMMENT): before = len(tokens) child = CommentNode.consume(ctx, tokens) assert len(tokens) < before, \ "consume_comment didn't consume any tokens" tree.children.append(child) continue # Otherwise is it is a positional argument, so add it to the tree as such if get_normalized_kwarg(tokens[0]) in flags: child = TreeNode(NodeType.FLAG) else: child = TreeNode(NodeType.ARGUMENT) child.children.append(tokens.pop(0)) CommentNode.consume_trailing(ctx, tokens, child) tree.children.append(child) nconsumed += 1 return tree
def parse_add_custom_target(ctx, tokens, breakstack): """ :: add_custom_target(Name [ALL] [command1 [args1...]] [COMMAND command2 [args2...] ...] [DEPENDS depend depend depend ... ] [BYPRODUCTS [files...]] [WORKING_DIRECTORY dir] [COMMENT comment] [JOB_POOL job_pool] [VERBATIM] [USES_TERMINAL] [COMMAND_EXPAND_LISTS] [SOURCES src1 [src2...]]) :see: https://cmake.org/cmake/help/latest/command/add_custom_target.html """ kwargs = { "BYPRODUCTS": PositionalParser("+"), "COMMAND": ShellCommandNode.parse, "COMMENT": PositionalParser(1), "DEPENDS": PositionalParser("+"), "JOB_POOL": PositionalParser(1), "SOURCES": PositionalParser("+"), "WORKING_DIRECTORY": PositionalParser(1), } required_kwargs = { # Required by convention "COMMENT": "C0113" } flags = ("VERBATIM", "USES_TERMINAL", "COMMAND_EXPAND_LISTS") tree = ArgGroupNode() # If it is a whitespace token then put it directly in the parse tree at # the current depth while tokens and tokens[0].type in WHITESPACE_TOKENS: tree.children.append(tokens.pop(0)) continue breaker = KwargBreaker(list(kwargs.keys()) + list(flags)) child_breakstack = breakstack + [breaker] nametree = None state = "name" while tokens: # Break if the next token belongs to a parent parser, i.e. if it # matches a keyword argument of something higher in the stack, or if # it closes a parent group. if should_break(tokens[0], breakstack): break # If it is a whitespace token then put it directly in the parse tree at # the current depth if tokens[0].type in WHITESPACE_TOKENS: tree.children.append(tokens.pop(0)) continue # If it's a comment, then add it at the current depth if tokens[0].type in (TokenType.COMMENT, TokenType.BRACKET_COMMENT): child = TreeNode(NodeType.COMMENT) tree.children.append(child) child.children.append(tokens.pop(0)) continue ntokens = len(tokens) if state == "name": next_semantic = get_first_semantic_token(tokens[1:]) if (next_semantic is not None and get_normalized_kwarg(next_semantic) == "ALL"): npargs = 2 else: npargs = 1 nametree = PositionalGroupNode.parse(ctx, tokens, npargs, ["ALL"], child_breakstack) assert len(tokens) < ntokens tree.children.append(nametree) state = "first-command" continue word = get_normalized_kwarg(tokens[0]) if state == "first-command": if not (word in kwargs or word in flags): subtree = PositionalGroupNode.parse(ctx, tokens, '+', [], child_breakstack) tree.children.append(subtree) assert len(tokens) < ntokens state = "kwargs" continue if word in flags: subtree = FlagGroupNode.parse( ctx, tokens, flags, breakstack + [KwargBreaker(list(kwargs.keys()))]) assert len(tokens) < ntokens tree.children.append(subtree) continue if word in kwargs: required_kwargs.pop(word, None) subtree = KeywordGroupNode.parse(ctx, tokens, word, kwargs[word], child_breakstack) assert len(tokens) < ntokens tree.children.append(subtree) continue ctx.lint_ctx.record_lint("E1122", location=tokens[0].get_location()) logger.warning("Unexpected positional argument %s at %s", tokens[0].spelling, tokens[0].location()) subtree = PositionalGroupNode.parse(ctx, tokens, '+', [], child_breakstack) assert len(tokens) < ntokens tree.children.append(subtree) continue if required_kwargs: location = () for token in tree.get_semantic_tokens(): location = token.get_location() break missing_kwargs = sorted( (lintid, word) for word, lintid in sorted(required_kwargs.items())) for lintid, word in missing_kwargs: ctx.lint_ctx.record_lint(lintid, word, location=location) return tree
def parse(cls, ctx, tokens, npargs, ntup, flags, breakstack): """Parse a continuous sequence of `npargs` positional argument pairs. If npargs is an integer we will consume exactly that many arguments. If it is not an integer then it is a string meaning: * "?": zero or one * "*": zero or more * "+": one or more """ tree = cls() subtree = None active_depth = tree npargs_consumed = 0 ntup_consumed = 0 while tokens: # Break if we have consumed enough positional arguments if pargs_are_full(npargs, npargs_consumed): break # Break if the next token belongs to a parent parser, i.e. if it # matches a keyword argument of something higher in the stack, or if # it closes a parent group. if should_break(tokens[0], breakstack): break # Otherwise we will consume the token token = tokens.pop(0) # If it is a whitespace token then put it directly in the parse tree at # the current depth if token.type in WHITESPACE_TOKENS: active_depth.children.append(token) continue # If it's a comment token not associated with an argument, then put it # directly into the parse tree at the current depth if token.type in (lexer.TokenType.COMMENT, lexer.TokenType.BRACKET_COMMENT): child = CommentNode() tree.children.append(child) child.children.append(token) continue if subtree is None: subtree = PositionalGroupNode() tree.children.append(subtree) ntup_consumed = 0 # Otherwise is it is a positional argument, so add it to the tree as such if get_normalized_kwarg(token) in flags: child = TreeNode(NodeType.FLAG) else: child = TreeNode(NodeType.ARGUMENT) child.children.append(token) CommentNode.consume_trailing(ctx, tokens, child) subtree.children.append(child) ntup_consumed += 1 if ntup_consumed >= ntup: npargs_consumed += 1 subtree = None return tree
def parse_add_library_standard(ctx, tokens, breakstack, sortable): """ :: add_library(<name> [STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL] source1 [source2 ...]) :see: https://cmake.org/cmake/help/v3.0/command/add_library.html """ # pylint: disable=too-many-statements parsing_name = 1 parsing_type = 2 parsing_flag = 3 parsing_sources = 4 tree = TreeNode(NodeType.ARGGROUP) # If it is a whitespace token then put it directly in the parse tree at # the current depth while tokens and tokens[0].type in WHITESPACE_TOKENS: tree.children.append(tokens.pop(0)) continue state_ = parsing_name parg_group = None src_group = None active_depth = tree flags = ("STATIC", "SHARED", "MODULE", "OBJECT") while tokens: # This parse function breakson the first right paren, since parenthetical # groups are not allowed. A parenthesis might exist in a filename, but # if so that filename should be quoted so it wont show up as a RIGHT_PAREN # token. if tokens[0].type is TokenType.RIGHT_PAREN: break # If it is a whitespace token then put it directly in the parse tree at # the current depth if tokens[0].type in WHITESPACE_TOKENS: active_depth.children.append(tokens.pop(0)) continue # If it's a comment token not associated with an argument, then put it # directly into the parse tree at the current depth if tokens[0].type in (TokenType.COMMENT, TokenType.BRACKET_COMMENT): if state_ > parsing_name: if get_tag(tokens[0]) in ("unsort", "unsortable"): sortable = False elif get_tag(tokens[0]) in ("sort", "sortable"): sortable = True child = TreeNode(NodeType.COMMENT) active_depth.children.append(child) child.children.append(tokens.pop(0)) continue if state_ is parsing_name: token = tokens.pop(0) parg_group = PositionalGroupNode() parg_group.spec = PositionalSpec("+") active_depth = parg_group tree.children.append(parg_group) child = TreeNode(NodeType.ARGUMENT) child.children.append(token) CommentNode.consume_trailing(ctx, tokens, child) parg_group.children.append(child) state_ += 1 elif state_ is parsing_type: if get_normalized_kwarg(tokens[0]) in flags: token = tokens.pop(0) child = TreeNode(NodeType.FLAG) child.children.append(token) CommentNode.consume_trailing(ctx, tokens, child) parg_group.children.append(child) state_ += 1 elif state_ is parsing_flag: if get_normalized_kwarg(tokens[0]) == "EXCLUDE_FROM_ALL": token = tokens.pop(0) child = TreeNode(NodeType.FLAG) child.children.append(token) CommentNode.consume_trailing(ctx, tokens, child) parg_group.children.append(child) state_ += 1 src_group = PositionalGroupNode(sortable=sortable, tags=["file-list"]) src_group.spec = PositionalSpec("+") active_depth = src_group tree.children.append(src_group) elif state_ is parsing_sources: token = tokens.pop(0) child = TreeNode(NodeType.ARGUMENT) child.children.append(token) CommentNode.consume_trailing(ctx, tokens, child) src_group.children.append(child) if only_comments_and_whitespace_remain(tokens, breakstack): active_depth = tree return tree
def parse2(cls, ctx, tokens, pargspecs, kwargs, breakstack): """ Standard parser for the commands in the form of:: command_name(parg1 parg2 parg3... KEYWORD1 kwarg1 kwarg2... KEYWORD2 kwarg3 kwarg4... FLAG1 FLAG2 FLAG3) The parser starts off as a positional parser. If a keyword or flag is encountered the positional parser is popped off the parse stack. If it was a keyword then the keyword parser is pushed on the parse stack. If it was a flag than a new flag parser is pushed onto the stack. """ tree = cls() # If it is a whitespace token then put it directly in the parse tree at # the current depth while tokens and tokens[0].type in WHITESPACE_TOKENS: tree.children.append(tokens.pop(0)) continue # NOTE(josh): if there is only one legacy specification then we reuse that # specification for any additional positional arguments that we pick up. # This is to maintain the current/legacy behavior of simple positional # argument specifications default_spec = DEFAULT_PSPEC if len(pargspecs) == 1 and pargspecs[0].legacy: default_spec = pargspecs.pop(0) all_flags = list(default_spec.flags) for pspec in pargspecs: all_flags.extend(pspec.flags) kwarg_breakstack = breakstack + [ KwargBreaker(list(kwargs.keys()) + all_flags) ] while tokens: # Break if the next token belongs to a parent parser, i.e. if it # matches a keyword argument of something higher in the stack, or if # it closes a parent group. if should_break(tokens[0], breakstack): break # If it is a whitespace token then put it directly in the parse tree at # the current depth if tokens[0].type in WHITESPACE_TOKENS: tree.children.append(tokens.pop(0)) continue # If it's a comment, then add it at the current depth if tokens[0].type in (lexer.TokenType.COMMENT, lexer.TokenType.BRACKET_COMMENT): child = TreeNode(NodeType.COMMENT) tree.children.append(child) child.children.append(tokens.pop(0)) continue ntokens = len(tokens) word = get_normalized_kwarg(tokens[0]) if word in kwargs: subtree = KeywordGroupNode.parse(ctx, tokens, word, kwargs[word], kwarg_breakstack) tree.kwarg_groups.append(subtree) else: if pargspecs: pspec = pargspecs.pop(0) else: pspec = default_spec other_flags = [] for otherspec in pargspecs: for flag in otherspec.flags: if flag in pspec.flags: continue other_flags.append(flag) positional_breakstack = breakstack + [ KwargBreaker(list(kwargs.keys()) + other_flags) ] subtree = PositionalGroupNode.parse(ctx, tokens, pspec.nargs, pspec.flags, positional_breakstack) subtree.tags.extend(pspec.tags) tree.parg_groups.append(subtree) assert len(tokens) < ntokens, "parsed an empty subtree" tree.children.append(subtree) return tree
def parse_add_executable_standard(ctx, tokens, breakstack, sortable): """ :: add_executable(<name> [WIN32] [MACOSX_BUNDLE] [EXCLUDE_FROM_ALL] [source1] [source2 ...]) :see: https://cmake.org/cmake/help/latest/command/add_executable.html#command:add_executable """ # pylint: disable=too-many-statements parsing_name = 1 parsing_flags = 2 parsing_sources = 3 tree = TreeNode(NodeType.ARGGROUP) # If it is a whitespace token then put it directly in the parse tree at # the current depth while tokens and tokens[0].type in WHITESPACE_TOKENS: tree.children.append(tokens.pop(0)) continue state_ = parsing_name parg_group = None src_group = None active_depth = tree while tokens: # This parse function breaks on the first right paren, since parenthetical # groups are not allowed. A parenthesis might exist in a filename, but # if so that filename should be quoted so it wont show up as a RIGHT_PAREN # token. if tokens[0].type is lexer.TokenType.RIGHT_PAREN: break # If it is a whitespace token then put it directly in the parse tree at # the current depth if tokens[0].type in WHITESPACE_TOKENS: active_depth.children.append(tokens.pop(0)) continue # If it's a comment token not associated with an argument, then put it # directly into the parse tree at the current depth if tokens[0].type in (lexer.TokenType.COMMENT, lexer.TokenType.BRACKET_COMMENT): if state_ > parsing_name: if get_tag(tokens[0]) in ("unsort", "unsortable"): sortable = False elif get_tag(tokens[0]) in ("unsort", "unsortable"): sortable = True child = TreeNode(NodeType.COMMENT) active_depth.children.append(child) child.children.append(tokens.pop(0)) continue if state_ is parsing_name: token = tokens.pop(0) parg_group = TreeNode(NodeType.PARGGROUP) active_depth = parg_group tree.children.append(parg_group) child = TreeNode(NodeType.ARGUMENT) child.children.append(token) CommentNode.consume_trailing(ctx, tokens, child) parg_group.children.append(child) state_ += 1 elif state_ is parsing_flags: if get_normalized_kwarg(tokens[0]) in ( "WIN32", "MACOSX_BUNDLE", "EXCLUDE_FROM_ALL"): token = tokens.pop(0) child = TreeNode(NodeType.FLAG) child.children.append(token) CommentNode.consume_trailing(ctx, tokens, child) parg_group.children.append(child) else: state_ += 1 src_group = TreeNode(NodeType.PARGGROUP, sortable=sortable) active_depth = src_group tree.children.append(src_group) elif state_ is parsing_sources: token = tokens.pop(0) child = TreeNode(NodeType.ARGUMENT) child.children.append(token) CommentNode.consume_trailing(ctx, tokens, child) src_group.children.append(child) if only_comments_and_whitespace_remain(tokens, breakstack): active_depth = tree return tree
def parse_set(ctx, tokens, breakstack): """ :: set(<variable> <value> [[CACHE <type> <docstring> [FORCE]] | PARENT_SCOPE]) :see: https://cmake.org/cmake/help/v3.0/command/set.html? """ tree = SetFnNode() # If it is a whitespace token then put it directly in the parse tree at # the current depth while tokens and tokens[0].type in WHITESPACE_TOKENS: tree.children.append(tokens.pop(0)) continue kwargs = {"CACHE": PositionalParser('2+', flags=["FORCE"])} flags = ["PARENT_SCOPE"] kwarg_breakstack = breakstack + [KwargBreaker(list(kwargs.keys()) + flags)] positional_breakstack = breakstack + [KwargBreaker(list(kwargs.keys()))] ntokens = len(tokens) subtree = PositionalGroupNode.parse(ctx, tokens, 1, flags, positional_breakstack) assert len(tokens) < ntokens tree.children.append(subtree) for token in subtree.get_semantic_tokens(): tree.varname = token break while tokens: # Break if the next token belongs to a parent parser, i.e. if it # matches a keyword argument of something higher in the stack, or if # it closes a parent group. if should_break(tokens[0], breakstack): break # If it is a whitespace token then put it directly in the parse tree at # the current depth if tokens[0].type in WHITESPACE_TOKENS: tree.children.append(tokens.pop(0)) continue # If it's a comment, then add it at the current depth if tokens[0].type in (lexer.TokenType.COMMENT, lexer.TokenType.BRACKET_COMMENT): if not get_tag(tokens[0]) in ("sort", "sortable"): child = TreeNode(NodeType.COMMENT) tree.children.append(child) child.children.append(tokens.pop(0)) continue ntokens = len(tokens) # NOTE(josh): each flag is also stored in kwargs as with a positional parser # of size zero. This is a legacy thing that should be removed, but for now # just make sure we check flags first. word = get_normalized_kwarg(tokens[0]) if word == "CACHE": subtree = KeywordGroupNode.parse(ctx, tokens, word, kwargs[word], kwarg_breakstack) cache_tokens = subtree.get_semantic_tokens() cache_tokens.pop(0) cache_args = [None, None, False] for idx, token in enumerate(cache_tokens[:2]): cache_args[idx] = token.spelling if not cache_tokens: ctx.lint_ctx.record_lint("E1120", "<variable>", location=tokens[0].get_location()) elif len(cache_tokens) < 2: ctx.lint_ctx.record_lint("E1120", "<value>", location=tokens[0].get_location()) elif len(cache_tokens) < 3: pass elif len(cache_tokens) > 4: ctx.lint_ctx.record_lint( "E1121", location=cache_tokens[2].get_location()) else: if cache_tokens[2].spelling.upper() == "FORCE": cache_args[2] = True else: ctx.lint_ctx.record_lint( "E1121", location=cache_tokens[2].get_location()) tree.cache = CacheTuple(*cache_args) elif word == "PARENT_SCOPE": subtree = PositionalGroupNode.parse(ctx, tokens, '+', ["PARENT_SCOPE"], positional_breakstack) tree.parent_scope = True else: subtree = PositionalGroupNode.parse(ctx, tokens, '+', [], kwarg_breakstack) tree.value_group = subtree assert len(tokens) < ntokens tree.children.append(subtree) return tree
def parse_install_targets(ctx, tokens, breakstack): """ :: install(TARGETS targets... [EXPORT <export-name>] [[ARCHIVE|LIBRARY|RUNTIME|OBJECTS|FRAMEWORK|BUNDLE| PRIVATE_HEADER|PUBLIC_HEADER|RESOURCE] [DESTINATION <dir>] [PERMISSIONS permissions...] [CONFIGURATIONS [Debug|Release|...]] [COMPONENT <component>] [NAMELINK_COMPONENT <component>] [OPTIONAL] [EXCLUDE_FROM_ALL] [NAMELINK_ONLY|NAMELINK_SKIP] ] [...] [INCLUDES DESTINATION [<dir> ...]] ) :see: https://cmake.org/cmake/help/v3.14/command/install.html#targets """ kwargs = { "TARGETS": PositionalParser('+'), "EXPORT": PositionalParser(1), "INCLUDES": PositionalParser('+', flags=["DESTINATION"]), # Common kwargs "DESTINATION": PositionalParser(1), "PERMISSIONS": PositionalParser('+'), "CONFIGURATIONS": PositionalParser('+'), "COMPONENT": PositionalParser(1), "NAMELINK_COMPONENT": PositionalParser(1), } flags = ( "OPTIONAL", "EXCLUDE_FROM_ALL", "NAMELINK_ONLY", "NAMELINK_SKIP" ) designated_kwargs = ( "ARCHIVE", "LIBRARY", "RUNTIME", "OBJECTS", "FRAMEWORK", "BUNDLE", "PRIVATE_HEADER", "PUBLIC_HEADER", "RESOURCE" ) # NOTE(josh): from here on, code is essentially StandardArgTree.parse(), # except that # we cannot break on the common subset of kwargs in the breakstack because # they are valid kwargs for the subtrees (ARCHIVE, LIBRARY, etc) as well as # the primary tree tree = TreeNode(NodeType.ARGGROUP) # If it is a whitespace token then put it directly in the parse tree at # the current depth while tokens and tokens[0].type in WHITESPACE_TOKENS: tree.children.append(tokens.pop(0)) continue # ARCHIVE, LIBRARY, RUNTIME, subtrees etc only break on the start of # another subtree, or on "INCLUDES DESTINATION" subtree_breakstack = breakstack + [KwargBreaker( list(designated_kwargs) + ["INCLUDES"] )] # kwargs at this tree depth break on other kwargs or flags kwarg_breakstack = breakstack + [KwargBreaker( list(kwargs.keys()) + list(designated_kwargs) + list(flags) )] # and flags at this depth break only on kwargs positional_breakstack = breakstack + [KwargBreaker( list(kwargs.keys()) + list(designated_kwargs) )] while tokens: # Break if the next token belongs to a parent parser, i.e. if it # matches a keyword argument of something higher in the stack, or if # it closes a parent group. if should_break(tokens[0], breakstack): break # If it is a whitespace token then put it directly in the parse tree at # the current depth if tokens[0].type in WHITESPACE_TOKENS: tree.children.append(tokens.pop(0)) continue # If it's a comment, then add it at the current depth if tokens[0].type in (lexer.TokenType.COMMENT, lexer.TokenType.BRACKET_COMMENT): child = TreeNode(NodeType.COMMENT) tree.children.append(child) child.children.append(tokens.pop(0)) continue ntokens = len(tokens) # NOTE(josh): each flag is also stored in kwargs as with a positional parser # of size zero. This is a legacy thing that should be removed, but for now # just make sure we check flags first. word = get_normalized_kwarg(tokens[0]) if word in designated_kwargs: subtree = KeywordGroupNode.parse( ctx, tokens, word, parse_install_targets, subtree_breakstack) elif word in kwargs: subtree = KeywordGroupNode.parse( ctx, tokens, word, kwargs[word], kwarg_breakstack) else: subtree = PositionalGroupNode.parse( ctx, tokens, '+', flags, positional_breakstack) assert len(tokens) < ntokens tree.children.append(subtree) return tree