def parse_install(ctx, tokens, breakstack): """ The ``install()`` command has multiple different forms, implemented by different functions. The forms are indicated by the first token and are: * TARGETS * FILES * DIRECTORY * SCRIPT * CODE * EXPORT :see: https://cmake.org/cmake/help/v3.0/command/install.html """ descriminator_token = get_first_semantic_token(tokens) if descriminator_token is None: logger.warning("Invalid install() command at %s", tokens[0].get_location()) return StandardArgTree.parse(ctx, tokens, npargs='*', kwargs={}, flags=[], breakstack=breakstack) descriminator = descriminator_token.spelling.upper() parsemap = { "TARGETS": parse_install_targets, "FILES": parse_install_files, "PROGRAMS": parse_install_files, "DIRECTORY": parse_install_directory, "SCRIPT": parse_install_script, "CODE": parse_install_script, "EXPORT": parse_install_export } if descriminator not in parsemap: logger.warning("Invalid install form \"%s\" at %s", descriminator, tokens[0].location()) return StandardArgTree.parse(ctx, tokens, npargs='*', kwargs={}, flags=[], breakstack=breakstack) return parsemap[descriminator](ctx, tokens, breakstack)
def parse_add_custom_command(ctx, tokens, breakstack): """ There are two forms of `add_custom_command`. This is the dispatcher between the two forms. """ descriminator_token = get_first_semantic_token(tokens) if (descriminator_token is None or descriminator_token.type is TokenType.RIGHT_PAREN): location = () if tokens: location = tokens[0].get_location() logger.warning("Invalid empty file() command at %s", location) ctx.lint_ctx.record_lint("E1120", location=location) return StandardArgTree.parse(ctx, tokens, npargs='*', kwargs={}, flags=[], breakstack=breakstack) if descriminator_token.type is TokenType.DEREF: ctx.lint_ctx.record_lint("C0114", location=descriminator_token.get_location()) return parse_add_custom_command_standard(ctx, tokens, breakstack) descriminator_word = get_normalized_kwarg(descriminator_token) if descriminator_word == "TARGET": return parse_add_custom_command_events(ctx, tokens, breakstack) if descriminator_word == "OUTPUT": return parse_add_custom_command_standard(ctx, tokens, breakstack) logger.warning("Indeterminate form of add_custom_command \"%s\" at %s", descriminator_word, descriminator_token.location()) ctx.lint_ctx.record_lint("E1126", location=descriminator_token.get_location()) return parse_add_custom_command_standard(ctx, tokens, breakstack)
def parse_file(ctx, tokens, breakstack): """ The ``file()`` command has a lot of different forms, depending on the first argument. This function just dispatches the correct parse implementation for the given form:: Reading file(READ <filename> <out-var> [...]) file(STRINGS <filename> <out-var> [...]) file(<HASH> <filename> <out-var>) file(TIMESTAMP <filename> <out-var> [...]) Writing file({WRITE | APPEND} <filename> <content>...) file({TOUCH | TOUCH_NOCREATE} [<file>...]) file(GENERATE OUTPUT <output-file> [...]) Filesystem file({GLOB | GLOB_RECURSE} <out-var> [...] [<globbing-expr>...]) file(RENAME <oldname> <newname>) file({REMOVE | REMOVE_RECURSE } [<files>...]) file(MAKE_DIRECTORY [<dir>...]) file({COPY | INSTALL} <file>... DESTINATION <dir> [...]) file(SIZE <filename> <out-var>) file(READ_SYMLINK <linkname> <out-var>) file(CREATE_LINK <original> <linkname> [...]) Path Conversion file(RELATIVE_PATH <out-var> <directory> <file>) file({TO_CMAKE_PATH | TO_NATIVE_PATH} <path> <out-var>) Transfer file(DOWNLOAD <url> <file> [...]) file(UPLOAD <file> <url> [...]) Locking file(LOCK <path> [...]) :see: https://cmake.org/cmake/help/v3.14/command/file.html """ descriminator_token = get_first_semantic_token(tokens) if (descriminator_token is None or descriminator_token.type is TokenType.RIGHT_PAREN): location = () if tokens: location = tokens[0].get_location() logger.warning("Invalid empty file() command at %s", location) ctx.lint_ctx.record_lint("E1120", location=location) return StandardArgTree.parse(ctx, tokens, npargs='*', kwargs={}, flags=[], breakstack=breakstack) if descriminator_token.type is TokenType.DEREF: ctx.lint_ctx.record_lint("C0114", location=descriminator_token.get_location()) return StandardArgTree.parse(ctx, tokens, npargs='*', kwargs={}, flags=[], breakstack=breakstack) descriminator = descriminator_token.spelling.upper() parsemap = { "READ": parse_file_read, "STRINGS": parse_file_strings, "TIMESTAMP": parse_file_timestamp, "WRITE": parse_file_write, "APPEND": parse_file_write, "TOUCH": StandardParser('+', flags=["TOUCH"]), "TOUCH_NOCREATE": StandardParser('+', flags=["TOUCH_NOCREATE"]), "GENERATE": parse_file_generate_output, "GLOB": parse_file_glob, "GLOB_RECURSE": parse_file_glob, "RENAME": StandardParser(3, flags=["RENAME"]), "REMOVE": StandardParser('+', flags=["REMOVE"]), "REMOVE_RECURSE": StandardParser('+', flags=["REMOVE_RECURSE"]), "MAKE_DIRECTORY": StandardParser('+', flags=["MAKE_DIRECTORY"]), "COPY": parse_file_copy, "INSTALL": parse_file_copy, "SIZE": StandardParser(3, flags=["SIZE"]), "READ_SYMLINK": StandardParser(3, flags=["READ_SYMLINK"]), "CREATE_LINK": parse_file_create_link, "RELATIVE_PATH": StandardParser(4, flags=["RELATIVE_PATH"]), "TO_CMAKE_PATH": StandardParser(3, flags=["TO_CMAKE_PATH"]), "TO_NATIVE_PATH": StandardParser(3, flags=["TO_NATIVE_PATH"]), "DOWNLOAD": parse_file_xfer, "UPLOAD": parse_file_xfer, "LOCK": parse_file_lock } for hashname in HASH_STRINGS: parsemap[hashname] = parse_file_hash if descriminator not in parsemap: logger.warning("Indeterminate form of file() command \"%s\" at %s", descriminator, tokens[0].location()) ctx.lint_ctx.record_lint("E1126", location=descriminator_token.get_location()) return StandardArgTree.parse(ctx, tokens, npargs='*', kwargs={}, flags=[], breakstack=breakstack) return parsemap[descriminator](ctx, tokens, breakstack)
def parse_list(ctx, tokens, breakstack): """ Reading list(LENGTH <list> <out-var>) list(GET <list> <element index> [<index> ...] <out-var>) list(JOIN <list> <glue> <out-var>) list(SUBLIST <list> <begin> <length> <out-var>) Search list(FIND <list> <value> <out-var>) Modification list(APPEND <list> [<element>...]) list(FILTER <list> {INCLUDE | EXCLUDE} REGEX <regex>) list(INSERT <list> <index> [<element>...]) list(POP_BACK <list> [<out-var>...]) list(POP_FRONT <list> [<out-var>...]) list(PREPEND <list> [<element>...]) list(REMOVE_ITEM <list> <value>...) list(REMOVE_AT <list> <index>...) list(REMOVE_DUPLICATES <list>) list(TRANSFORM <list> <ACTION> [...]) Ordering list(REVERSE <list>) list(SORT <list> [...]) TRANSFORM actions: list(TRANSFORM <list> <APPEND|PREPEND> <value> ...) list(TRANSFORM <list> <TOLOWER|TOUPPER> ...) list(TRANSFORM <list> STRIP ...) list(TRANSFORM <list> GENEX_STRIP ...) list(TRANSFORM <list> REPLACE <regular_expression> <replace_expression> ...) TRANSFORM selectors list(TRANSFORM <list> <ACTION> AT <index> [<index> ...] ...) list(TRANSFORM <list> <ACTION> FOR <start> <stop> [<step>] ...) list(TRANSFORM <list> <ACTION> REGEX <regular_expression> ...) :see: https://cmake.org/cmake/help/latest/command/list.html """ descriminator_token = get_first_semantic_token(tokens) if (descriminator_token is None or descriminator_token.type is TokenType.RIGHT_PAREN): location = () if tokens: location = tokens[0].get_location() logger.warning("Invalid empty list() command at %s", location) ctx.lint_ctx.record_lint("E1120", location=location) return StandardArgTree.parse(ctx, tokens, npargs='*', kwargs={}, flags=[], breakstack=breakstack) if descriminator_token.type is TokenType.DEREF: ctx.lint_ctx.record_lint("C0114", location=descriminator_token.get_location()) return StandardArgTree.parse(ctx, tokens, npargs='*', kwargs={}, flags=[], breakstack=breakstack) descriminator = descriminator_token.spelling.upper() parsemap = { "LENGTH": StandardParser(3, flags=["LENGTH"]), "GET": StandardParser("4+", flags=["GET"]), "JOIN": StandardParser(4, flags=["JOIN"]), "SUBLIST": StandardParser(5, flags=["SUBLIST"]), "FIND": StandardParser(4, flags=["FIND"]), "APPEND": StandardParser("2+", flags=["APPEND"]), "FILTER": StandardParser(5, flags=["FILTER", "INCLUDE", "EXCLUDE", "REGEX"]), "INSERT": StandardParser("4+", flags=["INSERT"]), "POP_BACK": StandardParser("2+", flags=["POP_BACK"]), "POP_FRONT": StandardParser("2+", flags=["POP_FRONT"]), "PREPEND": StandardParser("2+", flags=["PREPEND"]), "REMOVE_ITEM": StandardParser("3+", flags=["REMOVE_ITEM"]), "REMOVE_AT": StandardParser("3+", flags=["REMOVE_AT"]), "REMOVE_DUPLICATES": StandardParser(2, flags=["REMOVE_DUPLICATES"]), "TRANSFORM": StandardParser(2, TRANSFORM_KWARGS), "REVERSE": StandardParser(2, flags=["REVERSE"]), "SORT": StandardParser(2, kwargs=SORT_KWARGS) } if descriminator not in parsemap: logger.warning("Indeterminate form of file() command \"%s\" at %s", descriminator, tokens[0].location()) ctx.lint_ctx.record_lint("E1126", location=descriminator_token.get_location()) return StandardArgTree.parse(ctx, tokens, npargs='*', kwargs={}, flags=[], breakstack=breakstack) return parsemap[descriminator](ctx, tokens, breakstack)
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