def get_target_info_smart(target, mode="lowest"): """Converts target into a length 2 Python version tuple. Modes: - "lowest" (default): Gets the lowest version supported by the target. - "highest": Gets the highest version supported by the target. - "nearest": Gets the supported version that is nearest to the current one. - "mypy": Gets the version to use for --mypy.""" supported_vers = get_vers_for_target(target) if mode == "lowest": return supported_vers[0] elif mode == "highest": return supported_vers[-1] elif mode == "nearest": sys_ver = sys.version_info[:2] if sys_ver in supported_vers: return sys_ver elif sys_ver > supported_vers[-1]: return supported_vers[-1] elif sys_ver < supported_vers[0]: return supported_vers[0] else: raise CoconutInternalException("invalid sys version", sys_ver) elif mode == "mypy": if any(v[0] == 2 for v in supported_vers): return supported_py2_vers[-1] else: return supported_vers[-1] else: raise CoconutInternalException("unknown get_target_info_smart mode", mode)
def transform(grammar, text): """Transform text by replacing matches to grammar.""" results = [] intervals = [] for result, start, stop in all_matches(grammar, text): if result is not ignore_transform: internal_assert(isinstance(result, str), "got non-string transform result", result) if start == 0 and stop == len(text): return result results.append(result) intervals.append((start, stop)) if not results: return None split_indices = [0] split_indices.extend(start for start, _ in intervals) split_indices.extend(stop for _, stop in intervals) split_indices.sort() split_indices.append(None) out = [] for i in range(len(split_indices) - 1): if i % 2 == 0: start, stop = split_indices[i], split_indices[i + 1] out.append(text[start:stop]) else: out.append(results[i // 2]) if i // 2 < len(results) - 1: raise CoconutInternalException("unused transform results", results[i // 2 + 1:]) if stop is not None: raise CoconutInternalException("failed to properly split text to be transformed") return "".join(out)
def transform(grammar, text): """Transforms text by replacing matches to grammar.""" results = [] intervals = [] for tokens, start, stop in grammar.parseWithTabs().scanString(text): if len(tokens) != 1: raise CoconutInternalException("invalid transform result tokens", tokens) results.append(tokens[0]) intervals.append((start, stop)) if not results: return None split_indices = [0] split_indices.extend(start for start, _ in intervals) split_indices.extend(stop for _, stop in intervals) split_indices.sort() split_indices.append(None) out = [] for i in range(len(split_indices) - 1): if i % 2 == 0: start, stop = split_indices[i], split_indices[i + 1] out.append(text[start:stop]) else: out.append(results[i // 2]) if i // 2 < len(results) - 1: raise CoconutInternalException("unused transform results", results[i // 2 + 1:]) if stop is not None: raise CoconutInternalException("failed to properly split text to be transformed") return "".join(out)
def addskip(skips, skip): """Adds a line skip to the skips.""" if skip < 1: complain(CoconutInternalException("invalid skip of line " + str(skip))) elif skip in skips: complain(CoconutInternalException("duplicate skip of line " + str(skip))) else: skips.add(skip) return skips
def show_tabulated(self, begin, middle, end): """Shows a tabulated message.""" if len(begin) < info_tabulation: self.show(begin + " " * (info_tabulation - len(begin)) + middle + " " + end) else: raise CoconutInternalException("info message too long", begin)
def find_new_value(value, toklist, new_toklist): """Find the value in new_toklist that corresponds to the given value in toklist.""" # find ParseResults by looking up their tokens if isinstance(value, ParseResults): if value._ParseResults__toklist == toklist: new_value_toklist = new_toklist else: new_value_toklist = [] for inner_value in value._ParseResults__toklist: new_value_toklist.append(find_new_value(inner_value, toklist, new_toklist)) return ParseResults(new_value_toklist) # find other objects by looking them up directly try: return new_toklist[toklist.index(value)] except ValueError: complain( lambda: CoconutInternalException( "inefficient reevaluation of tokens: {} not in {}".format( value, toklist, ), ), ) return evaluate_tokens(value)
def match_sequence(self, tokens, item): """Matches a sequence.""" tail = None if len(tokens) == 2: series_type, matches = tokens else: series_type, matches, tail = tokens self.add_check("_coconut.isinstance(" + item + ", _coconut.abc.Sequence)") if tail is None: self.add_check("_coconut.len(" + item + ") == " + str(len(matches))) else: self.add_check("_coconut.len(" + item + ") >= " + str(len(matches))) if len(matches): splice = "[" + str(len(matches)) + ":]" else: splice = "" if series_type == "(": self.add_def(tail + " = _coconut.tuple(" + item + splice + ")") elif series_type == "[": self.add_def(tail + " = _coconut.list(" + item + splice + ")") else: raise CoconutInternalException("invalid series match type", series_type) self.match_all_in(matches, item)
def match_msequence(self, tokens, item): """Matches a middle sequence.""" series_type, head_matches, middle, _, last_matches = tokens self.add_check("_coconut.isinstance(" + item + ", _coconut.abc.Sequence)") self.add_check("_coconut.len(" + item + ") >= " + str(len(head_matches) + len(last_matches))) if len(head_matches) and len(last_matches): splice = "[" + str( len(head_matches)) + ":" + str(-len(last_matches)) + "]" elif len(head_matches): splice = "[" + str(len(head_matches)) + ":]" elif len(last_matches): splice = "[:" + str(-len(last_matches)) + "]" else: splice = "" if series_type == "(": self.add_def(middle + " = _coconut.tuple(" + item + splice + ")") elif series_type == "[": self.add_def(middle + " = _coconut.list(" + item + splice + ")") else: raise CoconutInternalException("invalid series match type", series_type) self.match_all_in(head_matches, item) for x in range(len(last_matches)): self.match(last_matches[x], item + "[" + str(x - len(last_matches)) + "]")
def match(self, tokens, item): """Performs pattern-matching processing.""" for flag, get_handler in self.matchers.items(): if flag in tokens.keys(): return get_handler(self)(tokens, item) raise CoconutInternalException("invalid pattern-matching tokens", tokens)
def split_data_or_class_match(self, tokens): """Split data/class match tokens into cls_name, pos_matches, name_matches, star_match.""" cls_name, matches = tokens pos_matches = [] name_matches = {} star_match = None for match_arg in matches: if len(match_arg) == 1: match, = match_arg if star_match is not None: raise CoconutDeferredSyntaxError("positional arg after starred arg in data/class match", self.loc) if name_matches: raise CoconutDeferredSyntaxError("positional arg after named arg in data/class match", self.loc) pos_matches.append(match) elif len(match_arg) == 2: internal_assert(match_arg[0] == "*", "invalid starred data/class match arg tokens", match_arg) _, match = match_arg if star_match is not None: raise CoconutDeferredSyntaxError("duplicate starred arg in data/class match", self.loc) if name_matches: raise CoconutDeferredSyntaxError("both starred arg and named arg in data/class match", self.loc) star_match = match elif len(match_arg) == 3: internal_assert(match_arg[1] == "=", "invalid named data/class match arg tokens", match_arg) name, _, match = match_arg if star_match is not None: raise CoconutDeferredSyntaxError("both named arg and starred arg in data/class match", self.loc) if name in name_matches: raise CoconutDeferredSyntaxError("duplicate named arg {name!r} in data/class match".format(name=name), self.loc) name_matches[name] = match else: raise CoconutInternalException("invalid data/class match arg", match_arg) return cls_name, pos_matches, name_matches, star_match
def evaluate_tokens(tokens): """Evaluate the given tokens in the computation graph.""" if isinstance(tokens, str): return tokens elif isinstance(tokens, ParseResults): # evaluate the list portion of the ParseResults toklist, name, asList, modal = tokens.__getnewargs__() new_toklist = [evaluate_tokens(toks) for toks in toklist] new_tokens = ParseResults(new_toklist, name, asList, modal) # evaluate the dictionary portion of the ParseResults new_tokdict = {} for name, occurrences in tokens._ParseResults__tokdict.items(): new_occurences = [] for value, position in occurrences: new_value = find_new_value(value, toklist, new_toklist) new_occurences.append(_ParseResultsWithOffset(new_value, position)) new_tokdict[name] = occurrences new_tokens._ParseResults__accumNames.update(tokens._ParseResults__accumNames) new_tokens._ParseResults__tokdict.update(new_tokdict) return new_tokens elif isinstance(tokens, ComputationNode): return tokens.evaluate() elif isinstance(tokens, (list, tuple)): return [evaluate_tokens(inner_toks) for inner_toks in tokens] else: raise CoconutInternalException("invalid computation graph tokens", tokens)
def assign_to_series(self, name, series_type, item): """Assign name to item converted to the given series_type.""" if self.using_python_rules or series_type == "[": self.add_def(name + " = _coconut.list(" + item + ")") elif series_type == "(": self.add_def(name + " = _coconut.tuple(" + item + ")") else: raise CoconutInternalException("invalid series match type", series_type)
def compile(self, codepath, destpath=None, package=False, run=False, force=False, show_unchanged=True): """Compile a source Coconut file to a destination Python file.""" with openfile(codepath, "r") as opened: code = readfile(opened) if destpath is not None: destdir = os.path.dirname(destpath) if not os.path.exists(destdir): os.makedirs(destdir) if package is True: self.create_package(destdir) foundhash = None if force else self.has_hash_of( destpath, code, package) if foundhash: if show_unchanged: logger.show_tabulated("Left unchanged", showpath(destpath), "(pass --force to override).") if self.show: print(foundhash) if run: self.execute_file(destpath) else: logger.show_tabulated("Compiling", showpath(codepath), "...") if package is True: compile_method = "parse_package" elif package is False: compile_method = "parse_file" else: raise CoconutInternalException("invalid value for package", package) def callback(compiled): if destpath is None: logger.show_tabulated("Compiled", showpath(codepath), "without writing to file.") else: with openfile(destpath, "w") as opened: writefile(opened, compiled) logger.show_tabulated("Compiled to", showpath(destpath), ".") if self.show: print(compiled) if run: if destpath is None: self.execute(compiled, path=codepath, allow_show=False) else: self.execute_file(destpath) self.submit_comp_job(codepath, callback, compile_method, code)
def get_vers_for_target(target): """Gets a list of the versions supported by the given target.""" target_info = get_target_info(target) if not target_info: return supported_py2_vers + supported_py3_vers elif len(target_info) == 1: if target_info == (2,): return supported_py2_vers elif target_info == (3,): return supported_py3_vers else: raise CoconutInternalException("invalid target info", target_info) elif target_info[0] == 2: return tuple(ver for ver in supported_py2_vers if ver >= target_info) elif target_info[0] == 3: return tuple(ver for ver in supported_py3_vers if ver >= target_info) else: raise CoconutInternalException("invalid target info", target_info)
def longest(*args): """Match the longest of the given grammar elements.""" if len(args) < 2: raise CoconutInternalException("longest expected at least two args") else: matcher = args[0] + skip_whitespace for elem in args[1:]: matcher ^= elem + skip_whitespace return matcher
def match_trailer(self, tokens, item): """Matches typedefs and as patterns.""" if len(tokens) <= 1 or len(tokens) % 2 != 1: raise CoconutInternalException("invalid trailer match tokens", tokens) else: match, trailers = tokens[0], tokens[1:] for i in range(0, len(trailers), 2): op, arg = trailers[i], trailers[i + 1] if op == "is": self.add_check("_coconut.isinstance(" + item + ", " + arg + ")") elif op == "as": if arg in self.names: self.add_check(self.names[arg] + " == " + item) elif arg != wildcard: self.add_def(arg + " = " + item) self.names[arg] = item else: raise CoconutInternalException("invalid trailer match operation", op) self.match(match, item)
def match_set(self, tokens, item): """Matches a set.""" if len(tokens) == 1: match = tokens[0] else: raise CoconutInternalException("invalid set match tokens", tokens) self.add_check("_coconut.isinstance(" + item + ", _coconut.abc.Set)") self.add_check("_coconut.len(" + item + ") == " + str(len(match))) for const in match: self.add_check(const + " in " + item)
def wrapped_handler(s, l, t): self.log_trace(handler.__name__, s, l, t) try: return _trim_arity(handler)(s, l, t) except CoconutException: raise except Exception: raise CoconutInternalException( "error calling handler " + handler.__name__ + " with tokens", t)
def evaluate_tokens(tokens): """Evaluate the given tokens in the computation graph.""" if isinstance(tokens, str): return tokens elif isinstance(tokens, ParseResults): # evaluate the list portion of the ParseResults toklist, name, asList, modal = tokens.__getnewargs__() new_toklist = [evaluate_tokens(toks) for toks in toklist] new_tokens = ParseResults(new_toklist, name, asList, modal) # evaluate the dictionary portion of the ParseResults new_tokdict = {} for name, occurrences in tokens._ParseResults__tokdict.items(): new_occurences = [] for value, position in occurrences: if isinstance(value, ParseResults ) and value._ParseResults__toklist == toklist: new_value = new_tokens else: try: new_value = new_toklist[toklist.index(value)] except ValueError: complain(lambda: CoconutInternalException( "inefficient reevaluation of tokens: {} not in {}". format( value, toklist, ))) new_value = evaluate_tokens(value) new_occurences.append( _ParseResultsWithOffset(new_value, position)) new_tokdict[name] = occurrences new_tokens._ParseResults__accumNames.update( tokens._ParseResults__accumNames) new_tokens._ParseResults__tokdict.update(new_tokdict) return new_tokens elif isinstance(tokens, ComputationNode): return tokens.evaluate() elif isinstance(tokens, (list, tuple)): return [evaluate_tokens(inner_toks) for inner_toks in tokens] else: raise CoconutInternalException("invalid computation graph tokens", tokens)
def input(self, more=False): """Prompts for code input.""" if more: msg = more_prompt else: msg = main_prompt if self.style is None: return input(msg) elif prompt_toolkit is None: raise CoconutInternalException("cannot highlight style without prompt_toolkit", self.style) else: return prompt_toolkit.prompt(msg, **self.prompt_kwargs())
def match_trailer(self, tokens, item): """Matches typedefs and as patterns.""" internal_assert(len(tokens) > 1 and len(tokens) % 2 == 1, "invalid trailer match tokens", tokens) match, trailers = tokens[0], tokens[1:] for i in range(0, len(trailers), 2): op, arg = trailers[i], trailers[i + 1] if op == "as": self.match_var([arg], item, bind_wildcard=True) elif op == "is": self.add_check("_coconut.isinstance(" + item + ", " + arg + ")") else: raise CoconutInternalException("invalid trailer match operation", op) self.match(match, item)
def do_complete(self, code, cursor_pos): # first try with Jedi completions self.use_experimental_completions = True try: return super(CoconutKernel, self).do_complete(code, cursor_pos) except Exception: logger.print_exc() logger.warn_err(CoconutInternalException("experimental IPython completion failed, defaulting to shell completion"), force=True) # then if that fails default to shell completions self.use_experimental_completions = False try: return super(CoconutKernel, self).do_complete(code, cursor_pos) finally: self.use_experimental_completions = True
def get_vers_for_target(target): """Gets a list of the versions supported by the given target.""" target_info = get_target_info(target) if not target_info: return py2_vers + py3_vers elif len(target_info) == 1: if target_info == (2, ): return py2_vers elif target_info == (3, ): return py3_vers else: raise CoconutInternalException("invalid target info", target_info) elif target_info == (3, 3): return [(3, 3), (3, 4)] else: return [target_info[:2]]
def match_rsequence(self, tokens, item): """Matches a reverse sequence.""" front, series_type, matches = tokens self.add_check("_coconut.isinstance(" + item + ", _coconut.abc.Sequence)") self.add_check("_coconut.len(" + item + ") >= " + str(len(matches))) if len(matches): splice = "[:" + str(-len(matches)) + "]" else: splice = "" if series_type == "(": self.add_def(front + " = _coconut.tuple(" + item + splice + ")") elif series_type == "[": self.add_def(front + " = _coconut.list(" + item + splice + ")") else: raise CoconutInternalException("invalid series match type", series_type) for x in range(len(matches)): self.match(matches[x], item + "[" + str(x - len(matches)) + "]")
def get_target_info_len2(target, lowest=False): """By default, gets the highest version supported by the target before the next target. If lowest is passed, instead gets the lowest version supported by the target.""" target_info = get_target_info(target) if not target_info: return (2, 6) if lowest else (2, 7) elif len(target_info) == 1: if target_info == (2, ): return (2, 6) if lowest else (2, 7) elif target_info == (3, ): return (3, 2) if lowest else (3, 4) else: raise CoconutInternalException("invalid target info", target_info) elif len(target_info) == 2: return target_info else: return target_info[:2]
def match_data(self, tokens, item): """Matches a data type.""" if len(tokens) == 2: data_type, matches = tokens star_match = None elif len(tokens) == 3: data_type, matches, star_match = tokens else: raise CoconutInternalException("invalid data match tokens", tokens) self.add_check("_coconut.isinstance(" + item + ", " + data_type + ")") if star_match is None: self.add_check("_coconut.len(" + item + ") == " + str(len(matches))) elif len(matches): self.add_check("_coconut.len(" + item + ") >= " + str(len(matches))) self.match_all_in(matches, item) if star_match is not None: self.match(star_match, item + "[" + str(len(matches)) + ":]")
def run_cmd(cmd, show_output=True, raise_errs=True): """Runs a console command.""" if not isinstance(cmd, list): raise CoconutInternalException("console commands must be passed as lists") else: if sys.version_info >= (3, 3): import shutil cmd[0] = shutil.which(cmd[0]) or cmd[0] logger.log_cmd(cmd) if show_output and raise_errs: return subprocess.check_call(cmd) elif raise_errs: return subprocess.check_output(cmd, stderr=subprocess.STDOUT) elif show_output: return subprocess.call(cmd) else: return "".join(call_output(cmd))
def minify(compiled): """Performs basic minifications (fails with strings or non-tabideal indentation).""" compiled = compiled.strip() if compiled: out = [] for line in compiled.splitlines(): line = line.split("#", 1)[0].rstrip() if line: ind = 0 while line.startswith(" "): line = line[1:] ind += 1 if ind % tabideal != 0: raise CoconutInternalException("invalid indentation in", line) out.append(" " * (ind // tabideal) + line) compiled = "\n".join(out) + "\n" return compiled
def evaluate(self): """Get the result of evaluating the computation graph at this node.""" if DEVELOP: internal_assert(not self.been_called, "inefficient reevaluation of action " + self.name + " with tokens", self.tokens) self.been_called = True evaluated_toks = evaluate_tokens(self.tokens) if logger.tracing: # avoid the overhead of the call if not tracing logger.log_trace(self.name, self.original, self.loc, evaluated_toks, self.tokens) try: return _trim_arity(self.action)( self.original, self.loc, evaluated_toks, ) except CoconutException: raise except (Exception, AssertionError): traceback.print_exc() raise CoconutInternalException("error computing action " + self.name + " of evaluated tokens", evaluated_toks)
def get_target_info_len2(target, mode="lowest"): """Converts target into a length 2 Python version tuple. Modes: - "lowest" (default): Gets the lowest version supported by the target. - "highest": Gets the highest version supported by the target. - "nearest": If the current version is supported, returns that, otherwise gets the highest.""" supported_vers = get_vers_for_target(target) if mode == "lowest": return supported_vers[0] elif mode == "highest": return supported_vers[-1] elif mode == "nearest": if sys.version_info[:2] in supported_vers: return sys.version_info[:2] else: return supported_vers[-1] else: raise CoconutInternalException("unknown get_target_info_len2 mode", mode)