diff --git a/include/pocketpy/pocketpy.h b/include/pocketpy/pocketpy.h index 908fdf84..e93d107f 100644 --- a/include/pocketpy/pocketpy.h +++ b/include/pocketpy/pocketpy.h @@ -38,13 +38,7 @@ enum BindType { BindType_CLASSMETHOD, }; -enum CompileMode { - EXEC_MODE, - EVAL_MODE, - REPL_MODE, - JSON_MODE, - CELL_MODE -}; +enum CompileMode { EXEC_MODE, EVAL_MODE, REPL_MODE, JSON_MODE, CELL_MODE }; /************* Global VMs *************/ void py_initialize(); @@ -294,7 +288,9 @@ bool py_callmethod(py_Ref self, py_Name, int argc, py_Ref argv); bool py_callmagic(py_Name name, int argc, py_Ref argv); bool py_str(py_Ref val); -bool py_repr(py_Ref val); + +#define py_repr(val) py_callmagic(__repr__, 1, val) +#define py_len(val) py_callmagic(__len__, 1, val) /// The return value of the most recent call. py_GlobalRef py_retval(); diff --git a/src/compiler/compiler.c b/src/compiler/compiler.c index e1832b1b..d8d33d99 100644 --- a/src/compiler/compiler.c +++ b/src/compiler/compiler.c @@ -4,6 +4,7 @@ #include "pocketpy/objects/sourcedata.h" #include "pocketpy/objects/object.h" #include "pocketpy/common/strname.h" +#include "pocketpy/common/sstream.h" #include "pocketpy/common/config.h" #include "pocketpy/common/memorypool.h" #include @@ -2348,6 +2349,124 @@ static Error* compile_function(Compiler* self, int decorators) { return NULL; } +static Error* compile_decorated(Compiler* self) { + Error* err; + int count = 0; + do { + check(EXPR(self)); + count += 1; + if(!match_newlines()) return SyntaxError("expected a newline after '@'"); + } while(match(TK_DECORATOR)); + + if(match(TK_CLASS)) { + // check(compile_class(count)); + } else { + consume(TK_DEF); + check(compile_function(self, count)); + } + return NULL; +} + +// import a [as b] +// import a [as b], c [as d] +static Error* compile_normal_import(Compiler* self) { + do { + consume(TK_ID); + c11_sv name = Token__sv(prev()); + int index = Ctx__add_const_string(ctx(), name); + Ctx__emit_(ctx(), OP_IMPORT_PATH, index, prev()->line); + if(match(TK_AS)) { + consume(TK_ID); + name = Token__sv(prev()); + } + Ctx__emit_store_name(ctx(), name_scope(self), py_name2(name), prev()->line); + } while(match(TK_COMMA)); + consume_end_stmt(); + return NULL; +} + +// from a import b [as c], d [as e] +// from a.b import c [as d] +// from . import a [as b] +// from .a import b [as c] +// from ..a import b [as c] +// from .a.b import c [as d] +// from xxx import * +static Error* compile_from_import(c11_sbuf* buf, Compiler* self) { + int dots = 0; + + while(true) { + switch(curr()->type) { + case TK_DOT: dots += 1; break; + case TK_DOTDOT: dots += 2; break; + case TK_DOTDOTDOT: dots += 3; break; + default: goto __EAT_DOTS_END; + } + advance(); + } +__EAT_DOTS_END: + for(int i = 0; i < dots; i++) { + c11_sbuf__write_char(buf, '.'); + } + + if(dots > 0) { + // @id is optional if dots > 0 + if(match(TK_ID)) { + c11_sbuf__write_sv(buf, Token__sv(prev())); + while(match(TK_DOT)) { + consume(TK_ID); + c11_sbuf__write_char(buf, '.'); + c11_sbuf__write_sv(buf, Token__sv(prev())); + } + } + } else { + // @id is required if dots == 0 + consume(TK_ID); + c11_sbuf__write_sv(buf, Token__sv(prev())); + while(match(TK_DOT)) { + consume(TK_ID); + c11_sbuf__write_char(buf, '.'); + c11_sbuf__write_sv(buf, Token__sv(prev())); + } + } + + c11_string* path = c11_sbuf__submit(buf); + Ctx__emit_(ctx(), + OP_IMPORT_PATH, + Ctx__add_const_string(ctx(), c11_string__sv(path)), + prev()->line); + consume(TK_IMPORT); + + if(match(TK_MUL)) { + if(name_scope(self) != NAME_GLOBAL) + return SyntaxError("from import * can only be used in global scope"); + // pop the module and import __all__ + Ctx__emit_(ctx(), OP_POP_IMPORT_STAR, BC_NOARG, prev()->line); + consume_end_stmt(); + return NULL; + } + + do { + Ctx__emit_(ctx(), OP_DUP_TOP, BC_NOARG, BC_KEEPLINE); + consume(TK_ID); + c11_sv name = Token__sv(prev()); + Ctx__emit_(ctx(), OP_LOAD_ATTR, py_name2(name), prev()->line); + if(match(TK_AS)) { + consume(TK_ID); + name = Token__sv(prev()); + } + Ctx__emit_store_name(ctx(), name_scope(self), py_name2(name), prev()->line); + } while(match(TK_COMMA)); + Ctx__emit_(ctx(), OP_POP_TOP, BC_NOARG, BC_KEEPLINE); + consume_end_stmt(); + return NULL; +} + +static Error* compile_try_except(Compiler* self) { + assert(false); + return NULL; +} + static Error* compile_stmt(Compiler* self) { Error* err; if(match(TK_CLASS)) { @@ -2402,11 +2521,18 @@ static Error* compile_stmt(Compiler* self) { case TK_IF: check(compile_if_stmt(self)); break; case TK_WHILE: check(compile_while_loop(self)); break; case TK_FOR: check(compile_for_loop(self)); break; - // case TK_IMPORT: check(compile_normal_import()); break; - // case TK_FROM: check(compile_from_import()); break; + case TK_IMPORT: check(compile_normal_import(self)); break; + case TK_FROM: { + c11_sbuf buf; + c11_sbuf__ctor(&buf); + err = compile_from_import(&buf, self); + c11_sbuf__dtor(&buf); + if(err) return err; + break; + } case TK_DEF: check(compile_function(self, 0)); break; - // case TK_DECORATOR: check(compile_decorated()); break; - // case TK_TRY: check(compile_try_except()); break; + case TK_DECORATOR: check(compile_decorated(self)); break; + case TK_TRY: check(compile_try_except(self)); break; case TK_PASS: consume_end_stmt(); break; /*************************************************/ case TK_ASSERT: { diff --git a/src/public/modules.c b/src/public/modules.c index f9d5a988..211b45a8 100644 --- a/src/public/modules.c +++ b/src/public/modules.c @@ -70,10 +70,16 @@ static bool _py_builtins__exit(int argc, py_Ref argv) { return false; } +static bool _py_builtins__len(int argc, py_Ref argv) { + PY_CHECK_ARGC(1); + return py_len(argv); +} + py_TValue pk_builtins__register() { py_Ref builtins = py_newmodule("builtins", NULL); py_bindnativefunc(builtins, "repr", _py_builtins__repr); py_bindnativefunc(builtins, "exit", _py_builtins__exit); + py_bindnativefunc(builtins, "len", _py_builtins__len); return *builtins; } diff --git a/src/public/py_str.c b/src/public/py_str.c index ededaeb6..a5b514cb 100644 --- a/src/public/py_str.c +++ b/src/public/py_str.c @@ -145,8 +145,15 @@ static bool _py_str__str__(int argc, py_Ref argv) { static bool _py_str__repr__(int argc, py_Ref argv) { PY_CHECK_ARGC(1); - assert(false); - return false; + c11_sbuf buf; + c11_sbuf__ctor(&buf); + int size; + const char* data = py_tostrn(&argv[0], &size); + c11_sbuf__write_quoted(&buf, (c11_sv){data, size}, '\''); + c11_string* res = c11_sbuf__submit(&buf); + py_newstrn(py_retval(), res->data, res->size); + c11_string__delete(res); + return true; } static bool _py_str__iter__(int argc, py_Ref argv) { @@ -294,5 +301,3 @@ bool py_str(py_Ref val) { if(!tmp) return py_repr(val); return py_call(tmp, 1, val); } - -bool py_repr(py_Ref val) { return py_callmagic(__repr__, 1, val); } \ No newline at end of file diff --git a/tests/04_str.py b/tests/04_str.py index 79ad9a8f..e9581b76 100644 --- a/tests/04_str.py +++ b/tests/04_str.py @@ -44,7 +44,6 @@ assert s.replace("foo","ball") == "balltball" assert s.startswith('f') == True;assert s.endswith('o') == False assert t.startswith('this') == True; - assert t.split('w') == ['this is string example....', 'o', '!!!'] assert "a,b,c".split(',') == ['a', 'b', 'c'] assert 'a,'.split(',') == ['a'] @@ -110,29 +109,9 @@ num = 6 assert str(num) == '6' # test Lo group names - 测试 = "test" assert 测试 == "test" -assert "Hello, {}!".format("World") == "Hello, World!" -assert "{} {} {}".format("I", "love", "Python") == "I love Python" -assert "{0} {1} {2}".format("I", "love", "Python") == "I love Python" -assert "{2} {1} {0}".format("I", "love", "Python") == "Python love I" -assert "{0}{1}{0}".format("abra", "cad") == "abracadabra" - -assert "{k}={v}".format(k="key", v="value") == "key=value" -assert "{k}={k}".format(k="key") == "key=key" -assert "{0}={1}".format('{0}', '{1}') == "{0}={1}" -assert "{{{0}}}".format(1) == "{1}" -assert "{0}{1}{1}".format(1, 2, 3) == "122" -try: - "{0}={1}}".format(1, 2) - exit(1) -except ValueError: - pass -assert "{{{}xxx{}x}}".format(1, 2) == "{1xxx2x}" -assert "{{abc}}".format() == "{abc}" - # 3rd slice a = "Hello, World!" assert a[::-1] == "!dlroW ,olleH" @@ -152,20 +131,35 @@ assert b[5:2:-2] == [',', 'l'] a = '123' assert a.rjust(5) == ' 123' assert a.rjust(5, '0') == '00123' -try: - a.rjust(5, '00') - exit(1) -except TypeError: - pass assert a.ljust(5) == '123 ' assert a.ljust(5, '0') == '12300' -try: - a.ljust(5, '00') - exit(1) -except TypeError: - pass assert '\x30\x31\x32' == '012' +assert '\b\b\b' == '\x08\x08\x08' +assert repr('\x1f\x1e\x1f') == '\'\\x1f\\x1e\\x1f\'' + +assert hex(-42) == '-0x2a' +assert hex(42) == '0x2a' + +assert hex(0) == '0x0' +assert hex(1) == '0x1' +assert hex(15) == '0xf' +assert hex(16) == '0x10' +assert hex(255) == '0xff' +assert hex(256) == '0x100' +assert hex(257) == '0x101' +assert hex(17) == '0x11' + +a = '123' +assert a.index('2') == 1 +assert a.index('1') == 0 +assert a.index('3') == 2 + +assert a.index('2', 1) == 1 +assert a.index('1', 0) == 0 + +assert a.find('1') == 0 +assert a.find('1', 1) == -1 a = 'abcd' assert list(a) == ['a', 'b', 'c', 'd'] @@ -184,78 +178,27 @@ assert list(a) == ['b'] a = '测' assert list(a) == ['测'] -assert '\b\b\b' == '\x08\x08\x08' +# test format() +assert "Hello, {}!".format("World") == "Hello, World!" +assert "{} {} {}".format("I", "love", "Python") == "I love Python" +assert "{0} {1} {2}".format("I", "love", "Python") == "I love Python" +assert "{2} {1} {0}".format("I", "love", "Python") == "Python love I" +assert "{0}{1}{0}".format("abra", "cad") == "abracadabra" + +assert "{k}={v}".format(k="key", v="value") == "key=value" +assert "{k}={k}".format(k="key") == "key=key" +assert "{0}={1}".format('{0}', '{1}') == "{0}={1}" +assert "{{{0}}}".format(1) == "{1}" +assert "{0}{1}{1}".format(1, 2, 3) == "122" + +# try: +# "{0}={1}}".format(1, 2) +# exit(1) +# except ValueError: +# pass + +assert "{{{}xxx{}x}}".format(1, 2) == "{1xxx2x}" +assert "{{abc}}".format() == "{abc}" + +# test f-string stack=[1,2,3,4]; assert f"{stack[2:]}" == '[3, 4]' - -assert repr('\x1f\x1e\x1f') == '\'\\x1f\\x1e\\x1f\'' - - -assert hex(-42) == '-0x2a' -assert hex(42) == '0x2a' - -assert hex(0) == '0x0' -assert hex(1) == '0x1' -assert hex(15) == '0xf' -assert hex(16) == '0x10' -assert hex(255) == '0xff' -assert hex(256) == '0x100' -assert hex(257) == '0x101' -assert hex(17) == '0x11' - -import c -assert repr(c.NULL) == '' -assert repr(c.void_p(1)) == '' -assert repr(c.void_p(15)) == '' -assert repr(c.void_p(16)) == '' -assert repr(c.void_p(255)) == '' -assert repr(c.void_p(256)) == '' -assert repr(c.void_p(257)) == '' -assert repr(c.void_p(17)) == '' - -# random hex test -import random - - -def test(__min, __max): - for _ in range(100): - num = random.randint(__min, __max) - hex_num = hex(num) - assert eval(hex_num) == num - if num >= 0: - assert repr(c.void_p(num)) == f'' - -test(0, 100) -test(0, 100000) -test(-100, 100) -test(-100000, 100000) -test(-2**30, 2**30) - - -a = '123' -assert a.index('2') == 1 -assert a.index('1') == 0 -assert a.index('3') == 2 - -assert a.index('2', 1) == 1 -assert a.index('1', 0) == 0 - -try: - a.index('1', 1) - exit(1) -except ValueError: - pass - -try: - a.index('1', -1) - exit(1) -except ValueError: - pass - -assert a.find('1') == 0 -assert a.find('1', 1) == -1 - -try: - a.find('1', -1) - exit(1) -except ValueError: - pass