Commit 10695f54 authored by asaka's avatar asaka

enable CPython's _ast module and use cpython's parser in builtin

function compile
parent 2526c94e
This diff is collapsed.
......@@ -77,7 +77,6 @@ add_library(PYSTON_OBJECTS OBJECT ${OPTIONAL_SRCS}
gc/gc_alloc.cpp
gc/heap.cpp
runtime/bool.cpp
runtime/builtin_modules/ast.cpp
runtime/builtin_modules/builtins.cpp
runtime/builtin_modules/gc.cpp
runtime/builtin_modules/pyston.cpp
......
......@@ -651,14 +651,27 @@ public:
return r;
}
AST_Module* convert(mod_ty mod) {
AST* convert(mod_ty mod) {
switch (mod->kind) {
case Module_kind:
case Module_kind: {
AST_Module* rtn = new AST_Module(llvm::make_unique<InternedStringPool>());
assert(!this->pool);
this->pool = rtn->interned_strings.get();
convertAll<stmt_ty>(mod->v.Module.body, rtn->body);
return rtn;
}
case Interactive_kind: {
AST_Module* rtn = new AST_Module(llvm::make_unique<InternedStringPool>());
assert(!this->pool);
this->pool = rtn->interned_strings.get();
convertAll<stmt_ty>(mod->v.Interactive.body, rtn->body);
makeModuleInteractive(rtn);
return rtn;
}
case Expression_kind: {
AST_Expression* rtn = new AST_Expression(llvm::make_unique<InternedStringPool>());
this->pool = rtn->interned_strings.get();
rtn->body = this->convert(mod->v.Expression.body);
return rtn;
}
default:
......@@ -667,7 +680,7 @@ public:
}
};
AST_Module* cpythonToPystonAST(mod_ty mod, llvm::StringRef fn) {
AST* cpythonToPystonAST(mod_ty mod, llvm::StringRef fn) {
Converter c(fn);
return c.convert(mod);
}
......
......@@ -25,7 +25,7 @@ namespace pyston {
// Convert a CPython ast object to a Pyston ast object.
// This will also check for certain kinds of "syntax errors" (ex continue not in loop) and will
// throw them as C++ exceptions.
AST_Module* cpythonToPystonAST(mod_ty mod, llvm::StringRef fn);
AST* cpythonToPystonAST(mod_ty mod, llvm::StringRef fn);
}
#endif
......@@ -488,26 +488,57 @@ Box* compile(Box* source, Box* fn, Box* type, Box** _args) {
RELEASE_ASSERT(iflags == 0, "");
AST* parsed;
mod_ty mod;
if (PyAST_Check(source)) {
parsed = unboxAst(source);
int mode;
ArenaWrapper arena;
if (type_str->s() == "exec")
mode = 0;
else if (type_str->s() == "eval")
mode = 1;
else if (type_str->s() == "single")
mode = 2;
else {
raiseExcHelper(ValueError, "compile() arg 3 must be 'exec', 'eval' or 'single'");
}
mod = PyAST_obj2mod(source, arena, mode);
if (PyErr_Occurred())
throwCAPIException();
parsed = cpythonToPystonAST(mod, filename_str->c_str());
} else {
RELEASE_ASSERT(PyString_Check(source), "");
llvm::StringRef source_str = static_cast<BoxedString*>(source)->s();
if (type_str->s() == "exec") {
parsed = parseExec(source_str, future_flags);
} else if (type_str->s() == "eval") {
parsed = parseEval(source_str, future_flags);
} else if (type_str->s() == "single") {
parsed = parseExec(source_str, future_flags, true);
} else {
int mode;
if (type_str->s() == "exec")
mode = Py_file_input;
else if (type_str->s() == "eval")
mode = Py_eval_input;
else if (type_str->s() == "single")
mode = Py_single_input;
else {
raiseExcHelper(ValueError, "compile() arg 3 must be 'exec', 'eval' or 'single'");
}
PyCompilerFlags cf;
cf.cf_flags = future_flags;
ArenaWrapper arena;
const char* code = static_cast<BoxedString*>(source)->s().data();
assert(arena);
const char* fn = filename_str->c_str();
mod = PyParser_ASTFromString(code, fn, mode, &cf, arena);
if (!mod)
throwCAPIException();
parsed = cpythonToPystonAST(mod, filename_str->c_str());
}
if (only_ast)
return boxAst(parsed);
if (only_ast) {
Box* result = PyAST_mod2obj(mod);
if (PyErr_Occurred())
throwCAPIException();
return result;
}
PyCompilerFlags pcf;
pcf.cf_flags = future_flags;
......@@ -515,12 +546,14 @@ Box* compile(Box* source, Box* fn, Box* type, Box** _args) {
FunctionMetadata* md;
if (type_str->s() == "exec" || type_str->s() == "single") {
// TODO: CPython parses execs as Modules
if (parsed->type != AST_TYPE::Module)
raiseExcHelper(TypeError, "expected Module node, got %s", boxAst(parsed)->cls->tp_name);
if (parsed->type != AST_TYPE::Module) {
raiseExcHelper(TypeError, "expected Module node, got %s", AST_TYPE::stringify(parsed->type));
}
md = compileExec(static_cast<AST_Module*>(parsed), filename_str, &pcf);
} else if (type_str->s() == "eval") {
if (parsed->type != AST_TYPE::Expression)
raiseExcHelper(TypeError, "expected Expression node, got %s", boxAst(parsed)->cls->tp_name);
if (parsed->type != AST_TYPE::Expression) {
raiseExcHelper(TypeError, "expected Expression node, got %s", AST_TYPE::stringify(parsed->type));
}
md = compileEval(static_cast<AST_Expression*>(parsed), filename_str, &pcf);
} else {
raiseExcHelper(ValueError, "compile() arg 3 must be 'exec', 'eval' or 'single'");
......
......@@ -1027,7 +1027,8 @@ AST_Module* parse_string(const char* code, FutureFlags inherited_flags) {
mod_ty mod = PyParser_ASTFromString(code, fn, Py_file_input, &cf, arena);
if (!mod)
throwCAPIException();
auto rtn = cpythonToPystonAST(mod, fn);
assert(mod->kind != Interactive_kind);
auto rtn = static_cast<AST_Module*>(cpythonToPystonAST(mod, fn));
return rtn;
}
......@@ -1072,7 +1073,8 @@ AST_Module* parse_file(const char* fn, FutureFlags inherited_flags) {
mod_ty mod = PyParser_ASTFromFile(fp, fn, Py_file_input, 0, 0, &cf, NULL, arena);
if (!mod)
throwCAPIException();
auto rtn = cpythonToPystonAST(mod, fn);
assert(mod->kind != Interactive_kind);
auto rtn = static_cast<AST_Module*>(cpythonToPystonAST(mod, fn));
return rtn;
}
......@@ -1158,7 +1160,8 @@ static std::vector<char> _reparse(const char* fn, const std::string& cache_fn, A
mod_ty mod = PyParser_ASTFromFile(fp, fn, Py_file_input, 0, 0, &cf, NULL, arena);
if (!mod)
throwCAPIException();
module = cpythonToPystonAST(mod, fn);
assert(mod->kind != Interactive_kind);
module = static_cast<AST_Module*>(cpythonToPystonAST(mod, fn));
} else {
module = pypa_parse(fn, inherited_flags);
RELEASE_ASSERT(module, "unknown parse error");
......
......@@ -17,6 +17,7 @@
#include <cassert>
#include <cstdlib>
#include <map>
#include <stdint.h>
#include <string>
#include <vector>
......@@ -33,112 +34,132 @@ namespace pyston {
namespace AST_TYPE {
// These are in a pretty random order (started off alphabetical but then I had to add more).
// These can be changed freely as long as parse_ast.py is also updated
#define FOREACH_TYPE(X) \
X(alias, 1) \
X(arguments, 2) \
X(Assert, 3) \
X(Assign, 4) \
X(Attribute, 5) \
X(AugAssign, 6) \
X(BinOp, 7) \
X(BoolOp, 8) \
X(Call, 9) \
X(ClassDef, 10) \
X(Compare, 11) \
X(comprehension, 12) \
X(Delete, 13) \
X(Dict, 14) \
X(Exec, 16) \
X(ExceptHandler, 17) \
X(ExtSlice, 18) \
X(Expr, 19) \
X(For, 20) \
X(FunctionDef, 21) \
X(GeneratorExp, 22) \
X(Global, 23) \
X(If, 24) \
X(IfExp, 25) \
X(Import, 26) \
X(ImportFrom, 27) \
X(Index, 28) \
X(keyword, 29) \
X(Lambda, 30) \
X(List, 31) \
X(ListComp, 32) \
X(Module, 33) \
X(Num, 34) \
X(Name, 35) \
X(Pass, 37) \
X(Pow, 38) \
X(Print, 39) \
X(Raise, 40) \
X(Repr, 41) \
X(Return, 42) \
X(Slice, 44) \
X(Str, 45) \
X(Subscript, 46) \
X(TryExcept, 47) \
X(TryFinally, 48) \
X(Tuple, 49) \
X(UnaryOp, 50) \
X(With, 51) \
X(While, 52) \
X(Yield, 53) \
X(Store, 54) \
X(Load, 55) \
X(Param, 56) \
X(Not, 57) \
X(In, 58) \
X(Is, 59) \
X(IsNot, 60) \
X(Or, 61) \
X(And, 62) \
X(Eq, 63) \
X(NotEq, 64) \
X(NotIn, 65) \
X(GtE, 66) \
X(Gt, 67) \
X(Mod, 68) \
X(Add, 69) \
X(Continue, 70) \
X(Lt, 71) \
X(LtE, 72) \
X(Break, 73) \
X(Sub, 74) \
X(Del, 75) \
X(Mult, 76) \
X(Div, 77) \
X(USub, 78) \
X(BitAnd, 79) \
X(BitOr, 80) \
X(BitXor, 81) \
X(RShift, 82) \
X(LShift, 83) \
X(Invert, 84) \
X(UAdd, 85) \
X(FloorDiv, 86) \
X(DictComp, 15) \
X(Set, 43) \
X(Ellipsis, 87) \
/* like Module, but used for eval. */ \
X(Expression, 88) \
X(SetComp, 89) \
X(Suite, 90) \
\
/* Pseudo-nodes that are specific to this compiler: */ \
X(Branch, 200) \
X(Jump, 201) \
X(ClsAttribute, 202) \
X(AugBinOp, 203) \
X(Invoke, 204) \
X(LangPrimitive, 205) \
/* wraps a ClassDef to make it an expr */ \
X(MakeClass, 206) \
/* wraps a FunctionDef to make it an expr */ \
X(MakeFunction, 207) \
\
/* These aren't real AST types, but since we use AST types to represent binexp types */ \
/* and divmod+truediv are essentially types of binops, we add them here (at least for now): */ \
X(DivMod, 250) \
X(TrueDiv, 251) \
#define GENERATE_ENUM(ENUM, N) ENUM = N,
#define GENERATE_STRING(STRING, N) m[N] = #STRING;
enum AST_TYPE {
alias = 1,
arguments = 2,
Assert = 3,
Assign = 4,
Attribute = 5,
AugAssign = 6,
BinOp = 7,
BoolOp = 8,
Call = 9,
ClassDef = 10,
Compare = 11,
comprehension = 12,
Delete = 13,
Dict = 14,
Exec = 16,
ExceptHandler = 17,
ExtSlice = 18,
Expr = 19,
For = 20,
FunctionDef = 21,
GeneratorExp = 22,
Global = 23,
If = 24,
IfExp = 25,
Import = 26,
ImportFrom = 27,
Index = 28,
keyword = 29,
Lambda = 30,
List = 31,
ListComp = 32,
Module = 33,
Num = 34,
Name = 35,
Pass = 37,
Pow = 38,
Print = 39,
Raise = 40,
Repr = 41,
Return = 42,
Slice = 44,
Str = 45,
Subscript = 46,
TryExcept = 47,
TryFinally = 48,
Tuple = 49,
UnaryOp = 50,
With = 51,
While = 52,
Yield = 53,
Store = 54,
Load = 55,
Param = 56,
Not = 57,
In = 58,
Is = 59,
IsNot = 60,
Or = 61,
And = 62,
Eq = 63,
NotEq = 64,
NotIn = 65,
GtE = 66,
Gt = 67,
Mod = 68,
Add = 69,
Continue = 70,
Lt = 71,
LtE = 72,
Break = 73,
Sub = 74,
Del = 75,
Mult = 76,
Div = 77,
USub = 78,
BitAnd = 79,
BitOr = 80,
BitXor = 81,
RShift = 82,
LShift = 83,
Invert = 84,
UAdd = 85,
FloorDiv = 86,
DictComp = 15,
Set = 43,
Ellipsis = 87,
Expression = 88, // like Module, but used for eval.
SetComp = 89,
Suite = 90,
// Pseudo-nodes that are specific to this compiler:
Branch = 200,
Jump = 201,
ClsAttribute = 202,
AugBinOp = 203,
Invoke = 204,
LangPrimitive = 205,
MakeClass = 206, // wraps a ClassDef to make it an expr
MakeFunction = 207, // wraps a FunctionDef to make it an expr
// These aren't real AST types, but since we use AST types to represent binexp types
// and divmod+truediv are essentially types of binops, we add them here (at least for now):
DivMod = 250,
TrueDiv = 251,
FOREACH_TYPE(GENERATE_ENUM)
};
static const char *stringify(int n) {
static std::map<int, const char*> m;
FOREACH_TYPE(GENERATE_STRING)
return m[n];
}
#undef FOREACH_TYPE
#undef GENERATE_ENUM
#undef GENERATE_STRING
};
class ASTVisitor;
......
......@@ -256,7 +256,7 @@ private:
curblock = NULL;
}
void doContinue() {
void doContinue(AST* value) {
assert(curblock);
for (auto& cont : llvm::make_range(continuations.rbegin(), continuations.rend())) {
if (cont.continue_dest) {
......@@ -270,10 +270,10 @@ private:
}
}
raiseExcHelper(SyntaxError, "'continue' not properly in loop");
raiseSyntaxError("'continue' not properly in loop", value->lineno, value->col_offset, source->getFn()->s(), "", true);
}
void doBreak() {
void doBreak(AST* value) {
assert(curblock);
for (auto& cont : llvm::make_range(continuations.rbegin(), continuations.rend())) {
if (cont.break_dest) {
......@@ -287,7 +287,7 @@ private:
}
}
raiseExcHelper(SyntaxError, "'break' outside loop");
raiseSyntaxError("'break' outside loop", value->lineno, value->col_offset, source->getFn()->s(), "", true);
}
AST_expr* callNonzero(AST_expr* e) {
......@@ -1322,10 +1322,10 @@ private:
doReturn(makeLoad(internString(RETURN_NAME), node));
break;
case Why::BREAK:
doBreak();
doBreak(node);
break;
case Why::CONTINUE:
doContinue();
doContinue(node);
break;
case Why::FALLTHROUGH:
assert(exit_block);
......@@ -1973,7 +1973,7 @@ public:
bool visit_break(AST_Break* node) override {
assert(curblock);
doBreak();
doBreak(node);
assert(!curblock);
return true;
}
......@@ -1981,7 +1981,7 @@ public:
bool visit_continue(AST_Continue* node) override {
assert(curblock);
doContinue();
doContinue(node);
assert(!curblock);
return true;
}
......
// Copyright (c) 2014-2015 Dropbox, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <algorithm>
#include <cmath>
#include <langinfo.h>
#include <sstream>
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "codegen/unwinding.h"
#include "core/ast.h"
#include "core/types.h"
#include "runtime/file.h"
#include "runtime/inline/boxing.h"
#include "runtime/int.h"
#include "runtime/types.h"
#include "runtime/util.h"
namespace pyston {
static BoxedClass* AST_cls;
class BoxedAST : public Box {
public:
AST* ast;
BoxedAST() {}
};
static std::unordered_map<int, BoxedClass*> type_to_cls;
Box* boxAst(AST* ast) {
assert(ast);
BoxedClass* cls = type_to_cls[ast->type];
assert(cls);
BoxedAST* rtn = new (cls) BoxedAST();
assert(rtn->cls == cls);
rtn->ast = ast;
return rtn;
}
AST* unboxAst(Box* b) {
assert(isSubclass(b->cls, AST_cls));
AST* rtn = static_cast<BoxedAST*>(b)->ast;
assert(rtn);
return rtn;
}
extern "C" int PyAST_Check(PyObject* o) noexcept {
return isSubclass(o->cls, AST_cls);
}
void setupAST() {
BoxedModule* ast_module = createModule(boxString("_ast"), "__builtin__");
ast_module->giveAttr("PyCF_ONLY_AST", boxInt(PyCF_ONLY_AST));
// ::create takes care of registering the class as a GC root.
#define MAKE_CLS(name, base_cls) \
BoxedClass* name##_cls = BoxedClass::create(type_cls, base_cls, /* gchandler = */ NULL, 0, 0, sizeof(BoxedAST), \
false, STRINGIFY(name)); \
ast_module->giveAttr(STRINGIFY(name), name##_cls); \
type_to_cls[AST_TYPE::name] = name##_cls; \
name##_cls->giveAttr("__module__", boxString("_ast")); \
name##_cls->freeze()
AST_cls = BoxedClass::create(type_cls, object_cls, /* gchandler = */ NULL, 0, 0, sizeof(BoxedAST), false, "AST");
// ::create takes care of registering the class as a GC root.
AST_cls->giveAttr("__module__", boxString("_ast"));
AST_cls->freeze();
// TODO(kmod) you can call the class constructors, such as "ast.AST()", so we need new/init
// TODO(kmod) there is more inheritance than "they all inherit from AST"
MAKE_CLS(alias, AST_cls);
MAKE_CLS(arguments, AST_cls);
MAKE_CLS(Assert, AST_cls);
MAKE_CLS(Assign, AST_cls);
MAKE_CLS(Attribute, AST_cls);
MAKE_CLS(AugAssign, AST_cls);
MAKE_CLS(BinOp, AST_cls);
MAKE_CLS(BoolOp, AST_cls);
MAKE_CLS(Call, AST_cls);
MAKE_CLS(ClassDef, AST_cls);
MAKE_CLS(Compare, AST_cls);
MAKE_CLS(comprehension, AST_cls);
MAKE_CLS(Delete, AST_cls);
MAKE_CLS(Dict, AST_cls);
MAKE_CLS(Exec, AST_cls);
MAKE_CLS(ExceptHandler, AST_cls);
MAKE_CLS(ExtSlice, AST_cls);
MAKE_CLS(Expr, AST_cls);
MAKE_CLS(For, AST_cls);
MAKE_CLS(FunctionDef, AST_cls);
MAKE_CLS(GeneratorExp, AST_cls);
MAKE_CLS(Global, AST_cls);
MAKE_CLS(If, AST_cls);
MAKE_CLS(IfExp, AST_cls);
MAKE_CLS(Import, AST_cls);
MAKE_CLS(ImportFrom, AST_cls);
MAKE_CLS(Index, AST_cls);
MAKE_CLS(keyword, AST_cls);
MAKE_CLS(Lambda, AST_cls);
MAKE_CLS(List, AST_cls);
MAKE_CLS(ListComp, AST_cls);
MAKE_CLS(Module, AST_cls);
MAKE_CLS(Num, AST_cls);
MAKE_CLS(Name, AST_cls);
MAKE_CLS(Pass, AST_cls);
MAKE_CLS(Pow, AST_cls);
MAKE_CLS(Print, AST_cls);
MAKE_CLS(Raise, AST_cls);
MAKE_CLS(Repr, AST_cls);
MAKE_CLS(Return, AST_cls);
MAKE_CLS(Slice, AST_cls);
MAKE_CLS(Str, AST_cls);
MAKE_CLS(Subscript, AST_cls);
MAKE_CLS(TryExcept, AST_cls);
MAKE_CLS(TryFinally, AST_cls);
MAKE_CLS(Tuple, AST_cls);
MAKE_CLS(UnaryOp, AST_cls);
MAKE_CLS(With, AST_cls);
MAKE_CLS(While, AST_cls);
MAKE_CLS(Yield, AST_cls);
MAKE_CLS(Store, AST_cls);
MAKE_CLS(Load, AST_cls);
MAKE_CLS(Param, AST_cls);
MAKE_CLS(Not, AST_cls);
MAKE_CLS(In, AST_cls);
MAKE_CLS(Is, AST_cls);
MAKE_CLS(IsNot, AST_cls);
MAKE_CLS(Or, AST_cls);
MAKE_CLS(And, AST_cls);
MAKE_CLS(Eq, AST_cls);
MAKE_CLS(NotEq, AST_cls);
MAKE_CLS(NotIn, AST_cls);
MAKE_CLS(GtE, AST_cls);
MAKE_CLS(Gt, AST_cls);
MAKE_CLS(Mod, AST_cls);
MAKE_CLS(Add, AST_cls);
MAKE_CLS(Continue, AST_cls);
MAKE_CLS(Lt, AST_cls);
MAKE_CLS(LtE, AST_cls);
MAKE_CLS(Break, AST_cls);
MAKE_CLS(Sub, AST_cls);
MAKE_CLS(Del, AST_cls);
MAKE_CLS(Mult, AST_cls);
MAKE_CLS(Div, AST_cls);
MAKE_CLS(USub, AST_cls);
MAKE_CLS(BitAnd, AST_cls);
MAKE_CLS(BitOr, AST_cls);
MAKE_CLS(BitXor, AST_cls);
MAKE_CLS(RShift, AST_cls);
MAKE_CLS(LShift, AST_cls);
MAKE_CLS(Invert, AST_cls);
MAKE_CLS(UAdd, AST_cls);
MAKE_CLS(FloorDiv, AST_cls);
MAKE_CLS(DictComp, AST_cls);
MAKE_CLS(Set, AST_cls);
MAKE_CLS(Ellipsis, AST_cls);
MAKE_CLS(Expression, AST_cls);
MAKE_CLS(SetComp, AST_cls);
MAKE_CLS(Suite, AST_cls);
#undef MAKE_CLS
// Uncommenting this makes `import ast` work, which may or may not be desired.
// For now it seems like making the import fail is better than having the module not work properly.
// ast_module->giveAttr("__version__", boxInt(82160));
}
}
......@@ -1231,8 +1231,8 @@ extern "C" int PyRun_InteractiveOneFlags(FILE* fp, const char* filename, PyCompi
assert(PyModule_Check(m));
bool failed = false;
try {
AST_Module* pyston_module = cpythonToPystonAST(mod, filename);
makeModuleInteractive(pyston_module);
assert(mod->kind == Interactive_kind);
AST_Module* pyston_module = static_cast<AST_Module*>(cpythonToPystonAST(mod, filename));
compileAndRunModule(pyston_module, static_cast<BoxedModule*>(m));
} catch (ExcInfo e) {
setCAPIException(e);
......
......@@ -89,6 +89,7 @@ extern "C" void init_ssl();
extern "C" void init_sqlite3();
extern "C" void PyMarshal_Init();
extern "C" void initstrop();
extern "C" void init_ast();
namespace pyston {
......@@ -4087,7 +4088,6 @@ void setupRuntime() {
setupGC();
setupImport();
setupPyston();
setupAST();
PyType_Ready(&PyByteArrayIter_Type);
PyType_Ready(&PyCapsule_Type);
......@@ -4133,6 +4133,7 @@ void setupRuntime() {
init_sqlite3();
PyMarshal_Init();
initstrop();
init_ast();
setupDefaultClassGCParticipation();
......
from __future__ import print_function
import _ast
# test compile string:
a = compile("'hello world'", "test.py", "single", _ast.PyCF_ONLY_AST)
exec compile(a, "test.py", "single")
compile('1 + 1', 'test.py', 'eval', dont_inherit=True)
# test compile ast:
def hello():
print('hello again')
tree = compile('hello()', 'test.py', 'eval', _ast.PyCF_ONLY_AST)
exec compile(tree, '<tree>', 'eval', dont_inherit=True)
# test future flags:
exec compile('print(1, 2)', 'test.py', 'exec')
tree = compile('print(1, 2)', 'test.py', 'exec', _ast.PyCF_ONLY_AST)
exec compile(tree, '<tree>', 'exec')
# test bad syntax which should not raise in compile time:
try:
exec compile('break', '?', 'exec')
assert False
except SyntaxError:
pass
try:
exec compile('continue', '?', 'exec')
assert False
except SyntaxError:
pass
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment