py: Implement compile builtin, enabled only on unix port.

This should be pretty compliant with CPython, except perhaps for some
corner cases to do with globals/locals context.

Addresses issue #879.
diff --git a/py/builtin.h b/py/builtin.h
index 6b44a16..762e10d 100644
--- a/py/builtin.h
+++ b/py/builtin.h
@@ -35,6 +35,7 @@
 MP_DECLARE_CONST_FUN_OBJ(mp_builtin_any_obj);
 MP_DECLARE_CONST_FUN_OBJ(mp_builtin_bin_obj);
 MP_DECLARE_CONST_FUN_OBJ(mp_builtin_callable_obj);
+MP_DECLARE_CONST_FUN_OBJ(mp_builtin_compile_obj);
 MP_DECLARE_CONST_FUN_OBJ(mp_builtin_chr_obj);
 MP_DECLARE_CONST_FUN_OBJ(mp_builtin_dir_obj);
 MP_DECLARE_CONST_FUN_OBJ(mp_builtin_divmod_obj);
diff --git a/py/builtinevex.c b/py/builtinevex.c
index e240553..73f254c 100644
--- a/py/builtinevex.c
+++ b/py/builtinevex.c
@@ -34,19 +34,87 @@
 #include "lexerunix.h"
 #include "parse.h"
 #include "obj.h"
+#include "objfun.h"
 #include "parsehelper.h"
 #include "compile.h"
 #include "runtime0.h"
 #include "runtime.h"
 #include "builtin.h"
 
-STATIC mp_obj_t eval_exec_helper(mp_uint_t n_args, const mp_obj_t *args, mp_parse_input_kind_t parse_input_kind) {
+#if MICROPY_PY_BUILTINS_COMPILE
+
+typedef struct _mp_obj_code_t {
+    mp_obj_base_t base;
+    mp_obj_t module_fun;
+} mp_obj_code_t;
+
+STATIC const mp_obj_type_t mp_type_code = {
+    { &mp_type_type },
+    .name = MP_QSTR_code,
+};
+
+STATIC mp_obj_t code_execute(mp_obj_code_t *self, mp_obj_t globals, mp_obj_t locals) {
+    // save context and set new context
+    mp_obj_dict_t *old_globals = mp_globals_get();
+    mp_obj_dict_t *old_locals = mp_locals_get();
+    mp_globals_set(globals);
+    mp_locals_set(locals);
+
+    // a bit of a hack: fun_bc will re-set globals, so need to make sure it's
+    // the correct one
+    if (MP_OBJ_IS_TYPE(self->module_fun, &mp_type_fun_bc)) {
+        mp_obj_fun_bc_t *fun_bc = self->module_fun;
+        fun_bc->globals = globals;
+    }
+
+    // execute code
+    nlr_buf_t nlr;
+    if (nlr_push(&nlr) == 0) {
+        mp_obj_t ret = mp_call_function_0(self->module_fun);
+        nlr_pop();
+        mp_globals_set(old_globals);
+        mp_locals_set(old_locals);
+        return ret;
+    } else {
+        // exception; restore context and re-raise same exception
+        mp_globals_set(old_globals);
+        mp_locals_set(old_locals);
+        nlr_raise(nlr.ret_val);
+    }
+}
+
+STATIC mp_obj_t mp_builtin_compile(mp_uint_t n_args, const mp_obj_t *args) {
+    // get the source
     mp_uint_t str_len;
     const char *str = mp_obj_str_get_data(args[0], &str_len);
 
-    // create the lexer
-    mp_lexer_t *lex = mp_lexer_new_from_str_len(MP_QSTR__lt_string_gt_, str, str_len, 0);
+    // get the filename
+    qstr filename = mp_obj_str_get_qstr(args[1]);
 
+    // create the lexer
+    mp_lexer_t *lex = mp_lexer_new_from_str_len(filename, str, str_len, 0);
+
+    // get the compile mode
+    qstr mode = mp_obj_str_get_qstr(args[2]);
+    mp_parse_input_kind_t parse_input_kind;
+    switch (mode) {
+        case MP_QSTR_single: parse_input_kind = MP_PARSE_SINGLE_INPUT; break;
+        case MP_QSTR_exec: parse_input_kind = MP_PARSE_FILE_INPUT; break;
+        case MP_QSTR_eval: parse_input_kind = MP_PARSE_EVAL_INPUT; break;
+        default:
+            nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "bad compile mode"));
+    }
+
+    mp_obj_code_t *code = m_new_obj(mp_obj_code_t);
+    code->base.type = &mp_type_code;
+    code->module_fun = mp_parse_compile_execute(lex, parse_input_kind, NULL, NULL);
+    return code;
+}
+MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_compile_obj, 3, 6, mp_builtin_compile);
+
+#endif // MICROPY_PY_BUILTINS_COMPILE
+
+STATIC mp_obj_t eval_exec_helper(mp_uint_t n_args, const mp_obj_t *args, mp_parse_input_kind_t parse_input_kind) {
     // work out the context
     mp_obj_dict_t *globals = mp_globals_get();
     mp_obj_dict_t *locals = mp_locals_get();
@@ -59,6 +127,18 @@
         }
     }
 
+    #if MICROPY_PY_BUILTINS_COMPILE
+    if (MP_OBJ_IS_TYPE(args[0], &mp_type_code)) {
+        return code_execute(args[0], globals, locals);
+    }
+    #endif
+
+    mp_uint_t str_len;
+    const char *str = mp_obj_str_get_data(args[0], &str_len);
+
+    // create the lexer
+    mp_lexer_t *lex = mp_lexer_new_from_str_len(MP_QSTR__lt_string_gt_, str, str_len, 0);
+
     return mp_parse_compile_execute(lex, parse_input_kind, globals, locals);
 }
 
diff --git a/py/builtintables.c b/py/builtintables.c
index c8dea6b..2a5ced5 100644
--- a/py/builtintables.c
+++ b/py/builtintables.c
@@ -91,6 +91,9 @@
     { MP_OBJ_NEW_QSTR(MP_QSTR_any), (mp_obj_t)&mp_builtin_any_obj },
     { MP_OBJ_NEW_QSTR(MP_QSTR_bin), (mp_obj_t)&mp_builtin_bin_obj },
     { MP_OBJ_NEW_QSTR(MP_QSTR_callable), (mp_obj_t)&mp_builtin_callable_obj },
+#if MICROPY_PY_BUILTINS_COMPILE
+    { MP_OBJ_NEW_QSTR(MP_QSTR_compile), (mp_obj_t)&mp_builtin_compile_obj },
+#endif
     { MP_OBJ_NEW_QSTR(MP_QSTR_chr), (mp_obj_t)&mp_builtin_chr_obj },
     { MP_OBJ_NEW_QSTR(MP_QSTR_dir), (mp_obj_t)&mp_builtin_dir_obj },
     { MP_OBJ_NEW_QSTR(MP_QSTR_divmod), (mp_obj_t)&mp_builtin_divmod_obj },
diff --git a/py/mpconfig.h b/py/mpconfig.h
index d24e6b2..b9e87fc 100644
--- a/py/mpconfig.h
+++ b/py/mpconfig.h
@@ -312,6 +312,11 @@
 #define MICROPY_PY_BUILTINS_PROPERTY (1)
 #endif
 
+// Whether to support compile function
+#ifndef MICROPY_PY_BUILTINS_COMPILE
+#define MICROPY_PY_BUILTINS_COMPILE (0)
+#endif
+
 // Whether to set __file__ for imported modules
 #ifndef MICROPY_PY___FILE__
 #define MICROPY_PY___FILE__ (1)
diff --git a/py/qstrdefs.h b/py/qstrdefs.h
index e8a493d..c87a7a8 100644
--- a/py/qstrdefs.h
+++ b/py/qstrdefs.h
@@ -213,6 +213,12 @@
 Q(write)
 Q(zip)
 
+#if MICROPY_PY_BUILTINS_COMPILE
+Q(compile)
+Q(code)
+Q(single)
+#endif
+
 Q(sep)
 Q(end)
 
diff --git a/py/runtime.c b/py/runtime.c
index f701e0a..6cb5c7e 100644
--- a/py/runtime.c
+++ b/py/runtime.c
@@ -1189,6 +1189,13 @@
         nlr_raise(module_fun);
     }
 
+    // for compile only
+    if (MICROPY_PY_BUILTINS_COMPILE && globals == NULL) {
+        mp_globals_set(old_globals);
+        mp_locals_set(old_locals);
+        return module_fun;
+    }
+
     // complied successfully, execute it
     nlr_buf_t nlr;
     if (nlr_push(&nlr) == 0) {
diff --git a/unix/mpconfigport.h b/unix/mpconfigport.h
index a1e03be..7844cc9 100644
--- a/unix/mpconfigport.h
+++ b/unix/mpconfigport.h
@@ -47,6 +47,7 @@
 #define MICROPY_PY_BUILTINS_STR_UNICODE (1)
 #define MICROPY_PY_BUILTINS_MEMORYVIEW (1)
 #define MICROPY_PY_BUILTINS_FROZENSET (1)
+#define MICROPY_PY_BUILTINS_COMPILE (1)
 #define MICROPY_PY_SYS_EXIT         (1)
 #define MICROPY_PY_SYS_PLATFORM     "linux"
 #define MICROPY_PY_SYS_MAXSIZE      (1)