mirror of
https://github.com/pocketpy/pocketpy
synced 2025-10-19 19:10:17 +00:00
Compare commits
4 Commits
6f7df0c3af
...
e4a900dd88
Author | SHA1 | Date | |
---|---|---|---|
|
e4a900dd88 | ||
|
50db32f36e | ||
|
226febea82 | ||
|
62491dd99a |
@ -125,6 +125,8 @@ bool pk_stack_binaryop(VM* self, py_Name op, py_Name rop);
|
|||||||
|
|
||||||
void pk_print_stack(VM* self, py_Frame* frame, Bytecode byte);
|
void pk_print_stack(VM* self, py_Frame* frame, Bytecode byte);
|
||||||
|
|
||||||
|
bool pk_format_object(VM* self, py_Ref val, c11_sv spec);
|
||||||
|
|
||||||
// type registration
|
// type registration
|
||||||
void pk_object__register();
|
void pk_object__register();
|
||||||
void pk_number__register();
|
void pk_number__register();
|
||||||
|
@ -81,119 +81,6 @@ def sorted(iterable, key=None, reverse=False):
|
|||||||
a.sort(key=key, reverse=reverse)
|
a.sort(key=key, reverse=reverse)
|
||||||
return a
|
return a
|
||||||
|
|
||||||
##### str #####
|
|
||||||
def __format_string(self: str, *args, **kwargs) -> str:
|
|
||||||
def tokenizeString(s: str):
|
|
||||||
tokens = []
|
|
||||||
L, R = 0,0
|
|
||||||
|
|
||||||
mode = None
|
|
||||||
curArg = 0
|
|
||||||
# lookingForKword = False
|
|
||||||
|
|
||||||
while(R<len(s)):
|
|
||||||
curChar = s[R]
|
|
||||||
nextChar = s[R+1] if R+1<len(s) else ''
|
|
||||||
|
|
||||||
# Invalid case 1: stray '}' encountered, example: "ABCD EFGH {name} IJKL}", "Hello {vv}}", "HELLO {0} WORLD}"
|
|
||||||
if curChar == '}' and nextChar != '}':
|
|
||||||
raise ValueError("Single '}' encountered in format string")
|
|
||||||
|
|
||||||
# Valid Case 1: Escaping case, we escape "{{ or "}}" to be "{" or "}", example: "{{}}", "{{My Name is {0}}}"
|
|
||||||
if (curChar == '{' and nextChar == '{') or (curChar == '}' and nextChar == '}'):
|
|
||||||
|
|
||||||
if (L<R): # Valid Case 1.1: make sure we are not adding empty string
|
|
||||||
tokens.append(s[L:R]) # add the string before the escape
|
|
||||||
|
|
||||||
|
|
||||||
tokens.append(curChar) # Valid Case 1.2: add the escape char
|
|
||||||
L = R+2 # move the left pointer to the next char
|
|
||||||
R = R+2 # move the right pointer to the next char
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Valid Case 2: Regular command line arg case: example: "ABCD EFGH {} IJKL", "{}", "HELLO {} WORLD"
|
|
||||||
elif curChar == '{' and nextChar == '}':
|
|
||||||
if mode is not None and mode != 'auto':
|
|
||||||
# Invalid case 2: mixing automatic and manual field specifications -- example: "ABCD EFGH {name} IJKL {}", "Hello {vv} {}", "HELLO {0} WORLD {}"
|
|
||||||
raise ValueError("Cannot switch from manual field numbering to automatic field specification")
|
|
||||||
|
|
||||||
mode = 'auto'
|
|
||||||
if(L<R): # Valid Case 2.1: make sure we are not adding empty string
|
|
||||||
tokens.append(s[L:R]) # add the string before the special marker for the arg
|
|
||||||
|
|
||||||
tokens.append("{"+str(curArg)+"}") # Valid Case 2.2: add the special marker for the arg
|
|
||||||
curArg+=1 # increment the arg position, this will be used for referencing the arg later
|
|
||||||
|
|
||||||
L = R+2 # move the left pointer to the next char
|
|
||||||
R = R+2 # move the right pointer to the next char
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Valid Case 3: Key-word arg case: example: "ABCD EFGH {name} IJKL", "Hello {vv}", "HELLO {name} WORLD"
|
|
||||||
elif (curChar == '{'):
|
|
||||||
|
|
||||||
if mode is not None and mode != 'manual':
|
|
||||||
# # Invalid case 2: mixing automatic and manual field specifications -- example: "ABCD EFGH {} IJKL {name}", "Hello {} {1}", "HELLO {} WORLD {name}"
|
|
||||||
raise ValueError("Cannot switch from automatic field specification to manual field numbering")
|
|
||||||
|
|
||||||
mode = 'manual'
|
|
||||||
|
|
||||||
if(L<R): # Valid case 3.1: make sure we are not adding empty string
|
|
||||||
tokens.append(s[L:R]) # add the string before the special marker for the arg
|
|
||||||
|
|
||||||
# We look for the end of the keyword
|
|
||||||
kwL = R # Keyword left pointer
|
|
||||||
kwR = R+1 # Keyword right pointer
|
|
||||||
while(kwR<len(s) and s[kwR]!='}'):
|
|
||||||
if s[kwR] == '{': # Invalid case 3: stray '{' encountered, example: "ABCD EFGH {n{ame} IJKL {", "Hello {vv{}}", "HELLO {0} WOR{LD}"
|
|
||||||
raise ValueError("Unexpected '{' in field name")
|
|
||||||
kwR += 1
|
|
||||||
|
|
||||||
# Valid case 3.2: We have successfully found the end of the keyword
|
|
||||||
if kwR<len(s) and s[kwR] == '}':
|
|
||||||
tokens.append(s[kwL:kwR+1]) # add the special marker for the arg
|
|
||||||
L = kwR+1
|
|
||||||
R = kwR+1
|
|
||||||
|
|
||||||
# Invalid case 4: We didn't find the end of the keyword, throw error
|
|
||||||
else:
|
|
||||||
raise ValueError("Expected '}' before end of string")
|
|
||||||
continue
|
|
||||||
|
|
||||||
R = R+1
|
|
||||||
|
|
||||||
|
|
||||||
# Valid case 4: We have reached the end of the string, add the remaining string to the tokens
|
|
||||||
if L<R:
|
|
||||||
tokens.append(s[L:R])
|
|
||||||
|
|
||||||
# print(tokens)
|
|
||||||
return tokens
|
|
||||||
|
|
||||||
tokens = tokenizeString(self)
|
|
||||||
argMap = {}
|
|
||||||
for i, a in enumerate(args):
|
|
||||||
argMap[str(i)] = a
|
|
||||||
final_tokens = []
|
|
||||||
for t in tokens:
|
|
||||||
if t[0] == '{' and t[-1] == '}':
|
|
||||||
key = t[1:-1]
|
|
||||||
argMapVal = argMap.get(key, None)
|
|
||||||
kwargsVal = kwargs.get(key, None)
|
|
||||||
|
|
||||||
if argMapVal is None and kwargsVal is None:
|
|
||||||
raise ValueError("No arg found for token: "+t)
|
|
||||||
elif argMapVal is not None:
|
|
||||||
final_tokens.append(str(argMapVal))
|
|
||||||
else:
|
|
||||||
final_tokens.append(str(kwargsVal))
|
|
||||||
else:
|
|
||||||
final_tokens.append(t)
|
|
||||||
|
|
||||||
return ''.join(final_tokens)
|
|
||||||
|
|
||||||
str.format = __format_string
|
|
||||||
del __format_string
|
|
||||||
|
|
||||||
|
|
||||||
def help(obj):
|
def help(obj):
|
||||||
if hasattr(obj, '__func__'):
|
if hasattr(obj, '__func__'):
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
#include "pocketpy/common/str.h"
|
#include "pocketpy/common/str.h"
|
||||||
|
#include "pocketpy/objects/base.h"
|
||||||
#include "pocketpy/pocketpy.h"
|
#include "pocketpy/pocketpy.h"
|
||||||
|
|
||||||
#include "pocketpy/objects/object.h"
|
#include "pocketpy/objects/object.h"
|
||||||
#include "pocketpy/interpreter/vm.h"
|
#include "pocketpy/interpreter/vm.h"
|
||||||
#include "pocketpy/common/sstream.h"
|
#include "pocketpy/common/sstream.h"
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
c11_string* pk_tostr(py_Ref self) {
|
c11_string* pk_tostr(py_Ref self) {
|
||||||
assert(self->type == tp_str);
|
assert(self->type == tp_str);
|
||||||
@ -394,6 +396,99 @@ static bool str_encode(int argc, py_Ref argv) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool str_format(int argc, py_Ref argv) {
|
||||||
|
c11_sv self = py_tosv(argv);
|
||||||
|
py_Ref args = argv + 1;
|
||||||
|
int64_t auto_field_index = -1;
|
||||||
|
bool manual_field_used = false;
|
||||||
|
const char* p_begin = self.data;
|
||||||
|
const char* p_end = self.data + self.size;
|
||||||
|
const char* p = p_begin;
|
||||||
|
c11_sbuf buf;
|
||||||
|
c11_sbuf__ctor(&buf);
|
||||||
|
while(p < p_end) {
|
||||||
|
if(*p == '{') {
|
||||||
|
if((p + 1) < p_end && p[1] == '{') {
|
||||||
|
// '{{' -> '{'
|
||||||
|
c11_sbuf__write_char(&buf, '{');
|
||||||
|
p += 2;
|
||||||
|
} else {
|
||||||
|
if((p + 1) >= p_end) {
|
||||||
|
return ValueError("single '{' encountered in format string");
|
||||||
|
}
|
||||||
|
p++;
|
||||||
|
// parse field
|
||||||
|
c11_sv field = {p, 0};
|
||||||
|
while(p < p_end && *p != '}' && *p != ':') {
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
if(p < p_end) field.size = p - field.data;
|
||||||
|
// parse spec
|
||||||
|
c11_sv spec = {p, 0};
|
||||||
|
if(*p == ':') {
|
||||||
|
while(p < p_end && *p != '}') {
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
if(p < p_end) spec.size = p - spec.data;
|
||||||
|
}
|
||||||
|
if(p < p_end) {
|
||||||
|
c11__rtassert(*p == '}');
|
||||||
|
} else {
|
||||||
|
return ValueError("expected '}' before end of string");
|
||||||
|
}
|
||||||
|
// parse auto field
|
||||||
|
int64_t arg_index;
|
||||||
|
if(field.size > 0) { // {0}
|
||||||
|
if(auto_field_index >= 0) {
|
||||||
|
return ValueError(
|
||||||
|
"cannot switch from automatic field numbering to manual field specification");
|
||||||
|
}
|
||||||
|
IntParsingResult res = c11__parse_uint(field, &arg_index, 10);
|
||||||
|
if(res != IntParsing_SUCCESS) {
|
||||||
|
return ValueError("only integer field name is supported");
|
||||||
|
}
|
||||||
|
manual_field_used = true;
|
||||||
|
} else { // {}
|
||||||
|
if(manual_field_used) {
|
||||||
|
return ValueError(
|
||||||
|
"cannot switch from manual field specification to automatic field numbering");
|
||||||
|
}
|
||||||
|
auto_field_index++;
|
||||||
|
arg_index = auto_field_index;
|
||||||
|
}
|
||||||
|
// do format
|
||||||
|
if(arg_index < 0 || arg_index >= (argc - 1)) {
|
||||||
|
return IndexError("replacement index %i out of range for positional args tuple",
|
||||||
|
arg_index);
|
||||||
|
}
|
||||||
|
bool ok = pk_format_object(pk_current_vm, &args[arg_index], spec);
|
||||||
|
if(!ok) {
|
||||||
|
c11_sbuf__dtor(&buf);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// append to buf
|
||||||
|
c11__rtassert(py_isstr(py_retval()));
|
||||||
|
c11_sv formatted = py_tosv(py_retval());
|
||||||
|
c11_sbuf__write_sv(&buf, formatted);
|
||||||
|
p++; // skip '}'
|
||||||
|
}
|
||||||
|
} else if(*p == '}') {
|
||||||
|
if((p + 1) < p_end && p[1] == '}') {
|
||||||
|
// '}}' -> '}'
|
||||||
|
c11_sbuf__write_char(&buf, '}');
|
||||||
|
p += 2;
|
||||||
|
} else {
|
||||||
|
return ValueError("single '}' encountered in format string");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c11_sbuf__write_char(&buf, *p);
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c11_sbuf__py_submit(&buf, py_retval());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
py_Type pk_str__register() {
|
py_Type pk_str__register() {
|
||||||
py_Type type = pk_newtype("str", tp_object, NULL, NULL, false, true);
|
py_Type type = pk_newtype("str", tp_object, NULL, NULL, false, true);
|
||||||
// no need to dtor because the memory is controlled by the object
|
// no need to dtor because the memory is controlled by the object
|
||||||
@ -434,6 +529,7 @@ py_Type pk_str__register() {
|
|||||||
py_bindmethod(tp_str, "find", str_find);
|
py_bindmethod(tp_str, "find", str_find);
|
||||||
py_bindmethod(tp_str, "index", str_index);
|
py_bindmethod(tp_str, "index", str_index);
|
||||||
py_bindmethod(tp_str, "encode", str_encode);
|
py_bindmethod(tp_str, "encode", str_encode);
|
||||||
|
py_bindmethod(tp_str, "format", str_format);
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
File diff suppressed because one or more lines are too long
@ -11,8 +11,6 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
static bool format_object(VM* self, py_Ref val, c11_sv spec);
|
|
||||||
|
|
||||||
#define DISPATCH() \
|
#define DISPATCH() \
|
||||||
do { \
|
do { \
|
||||||
frame->ip++; \
|
frame->ip++; \
|
||||||
@ -1191,8 +1189,9 @@ __NEXT_STEP:
|
|||||||
//////////////////
|
//////////////////
|
||||||
case OP_FORMAT_STRING: {
|
case OP_FORMAT_STRING: {
|
||||||
py_Ref spec = c11__at(py_TValue, &frame->co->consts, byte.arg);
|
py_Ref spec = c11__at(py_TValue, &frame->co->consts, byte.arg);
|
||||||
bool ok = format_object(self, TOP(), py_tosv(spec));
|
bool ok = pk_format_object(self, TOP(), py_tosv(spec));
|
||||||
if(!ok) goto __ERROR;
|
if(!ok) goto __ERROR;
|
||||||
|
py_assign(TOP(), py_retval());
|
||||||
DISPATCH();
|
DISPATCH();
|
||||||
}
|
}
|
||||||
default: c11__unreachable();
|
default: c11__unreachable();
|
||||||
@ -1298,9 +1297,9 @@ bool pk_stack_binaryop(VM* self, py_Name op, py_Name rop) {
|
|||||||
rhs_t);
|
rhs_t);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool format_object(VM* self, py_Ref val, c11_sv spec) {
|
bool pk_format_object(VM* self, py_Ref val, c11_sv spec) {
|
||||||
// format TOS via `spec` inplace
|
// format TOS via `spec` inplace
|
||||||
// spec: '!r:.2f', '.2f'
|
// spec: '!r:.2f', ':.2f', '.2f'
|
||||||
if(spec.size == 0) return py_str(val);
|
if(spec.size == 0) return py_str(val);
|
||||||
|
|
||||||
if(spec.data[0] == '!') {
|
if(spec.data[0] == '!') {
|
||||||
@ -1441,7 +1440,7 @@ static bool format_object(VM* self, py_Ref val, c11_sv spec) {
|
|||||||
|
|
||||||
c11_string__delete(body);
|
c11_string__delete(body);
|
||||||
// inplace update
|
// inplace update
|
||||||
c11_sbuf__py_submit(&buf, val);
|
c11_sbuf__py_submit(&buf, py_retval());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -651,10 +651,18 @@ bool dict_items__next__(int argc, py_Ref argv) {
|
|||||||
return StopIteration();
|
return StopIteration();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool dict_items__len__(int argc, py_Ref argv) {
|
||||||
|
PY_CHECK_ARGC(1);
|
||||||
|
DictIterator* iter = py_touserdata(py_arg(0));
|
||||||
|
py_newint(py_retval(), iter->dict->length);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
py_Type pk_dict_items__register() {
|
py_Type pk_dict_items__register() {
|
||||||
py_Type type = pk_newtype("dict_iterator", tp_object, NULL, NULL, false, true);
|
py_Type type = pk_newtype("dict_iterator", tp_object, NULL, NULL, false, true);
|
||||||
py_bindmagic(type, __iter__, pk_wrapper__self);
|
py_bindmagic(type, __iter__, pk_wrapper__self);
|
||||||
py_bindmagic(type, __next__, dict_items__next__);
|
py_bindmagic(type, __next__, dict_items__next__);
|
||||||
|
py_bindmagic(type, __len__, dict_items__len__);
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,8 +209,8 @@ assert "{0} {1} {2}".format("I", "love", "Python") == "I love Python"
|
|||||||
assert "{2} {1} {0}".format("I", "love", "Python") == "Python love I"
|
assert "{2} {1} {0}".format("I", "love", "Python") == "Python love I"
|
||||||
assert "{0}{1}{0}".format("abra", "cad") == "abracadabra"
|
assert "{0}{1}{0}".format("abra", "cad") == "abracadabra"
|
||||||
|
|
||||||
assert "{k}={v}".format(k="key", v="value") == "key=value"
|
# assert "{k}={v}".format(k="key", v="value") == "key=value"
|
||||||
assert "{k}={k}".format(k="key") == "key=key"
|
# assert "{k}={k}".format(k="key") == "key=key"
|
||||||
assert "{0}={1}".format('{0}', '{1}') == "{0}={1}"
|
assert "{0}={1}".format('{0}', '{1}') == "{0}={1}"
|
||||||
assert "{{{0}}}".format(1) == "{1}"
|
assert "{{{0}}}".format(1) == "{1}"
|
||||||
assert "{0}{1}{1}".format(1, 2, 3) == "122"
|
assert "{0}{1}{1}".format(1, 2, 3) == "122"
|
||||||
|
@ -43,9 +43,18 @@ assert f'{a:<10}' == '10 '
|
|||||||
assert f'{a:<10.2f}' == '10.00 '
|
assert f'{a:<10.2f}' == '10.00 '
|
||||||
assert f'{a:>10.2f}' == ' 10.00'
|
assert f'{a:>10.2f}' == ' 10.00'
|
||||||
|
|
||||||
|
assert '{}'.format(a) == '10'
|
||||||
|
assert '{:>10}'.format(a) == ' 10'
|
||||||
|
assert '{:<10}'.format(a) == '10 '
|
||||||
|
assert '{:<10.2f}'.format(a) == '10.00 '
|
||||||
|
assert '{:>10.2f}'.format(a) == ' 10.00'
|
||||||
|
|
||||||
assert f'{a:^10}' == ' 10 '
|
assert f'{a:^10}' == ' 10 '
|
||||||
assert f'{a:^10.2f}' == ' 10.00 '
|
assert f'{a:^10.2f}' == ' 10.00 '
|
||||||
|
|
||||||
|
assert '{:^10}'.format(a) == ' 10 '
|
||||||
|
assert '{:^10.2f}'.format(a) == ' 10.00 '
|
||||||
|
|
||||||
assert f'{a:3d}' == ' 10'
|
assert f'{a:3d}' == ' 10'
|
||||||
assert f'{a:10d}' == ' 10'
|
assert f'{a:10d}' == ' 10'
|
||||||
assert f'{a:1d}' == '10'
|
assert f'{a:1d}' == '10'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user