Exemplo n.º 1
0
    def test_invalid_parse(self):
        invalid_programs = [
            """
module invalid with

    main = f x + + 2
            """,

            """
module invalid some rubbish with 
    nothing
            """,

            """
module invalid with

    n = * 5 2 # '*' needs to be '(*)' for this to be valid!
            """,

            """
module invalid with
    10 + 10"""
            ]

        parser = FunkyParser()
        parser.build()
        for invalid_program in invalid_programs:
            with self.assertRaises(FunkySyntaxError):
                parser.do_parse(invalid_program)
Exemplo n.º 2
0
    def test_sanity(self):
        sanity_fails = [
            """
module test with

    tau  = 2 * pi
            """,
            """
module test with
    
    f 0 0 = 0
    f x y z = 1
            """,
            """
module test with
    
    f x x = 1
            """,
            """
module test with
    
    to_int = 10
            """,
            """
module test with
    
    to_str = 10
            """,
            """
module test with
    
    to_float = 10
            """,
            """
module test with
    newtype Test = P Integer | P Float
            """,
            """
module test with
    newtype Test  = P Integer
    newtype Test2 = P Float
            """,
            """
module test with
    x = 2
    x = 3
            """,
        ]

        parser = FunkyParser()
        parser.build()
        for test in sanity_fails:
            ast = parser.do_parse(test)
            with self.assertRaises(FunkyRenamingError):
                do_rename(ast)
Exemplo n.º 3
0
def compiler_lex_and_parse(source, dump_pretty, dump_lexed, dump_parsed):
    """Lexes and parses the source code to get the syntax tree.
    
    :param source str:       the Funky source code
    :param dump_pretty bool: if true, dump the syntax-highlighted, prettified
                             code to stdout
    :param dump_lexed bool:  if true, dump the lexed code to stdout
    :param dump_parsed bool: if true, dump the syntax tree to stdout
    """
    # lex and parse code
    parser = FunkyParser()
    parser.build(dump_pretty=dump_pretty, dump_lexed=dump_lexed)
    # lexing is done in the same step as parsing -- so we have to tell the
    # parser whether we want the lexer's output to be displayed
    parsed = parser.do_parse(source)
    if dump_parsed:
        print(cblue("## DUMPED PARSE TREE"))
        print(parsed)
        print("")

    log.info("Parsing source code completed.")
    return parsed
Exemplo n.º 4
0
class FunkyShell(CustomCmd):

    def __init__(self, lazy=False):
        start = time.time()
        super().__init__()

        self.intro   =  cgreen("\nfunkyi ({}) repl".format(__version__)) + \
                        "\nReady!\nFor help, use the ':help' command.\n"
        self.prompt  =  cyellow("funkyi> ")

        log.debug("Lazy mode {}.".format("enabled" if lazy else "disabled"))
        if lazy:
            print(cblue("Lazy evalation enabled."))
        else:
            print(cblue("Lazy evalation disabled."))

        # create the various parsers:
        log.debug("Creating required parsers...")
        self.decl_parser = FunkyParser()
        self.decl_parser.build(start="TOPLEVEL_DECLARATIONS")
        self.expr_parser = FunkyParser()
        self.expr_parser.build(start="EXP")
        self.newtype_parser = FunkyParser()
        self.newtype_parser.build(start="ADT_DECLARATION")
        self.setfix_parser = FunkyParser()
        self.setfix_parser.build(start="FIXITY_DECLARATION")
        self.import_parser = FunkyParser()
        self.import_parser.build(start="IMPORT_STATEMENT")

        if lazy:
            log.debug("Using lazy code generator for REPL.")
            self.py_generator = LazyPythonCodeGenerator()
        else:
            log.debug("Using strict code generator for REPL.")
            self.py_generator = StrictPythonCodeGenerator()

        log.debug("Done creating parsers.")

        self.reset()

        end = time.time()
        print(cblue("Startup completed ({0:.3f}s).".format(end - start)))

    @report_errors
    @atomic
    def do_begin_block(self, arg):
        """Start a block of definitions."""
        block_prompt = cyellow("block>  ")
        end_block = ":end_block" # <- type this to end the block
        lines = []
        try:
            while True:
                inp = input(block_prompt)
                if inp:
                    if inp == end_block:
                        break
                    lines.append(" " + inp)
        except KeyboardInterrupt:
            print("^C\n{}".format(cred("Cancelled block.")))
            return
        self.parse_and_add_declarations(lines)

    @report_errors
    @atomic
    @needs_argument
    def do_type(self, arg):
        """Show the type of an expression. E.g.: :type 5"""
        expr = self.get_core(arg)
        self.global_let.expr = expr
        do_type_inference(self.global_let, self.global_types)
        print("{} :: {}".format(arg, self.global_let.inferred_type))

    @needs_argument
    def do_lazy(self, arg):
        """Use ':lazy on' to use lazy evaluation and ':lazy off' to use strict evaluation."""
        SWAPPED = "Swapped to {} code generator.".format
        ALREADY = "Already using {} code generator; ignoring.".format
        if arg.lower() == "on":
            log.debug("Now using lazy code generator for REPL.")
            if isinstance(self.py_generator, StrictPythonCodeGenerator):
                self.py_generator = LazyPythonCodeGenerator()
                print(cgreen(SWAPPED("lazy")))
            else:
                print(ALREADY("lazy"))
        elif arg.lower() == "off":
            log.debug("Now using strict code generator for REPL.")
            if isinstance(self.py_generator, LazyPythonCodeGenerator):
                self.py_generator = StrictPythonCodeGenerator()
                print(cgreen(SWAPPED("strict")))
            else:
                print(ALREADY("strict"))
        else:
            print(cred("Invalid option '{}'. Please specify 'on' or "
                       "'off'.".format(arg)))

    @needs_argument
    def do_color(self, arg):
        """Use ':color on' to enable colors and ':color off' to disable them."""
        SWAPPED = "Turned colors {}.".format
        ALREADY = "Colors are already {}.".format
        
        if arg.lower() == "on":
            log.debug("Enabling colors.")
            if not funky.globals.USE_COLORS:
                funky.globals.USE_COLORS = True
                print(cgreen(SWAPPED("on")))
            else:
                print(ALREADY("on"))
        elif arg.lower() == "off":
            log.debug("Disabling colors.")
            if funky.globals.USE_COLORS:
                funky.globals.USE_COLORS = False
                print(cgreen(SWAPPED("off")))
            else:
                print(ALREADY("off"))
        else:
            print(cred("Invalid option '{}'. Please specify 'on' or "
                       "'off'.".format(arg)))

    @needs_argument
    def do_unicode(self, arg):
        """Use ':unicode on' to enable unicode characters and ':unicode off' to disable them."""
        SWAPPED = "Unicode printing is now {}.".format
        ALREADY = "Unicode printing is already {}.".format
        
        if arg.lower() == "on":
            log.debug("Enabling unicode printing.")
            if not funky.globals.USE_UNICODE:
                funky.globals.USE_UNICODE = True
                print(cgreen(SWAPPED("on")))
            else:
                print(ALREADY("on"))
        elif arg.lower() == "off":
            log.debug("Disabling unicode printing.")
            if funky.globals.USE_UNICODE:
                funky.globals.USE_UNICODE = False
                print(cgreen(SWAPPED("off")))
            else:
                print(ALREADY("off"))
        else:
            print(cred("Invalid option '{}'. Please specify 'on' or "
                       "'off'.".format(arg)))

    def do_list(self, arg):
        """List the current bindings in desuguared intermediate code."""
        if not (self.global_types or self.global_let.binds):
            print(cgreen("Nothing currently registered."))
            return

        print(cgreen("Currently registered bindings:"))
        if self.global_types:
            print("\n".join(str(b) for b in self.global_types))
        if self.global_let.binds:
            print("\n".join(str(b) for b in self.global_let.binds))

    def do_binds(self, arg):
        """List the available bindings."""
        if not self.scope.local:
            print("No bindings.")
            return

        print(cgreen("Available bindings:"))
        self.scope.pprint_local_binds()

    @report_errors
    @atomic
    @needs_argument
    def do_newtype(self, arg):
        """Create an ADT. E.g.: :newtype List = Cons Integer List | Nil"""
        parsed = self.newtype_parser.do_parse("newtype {}".format(arg))
        self.add_typedefs([parsed])

    @report_errors
    @atomic
    @needs_argument
    def do_show(self, arg):
        """Show the compiled code for an expression. E.g.: :show 1 + 1"""
        code = self.get_compiled(arg)
        print(code)

    @report_errors
    @needs_argument
    def do_setfix(self, arg):
        """Change the fixity of an operator. E.g.: :setfix leftassoc 8 **"""
        self.setfix_parser.do_parse("setfix {}".format(arg))

    @report_errors
    @atomic
    @needs_argument
    def do_import(self, arg):
        """Import a .fky file into the REPL. E.g.: :import "stdlib.fky"."""
        start = time.time()
        try:
            import_stmt = self.import_parser.do_parse("import {}".format(arg))

            cwd = os.path.abspath(os.getcwd())
            imports_source = get_imported_declarations(cwd, [import_stmt],
                                                       imported=self.imported)

            typedefs, code = split_typedefs_and_code(imports_source)

            self.add_typedefs(typedefs)
            self.add_declarations(code)

            end = time.time()
            print(cgreen("Successfully imported {0} ({1:.3f}s).".format(arg,
                                                                        end - start)))
        except FunkyError as e:
            # if an error occurs, extend the error message to tell the user
            # that the error occurred while importing *this* file.
            new_msg = "{} (while importing {})".format(e.args[0], arg)
            e.args = (new_msg,) + e.args[1:]
            raise

    @needs_argument
    def do_typeclass(self, arg):
        """Prints a quick summary of a typeclass."""
        try:
            typeclass = TYPECLASSES[arg]
            print(arg, typeclass.constraints_str())
        except KeyError:
            print(cred("Typeclass '{}' does not exist.".format(arg)))

    def do_reset(self, arg):
        """Reset the environment (clear the current list of bindings)."""
        self.reset()
        print(cgreen("All bindings reset."))

    def reset(self):
        """Resets the scope and bindings."""
        self.imported = set([])
        self.scope = Scope()

        # global_types is the collection of user-defined type declarations.
        self.global_types = []
        # global_let is a core let whose bindings are just the bindings the
        # user has introduced, and whose expression is 'dynamic' -- it is
        # changed each time the user asks for an expression to be evaluated and
        # recompiled as a new program to give the new result.
        self.global_let = CoreLet([], CoreLiteral(0))

    def get_state(self):
        return (
            copy.copy(self.imported),
            copy.deepcopy(self.scope),
            copy.deepcopy(self.global_types),
            copy.deepcopy(self.global_let)
        )

    def revert_state(self, state):
        self.imported, self.scope, self.global_types, self.global_let = state

    def get_core(self, source):
        """Converts a string of Funky code into the intermediate language.

        :param source: the source code to convert to the intermediate language
        :return:       the core code
        """
        parsed = self.expr_parser.do_parse(source)

        rename(parsed, self.scope)
        check_scope_for_errors(self.scope)
        core_tree, _ = do_desugar(parsed)
        return core_tree

    def get_compiled(self, source):
        """Converts a string of funky code into the target source language.

        :param source: the source code to convert to the target source language
        :return:       the compiled code in the target source language
        """
        core_expr = self.get_core(source)
        self.global_let.expr = core_expr
        do_type_inference(self.global_let, self.global_types)

        target_source = self.py_generator.do_generate_code(self.global_let,
                                                           self.global_types)
        return target_source

    def parse_and_add_declarations(self, lines):
        """Add a new block of declarations to global_let.

        :param lines: the Funky source code lines in the new block of
                      declarations
        """
        # shoehorn the lines into a 'fake' with clause so that they can be
        # parsed correctly.
        parsed = self.decl_parser.do_parse("func = 0 with\n{}".format(
                                        "\n".join(lines)))

        # extract the parsed declarations back out from our 'fake' with clause.
        declarations = parsed[0].expression.declarations

        self.add_declarations(declarations)

    def add_typedefs(self, typedefs):
        for typedef in typedefs:
            rename(typedef, self.scope)
            typedef = desugar(typedef)
            self.global_types.append(typedef)

    def add_declarations(self, declarations):
        self.global_let.expr = CoreLiteral(0)

        # rename and desugar each declaration one-by-one, and append each to
        # the (new_) global_let binds
        for decl in declarations:
            rename(decl, self.scope)
            core_tree, _ = do_desugar(decl)
            self.global_let.binds.append(core_tree)

        check_scope_for_errors(self.scope)
        self.global_let.binds = condense_function_binds(self.global_let.binds)
        self.global_let.create_dependency_graph()

        # type infer the new global let to check for inconsistencies
        do_type_inference(self.global_let, self.global_types)

    def do_EOF(self, line):
        """Exit safely."""
        print("^D\nEOF, exiting.")
        exit(0)

    @report_errors
    @atomic
    def default(self, arg):
        """This is called when the user does not type in any command in
        particular. Since this is a REPL, we should interpret the given
        text as an expression or a declaration
        """

        # if there are comments, drop them
        try:
            arg = arg[:arg.index("#")]
            if not arg:
                return
        except ValueError:
            pass

        try:
            # try treating as an expression...
            code = self.get_compiled(arg)
            print(cblue("= "), end="")
            try:
                exec(code, {"__name__" : "__main__"})
            except Exception as e:
                print(cred(str(e)))
        except FunkyParsingError:
            # if that didn't work, try treating as a declaration
            self.parse_and_add_declarations([arg])

    def emptyline(self):
        """Empty lines in the REPL do nothing."""
        pass
Exemplo n.º 5
0
    def test_valid_parse(self):
        valid_programs = ["""
module test with

    main = 10 + 10
        """,

        """
module test with

    factorial 0 = 1
    factorial n = n * factorial (n - 1)
    main = factorial 5""",
    
    """
module test with

    area r = pi * r ** 2.0
             with pi = 3.141
    """,
    
    """
# Treesort algorithm for integer in Funky.
module treesort with

    import "stdlib.fky"
    import "intlist.fky"

    newtype Tree = Branch Tree Integer Tree | Empty

    insert e Empty                          = Branch Empty e Empty
    insert e (Branch l v r) given e <= v    = Branch (insert e l) v r
                            given otherwise = Branch l v (insert e r)
    
    inorder Empty = Nil
    inorder (Branch l v r) = inorder l ~concatenate~
                             unit v    ~concatenate~
                             inorder r

    treesort list = inorder (foldr insert Empty list)

    main = treesort my_list
           with my_list = Cons 5 (Cons 1 (Cons 7 (Cons 7 (Cons 3 Nil))))
    """,
    
    """
# Representing and evaluating expression trees in Funky.
module expressiontree with

    newtype Expression = Const Float
                       | BinOp Expression
                               (Expression -> Expression -> Float)
                               Expression
                       | UnOp  (Expression -> Float)
                               Expression

    evaluate (Const x)     = x
    evaluate (BinOp a f b) = f a b
    evaluate (UnOp f x)    = f x

    add  exp1  exp2  =  (evaluate  exp1)  +  (evaluate  exp2)
    sub  exp1  exp2  =  (evaluate  exp1)  -  (evaluate  exp2)
    mul  exp1  exp2  =  (evaluate  exp1)  *  (evaluate  exp2)
    div  exp1  exp2  =  (evaluate  exp1)  /  (evaluate  exp2)

    test_exp = (BinOp (Const 5.0) add (BinOp (Const 3.0) div (Const 2.0)))

    main = evaluate test_exp
    """,
    
    """
# Demonstrating the use of Funky's random library to generate a random float.
module randomfloat with

    import "random.fky"

    seed = 51780

    main = randfloat seed
    """]

        parser = FunkyParser()
        parser.build()
        for valid_program in valid_programs:
            ast = parser.do_parse(valid_program)
            self.assertTrue(isinstance(ast, ASTNode))