mirror of
https://github.com/arendst/Tasmota.git
synced 2025-04-25 15:27:17 +00:00
Berry added f-strings as an alternative to string formatting (#18937)
This commit is contained in:
parent
558f812ec2
commit
85f357096a
@ -13,6 +13,7 @@ All notable changes to this project will be documented in this file.
|
||||
- Berry `string.format()` now automatically converts type according to format
|
||||
- Matter add friendly-name (NodeLabel) to each endpoint
|
||||
- Berry add global function `format` as a simpler syntax to `string.format`
|
||||
- Berry added f-strings as an alternative to string formatting
|
||||
|
||||
### Breaking Changed
|
||||
|
||||
|
@ -137,7 +137,7 @@ void be_print_inst(binstruction ins, int pc, void* fout)
|
||||
logbuf("%s", opc2str(op));
|
||||
break;
|
||||
}
|
||||
memcpy(__lbuf_tmp, __lbuf, strlen(__lbuf)+1);
|
||||
memcpy(__lbuf_tmp, __lbuf, strlen(__lbuf) + 1);
|
||||
logbuf("%s\n", __lbuf_tmp);
|
||||
if (fout) {
|
||||
be_fwrite(fout, __lbuf, strlen(__lbuf));
|
||||
@ -184,7 +184,7 @@ static void sourceinfo(bproto *proto, binstruction *ip)
|
||||
blineinfo *end = it + proto->nlineinfo;
|
||||
int pc = cast_int(ip - proto->code - 1); /* now vm->ip has been increased */
|
||||
for (; it < end && pc > it->endpc; ++it);
|
||||
sprintf(buf, ":%d:", it->linenumber);
|
||||
snprintf(buf, sizeof(buf), ":%d:", it->linenumber);
|
||||
be_writestring(str(proto->source));
|
||||
be_writestring(buf);
|
||||
} else {
|
||||
|
@ -183,8 +183,9 @@ int be_protectedparser(bvm *vm,
|
||||
return res;
|
||||
}
|
||||
|
||||
static const char* _sgets(void *data, size_t *size)
|
||||
static const char* _sgets(struct blexer* lexer, void *data, size_t *size)
|
||||
{
|
||||
(void)lexer;
|
||||
struct strbuf *sb = data;
|
||||
*size = sb->len;
|
||||
if (sb->len) {
|
||||
@ -194,8 +195,9 @@ static const char* _sgets(void *data, size_t *size)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char* _fgets(void *data, size_t *size)
|
||||
static const char* _fgets(struct blexer* lexer, void *data, size_t *size)
|
||||
{
|
||||
(void)lexer;
|
||||
struct filebuf *fb = data;
|
||||
*size = be_fread(fb->fp, fb->buf, sizeof(fb->buf));
|
||||
if (*size) {
|
||||
|
@ -13,14 +13,15 @@
|
||||
#include "be_map.h"
|
||||
#include "be_vm.h"
|
||||
#include "be_strlib.h"
|
||||
#include <string.h>
|
||||
|
||||
#define SHORT_STR_LEN 32
|
||||
#define EOS '\0' /* end of source */
|
||||
|
||||
#define type_count() (int)array_count(kwords_tab)
|
||||
#define lexbuf(lex) ((lex)->buf.s)
|
||||
#define isvalid(lex) ((lex)->cursor < (lex)->endbuf)
|
||||
#define lgetc(lex) ((lex)->cursor)
|
||||
#define isvalid(lex) ((lex)->reader.cursor < (lex)->endbuf)
|
||||
#define lgetc(lex) ((lex)->reader.cursor)
|
||||
#define setstr(lex, v) ((lex)->token.u.s = (v))
|
||||
#define setint(lex, v) ((lex)->token.u.i = (v))
|
||||
#define setreal(lex, v) ((lex)->token.u.r = (v))
|
||||
@ -39,7 +40,8 @@ static const char* const kwords_tab[] = {
|
||||
":", "?", "->", "if", "elif", "else", "while",
|
||||
"for", "def", "end", "class", "break", "continue",
|
||||
"return", "true", "false", "nil", "var", "do",
|
||||
"import", "as", "try", "except", "raise", "static"
|
||||
"import", "as", "try", "except", "raise", "static",
|
||||
// ".f"
|
||||
};
|
||||
|
||||
void be_lexerror(blexer *lexer, const char *msg)
|
||||
@ -102,12 +104,12 @@ static int next(blexer *lexer)
|
||||
struct blexerreader *lr = &lexer->reader;
|
||||
if (!(lr->len--)) {
|
||||
static const char eos = EOS;
|
||||
const char *s = lr->readf(lr->data, &lr->len);
|
||||
const char *s = lr->readf(lexer, lr->data, &lr->len);
|
||||
lr->s = s ? s : &eos;
|
||||
--lr->len;
|
||||
}
|
||||
lexer->cursor = *lr->s++;
|
||||
return lexer->cursor;
|
||||
lexer->reader.cursor = *lr->s++;
|
||||
return lexer->reader.cursor;
|
||||
}
|
||||
|
||||
static void clear_buf(blexer *lexer)
|
||||
@ -115,10 +117,7 @@ static void clear_buf(blexer *lexer)
|
||||
lexer->buf.len = 0;
|
||||
}
|
||||
|
||||
/* save and next */
|
||||
static int save(blexer *lexer)
|
||||
{
|
||||
int ch = lgetc(lexer);
|
||||
static void save_char(blexer *lexer, int ch) {
|
||||
struct blexerbuf *buf = &lexer->buf;
|
||||
if (buf->len >= buf->size) {
|
||||
size_t size = buf->size << 1;
|
||||
@ -126,6 +125,13 @@ static int save(blexer *lexer)
|
||||
buf->size = size;
|
||||
}
|
||||
buf->s[buf->len++] = (char)ch;
|
||||
}
|
||||
|
||||
/* save and next */
|
||||
static int save(blexer *lexer)
|
||||
{
|
||||
int ch = lgetc(lexer);
|
||||
save_char(lexer, ch);
|
||||
return next(lexer);
|
||||
}
|
||||
|
||||
@ -247,7 +253,7 @@ static int skip_newline(blexer *lexer)
|
||||
next(lexer); /* skip "\n\r" or "\r\n" */
|
||||
}
|
||||
lexer->linenumber++;
|
||||
return lexer->cursor;
|
||||
return lexer->reader.cursor;
|
||||
}
|
||||
|
||||
static void skip_comment(blexer *lexer)
|
||||
@ -373,12 +379,217 @@ static btokentype scan_numeral(blexer *lexer)
|
||||
return type;
|
||||
}
|
||||
|
||||
/* structure for a temporary reader used by transpiler, with attributes for an allocated buffer and size */
|
||||
struct blexerreader_save {
|
||||
struct blexerreader reader;
|
||||
char* s;
|
||||
size_t size;
|
||||
char cursor;
|
||||
};
|
||||
|
||||
/* buf reader for transpiled code from f-strings */
|
||||
/* it restores the original reader when the transpiler buffer is empty */
|
||||
/* the first pass returns a single byte buffer with the saved cursor */
|
||||
/* the second pass restores the original reader */
|
||||
static const char* _bufgets(struct blexer* lexer, void *data, size_t *size)
|
||||
{
|
||||
/* this is called once the temporaty transpiler buffer is empty */
|
||||
struct blexerreader *reader = &lexer->reader; /* current reader which is temporary only for the transpiler */
|
||||
struct blexerreader_save *reader_save = data; /* original reader that needs to be restored once the buffer is empty */
|
||||
|
||||
/* first case, we saved the cursor (fist char), we server it now */
|
||||
if (reader_save->reader.cursor >= 0) { /* serve the previously saved cursor */
|
||||
/* copy cursor to a 'char' type */
|
||||
reader_save->cursor = reader_save->reader.cursor;
|
||||
reader_save->reader.cursor = -1; /* no more cursor saved */
|
||||
*size = 1;
|
||||
return &reader_save->cursor;
|
||||
}
|
||||
|
||||
/* second case, the saved cursor was returned, now restore the normal flow of the original reader */
|
||||
/* restore the original reader */
|
||||
*reader = reader_save->reader;
|
||||
|
||||
/* free the memory from the structure */
|
||||
be_free(lexer->vm, reader_save->s, reader_save->size); /* free the buffer */
|
||||
be_free(lexer->vm, reader_save, sizeof(struct blexerreader_save)); /* free the structure */
|
||||
|
||||
if (!reader->len) { /* just in case the original buffer was also */
|
||||
return reader->readf(lexer, reader->data, size);
|
||||
}
|
||||
/* the following is not necessary, but safer */
|
||||
*size = reader->len;
|
||||
return reader->s;
|
||||
}
|
||||
|
||||
static btokentype scan_string(blexer *lexer); /* forward declaration */
|
||||
|
||||
/* scan f-string and transpile it to `format(...)` syntax then feeding the normal lexer and parser */
|
||||
static void scan_f_string(blexer *lexer)
|
||||
{
|
||||
char ch;
|
||||
clear_buf(lexer);
|
||||
scan_string(lexer); /* first scan the entire string in lexer->buf */
|
||||
|
||||
/* save original reader until the transpiled is processed */
|
||||
/* reader will be restored by the reader function once the transpiled buffer is empty */
|
||||
struct blexerreader_save *reader_save = (struct blexerreader_save *) be_malloc(lexer->vm, sizeof(struct blexerreader_save)); /* temporary reader */
|
||||
reader_save->reader = lexer->reader;
|
||||
|
||||
/* save blexerbuf which contains the unparsed_fstring */
|
||||
struct blexerbuf buf_unparsed_fstr = lexer->buf;
|
||||
|
||||
/* prepare and allocated a temporary buffer to save parsed f_string */
|
||||
lexer->buf.size = lexer->buf.size + 20;
|
||||
lexer->buf.s = be_malloc(lexer->vm, lexer->buf.size);
|
||||
lexer->buf.len = 0;
|
||||
|
||||
/* parse f_string */
|
||||
/* First pass, check syntax and extract string literals, and format */
|
||||
save_char(lexer, '(');
|
||||
save_char(lexer, '"');
|
||||
for (size_t i = 0; i < buf_unparsed_fstr.len; i++) {
|
||||
ch = buf_unparsed_fstr.s[i];
|
||||
switch (ch) {
|
||||
case '%': /* % needs to be encoded as %% */
|
||||
save_char(lexer, '%');
|
||||
save_char(lexer, '%');
|
||||
break;
|
||||
case '\\': /* \ needs to be encoded as \\ */
|
||||
save_char(lexer, '\\');
|
||||
save_char(lexer, '\\');
|
||||
break;
|
||||
case '"': /* " needs to be encoded as \" */
|
||||
save_char(lexer, '\\');
|
||||
save_char(lexer, '"');
|
||||
break;
|
||||
case '}': /* }} is converted as } yet we tolerate a single } */
|
||||
if ((i+1 < buf_unparsed_fstr.len) && (buf_unparsed_fstr.s[i+1] == '}')) { i++; } /* if '}}' replace with '}' */
|
||||
save_char(lexer, '}');
|
||||
break;
|
||||
default: /* copy any other character */
|
||||
save_char(lexer, ch);
|
||||
break;
|
||||
case '{': /* special case for { */
|
||||
i++; /* in all cases skip to next char */
|
||||
if ((i < buf_unparsed_fstr.len) && (buf_unparsed_fstr.s[i] == '{')) {
|
||||
save_char(lexer, '{'); /* {{ is simply encoded as { and continue parsing */
|
||||
} else {
|
||||
/* we still don't know if '=' is present, so we copy the expression each time, and rollback if we find out the '=' is not present */
|
||||
size_t rollback = lexer->buf.len; /* mark the end of string for later rollback if '=' is not present */
|
||||
/* parse inner part */
|
||||
/* skip everything until either ':' or '}' or '=' */
|
||||
/* if end of string is reached before '}' raise en error */
|
||||
for (; i < buf_unparsed_fstr.len; i++) {
|
||||
ch = buf_unparsed_fstr.s[i];
|
||||
if (ch == ':' || ch == '}') { break; }
|
||||
save_char(lexer, ch); /* copy any character unless it's ':' or '}' */
|
||||
if (ch == '=') { break; } /* '=' is copied but breaks parsing as well */
|
||||
}
|
||||
/* safe check if we reached the end of the string */
|
||||
if (i >= buf_unparsed_fstr.len) { be_raise(lexer->vm, "syntax_error", "'}' expected"); }
|
||||
/* if '=' detected then do some additional checks */
|
||||
if (ch == '=') {
|
||||
i++; /* skip '=' and check we haven't reached the end */
|
||||
if (i >= buf_unparsed_fstr.len) { be_raise(lexer->vm, "syntax_error", "'}' expected"); }
|
||||
ch = buf_unparsed_fstr.s[i];
|
||||
if ((ch != ':') && (ch != '}')) { /* '=' must be immediately followed by ':' or '}' */
|
||||
be_raise(lexer->vm, "syntax_error", "':' or '}' expected after '='");
|
||||
}
|
||||
} else {
|
||||
/* no '=' present, rollback the text of the expression */
|
||||
lexer->buf.len = rollback;
|
||||
}
|
||||
save_char(lexer, '%'); /* start format encoding */
|
||||
if (ch == ':') {
|
||||
/* copy format */
|
||||
i++;
|
||||
if ((i < buf_unparsed_fstr.len) && (buf_unparsed_fstr.s[i] == '%')) { i++; } /* skip '%' following ':' */
|
||||
for (; i < buf_unparsed_fstr.len; i++) {
|
||||
ch = buf_unparsed_fstr.s[i];
|
||||
if (ch == '}') { break; }
|
||||
save_char(lexer, ch);
|
||||
}
|
||||
if (i >= buf_unparsed_fstr.len) { be_raise(lexer->vm, "syntax_error", "'}' expected"); }
|
||||
} else {
|
||||
/* if no formatting, output '%s' */
|
||||
save_char(lexer, 's');
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
save_char(lexer, '"'); /* finish format string */
|
||||
|
||||
/* Second pass - add arguments if any */
|
||||
for (size_t i = 0; i < buf_unparsed_fstr.len; i++) {
|
||||
/* skip any character that is not '{' followed by '{' */
|
||||
if (buf_unparsed_fstr.s[i] == '{') {
|
||||
i++; /* in all cases skip to next char */
|
||||
if ((i < buf_unparsed_fstr.len) && (buf_unparsed_fstr.s[i] == '{')) { continue; }
|
||||
/* extract argument */
|
||||
save_char(lexer, ','); /* add ',' to start next argument to `format()` */
|
||||
for (; i < buf_unparsed_fstr.len; i++) {
|
||||
ch = buf_unparsed_fstr.s[i];
|
||||
if (ch == '=' || ch == ':' || ch == '}') { break; }
|
||||
save_char(lexer, ch); /* copy expression until we reach ':', '=' or '}' */
|
||||
}
|
||||
/* no need to check for end of string here, it was done already in first pass */
|
||||
if (ch == ':' || ch == '=') { /* if '=' or ':', skip everyting until '}' */
|
||||
i++;
|
||||
for (; i < buf_unparsed_fstr.len; i++) {
|
||||
ch = buf_unparsed_fstr.s[i];
|
||||
if (ch == '}') { break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
save_char(lexer, ')'); /* add final ')' */
|
||||
|
||||
/* Situation now: */
|
||||
/* `buf_unparsed_fstr` contains the buffer of the input unparsed f-string, ex: "age: {age:2i}" */
|
||||
/* `lexer->buf` contains the buffer of the transpiled f-string without call to format(), ex: '("age: %2i", age)' */
|
||||
/* `reader_save` contains the original reader to continue parsing after f-string */
|
||||
/* `lexer->reader` will contain a temporary reader from the parsed f-string */
|
||||
|
||||
/* extract the parsed f-string from the temporary buffer (needs later deallocation) */
|
||||
char * parsed_fstr_s = lexer->buf.s; /* needs later deallocation with parsed_fstr_size */
|
||||
size_t parsed_fstr_len = lexer->buf.len;
|
||||
size_t parsed_fstr_size = lexer->buf.size;
|
||||
|
||||
/* restore buf to lexer */
|
||||
lexer->buf = buf_unparsed_fstr;
|
||||
|
||||
/* change the temporary reader to the parsed f-string */
|
||||
lexer->reader.len = parsed_fstr_len;
|
||||
lexer->reader.data = (void*) reader_save; /* link to the saved context */
|
||||
lexer->reader.s = parsed_fstr_s; /* reader is responisble to deallocate later this buffer */
|
||||
lexer->reader.readf = _bufgets;
|
||||
|
||||
/* add information needed for `_bufgets` to later deallocate the buffer */
|
||||
reader_save->size = parsed_fstr_size;
|
||||
reader_save->s = parsed_fstr_s;
|
||||
|
||||
/* start parsing the parsed f-string, which is btw always '(' */
|
||||
next(lexer);
|
||||
|
||||
/* remember that we are still in `scan_identifier()`, we replace the 'f' identifier to 'format' which is the global function to call */
|
||||
static const char FORMAT[] = "format";
|
||||
lexer->buf.len = sizeof(FORMAT) - 1; /* we now that buf size is at least SHORT_STR_LEN (32) */
|
||||
memmove(lexer->buf.s, FORMAT, lexer->buf.len);
|
||||
}
|
||||
|
||||
static btokentype scan_identifier(blexer *lexer)
|
||||
{
|
||||
int type;
|
||||
bstring *s;
|
||||
save(lexer);
|
||||
match(lexer, is_word);
|
||||
/* check if the form is f"aaaa" or f'aaa' */
|
||||
char ch = lgetc(lexer);
|
||||
if ((lexer->buf.len == 1) && (lexer->buf.s[0] == 'f') && (ch == '"' || ch == '\'')) {
|
||||
scan_f_string(lexer);
|
||||
}
|
||||
s = buf_tostr(lexer);
|
||||
type = str_extra(s);
|
||||
if (type >= KeyIf && type < type_count()) {
|
||||
|
@ -97,6 +97,7 @@ struct blexerreader {
|
||||
size_t len;
|
||||
void *data;
|
||||
breader readf;
|
||||
int cursor;
|
||||
};
|
||||
|
||||
struct blexerbuf {
|
||||
@ -123,7 +124,6 @@ typedef struct blexer {
|
||||
struct blexerreader reader;
|
||||
bmap *strtab;
|
||||
bvm *vm;
|
||||
int cursor;
|
||||
} blexer;
|
||||
|
||||
void be_lexer_init(blexer *lexer, bvm *vm,
|
||||
|
@ -195,7 +195,8 @@ typedef struct {
|
||||
bntvfunc destroy;
|
||||
} bcommomobj;
|
||||
|
||||
typedef const char* (*breader)(void*, size_t*);
|
||||
struct blexer;
|
||||
typedef const char* (*breader)(struct blexer*, void*, size_t*);
|
||||
|
||||
#define cast(_T, _v) ((_T)(_v))
|
||||
#define cast_int(_v) cast(int, _v)
|
||||
|
@ -52,22 +52,22 @@ bstring* be_num2str(bvm *vm, bvalue *v)
|
||||
{
|
||||
char buf[25];
|
||||
if (var_isint(v)) {
|
||||
sprintf(buf, BE_INT_FORMAT, var_toint(v));
|
||||
snprintf(buf, sizeof(buf),BE_INT_FORMAT, var_toint(v));
|
||||
} else if (var_isreal(v)) {
|
||||
sprintf(buf, "%g", var_toreal(v));
|
||||
snprintf(buf, sizeof(buf), "%g", var_toreal(v));
|
||||
} else {
|
||||
sprintf(buf, "(nan)");
|
||||
snprintf(buf, sizeof(buf), "(nan)");
|
||||
}
|
||||
return be_newstr(vm, buf);
|
||||
}
|
||||
|
||||
static void module2str(char *buf, bvalue *v)
|
||||
static void module2str(char *buf, size_t buf_len, bvalue *v)
|
||||
{
|
||||
const char *name = be_module_name(cast(bmodule*, var_toobj(v)));
|
||||
if (name) {
|
||||
sprintf(buf, "<module: %s>", name);
|
||||
snprintf(buf, buf_len, "<module: %s>", name);
|
||||
} else {
|
||||
sprintf(buf, "<module: %p>", var_toobj(v));
|
||||
snprintf(buf, buf_len, "<module: %p>", var_toobj(v));
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,26 +83,26 @@ static bstring* sim2str(bvm *vm, bvalue *v)
|
||||
break;
|
||||
case BE_INDEX:
|
||||
case BE_INT:
|
||||
sprintf(sbuf, BE_INT_FORMAT, var_toint(v));
|
||||
snprintf(sbuf, sizeof(sbuf), BE_INT_FORMAT, var_toint(v));
|
||||
break;
|
||||
case BE_REAL:
|
||||
sprintf(sbuf, "%g", var_toreal(v));
|
||||
snprintf(sbuf, sizeof(sbuf), "%g", var_toreal(v));
|
||||
break;
|
||||
case BE_CLOSURE: case BE_NTVCLOS: case BE_NTVFUNC: case BE_CTYPE_FUNC:
|
||||
sprintf(sbuf, "<function: %p>", var_toobj(v));
|
||||
snprintf(sbuf, sizeof(sbuf), "<function: %p>", var_toobj(v));
|
||||
break;
|
||||
case BE_CLASS:
|
||||
sprintf(sbuf, "<class: %s>",
|
||||
snprintf(sbuf, sizeof(sbuf), "<class: %s>",
|
||||
str(be_class_name(cast(bclass*, var_toobj(v)))));
|
||||
break;
|
||||
case BE_MODULE:
|
||||
module2str(sbuf, v);
|
||||
module2str(sbuf, sizeof(sbuf), v);
|
||||
break;
|
||||
case BE_COMPTR:
|
||||
sprintf(sbuf, "<ptr: %p>", var_toobj(v));
|
||||
snprintf(sbuf, sizeof(sbuf), "<ptr: %p>", var_toobj(v));
|
||||
break;
|
||||
default:
|
||||
strcpy(sbuf, "(unknown value)");
|
||||
strncpy(sbuf, "(unknown value)", sizeof(sbuf));
|
||||
break;
|
||||
}
|
||||
return be_newstr(vm, sbuf);
|
||||
@ -117,8 +117,10 @@ static bstring* ins2str(bvm *vm, int idx)
|
||||
be_incrtop(vm); /* push the obj::tostring to stack */
|
||||
if (basetype(type) != BE_FUNCTION) {
|
||||
bstring *name = be_class_name(be_instance_class(obj));
|
||||
char *sbuf = be_malloc(vm, (size_t)str_len(name) + 16);
|
||||
sprintf(sbuf, "<instance: %s()>", str(name));
|
||||
size_t buf_len = (size_t) str_len(name) + 16;
|
||||
char *sbuf = be_malloc(vm, buf_len);
|
||||
/* TODO what if sbuf cannot be allocated */
|
||||
snprintf(sbuf, buf_len, "<instance: %s()>", str(name));
|
||||
be_stackpop(vm, 1); /* pop the obj::tostring */
|
||||
s = be_newstr(vm, sbuf);
|
||||
be_free(vm, sbuf, (size_t)str_len(name) + 16);
|
||||
@ -217,7 +219,7 @@ const char* be_pushvfstr(bvm *vm, const char *format, va_list arg)
|
||||
}
|
||||
case 'p': {
|
||||
char buf[2 * sizeof(void*) + 4];
|
||||
sprintf(buf, "%p", va_arg(arg, void*));
|
||||
snprintf(buf, sizeof(buf), "%p", va_arg(arg, void*));
|
||||
pushstr(vm, buf, strlen(buf));
|
||||
break;
|
||||
}
|
||||
@ -632,7 +634,7 @@ int be_str_format(bvm *vm)
|
||||
bint val;
|
||||
if (convert_to_int(vm, index, &val)) {
|
||||
mode_fixlen(mode, BE_INT_FMTLEN);
|
||||
sprintf(buf, mode, val);
|
||||
snprintf(buf, sizeof(buf), mode, val);
|
||||
}
|
||||
be_pushstring(vm, buf);
|
||||
break;
|
||||
@ -642,7 +644,7 @@ int be_str_format(bvm *vm)
|
||||
{
|
||||
breal val;
|
||||
if (convert_to_real(vm, index, &val)) {
|
||||
sprintf(buf, mode, val);
|
||||
snprintf(buf, sizeof(buf), mode, val);
|
||||
}
|
||||
be_pushstring(vm, buf);
|
||||
break;
|
||||
@ -651,7 +653,7 @@ int be_str_format(bvm *vm)
|
||||
{
|
||||
bint val;
|
||||
if (convert_to_int(vm, index, &val)) {
|
||||
sprintf(buf, "%c", (int)val);
|
||||
snprintf(buf, sizeof(buf), "%c", (int)val);
|
||||
}
|
||||
be_pushstring(vm, buf);
|
||||
break;
|
||||
@ -662,7 +664,7 @@ int be_str_format(bvm *vm)
|
||||
if (len > 100 && strlen(mode) == 2) {
|
||||
be_pushvalue(vm, index);
|
||||
} else {
|
||||
sprintf(buf, mode, s);
|
||||
snprintf(buf, sizeof(buf), mode, s);
|
||||
be_pushstring(vm, buf);
|
||||
}
|
||||
break;
|
||||
@ -810,10 +812,10 @@ static int str_i2hex(bvm *vm)
|
||||
if (top >= 2 && be_isint(vm, 2)) {
|
||||
bint num = be_toint(vm, 2);
|
||||
if (num > 0 && num <= 16) {
|
||||
sprintf(fmt, "%%.%d" BE_INT_FMTLEN "X", (int)num);
|
||||
snprintf(fmt, sizeof(fmt), "%%.%d" BE_INT_FMTLEN "X", (int)num);
|
||||
}
|
||||
}
|
||||
sprintf(buf, fmt, value);
|
||||
snprintf(buf, sizeof(buf), fmt, value);
|
||||
be_pushstring(vm, buf);
|
||||
be_return(vm);
|
||||
}
|
||||
|
@ -89,7 +89,6 @@ class Matter_UI
|
||||
#- ---------------------------------------------------------------------- -#
|
||||
def show_enable()
|
||||
import webserver
|
||||
import string
|
||||
var matter_enabled = self.matter_enabled
|
||||
|
||||
webserver.content_send("<fieldset><legend><b> Matter </b></legend>"
|
||||
@ -97,12 +96,14 @@ class Matter_UI
|
||||
"<form action='/matterc' method='post'>")
|
||||
|
||||
# checkbox for Matter enable
|
||||
webserver.content_send(string.format("<p><input id='menable' type='checkbox' name='menable' %s>", self.matter_enabled() ? "checked" : ""))
|
||||
var matter_enabled_checked = self.matter_enabled() ? 'checked' : ''
|
||||
webserver.content_send(f"<p><input id='menable' type='checkbox' name='menable' {matter_enabled_checked}>")
|
||||
webserver.content_send("<label for='menable'><b>Matter enable</b></label></p>")
|
||||
|
||||
if self.matter_enabled()
|
||||
# checkbox for Matter commissioning
|
||||
webserver.content_send(string.format("<p><input id='comm' type='checkbox' name='comm' %s>", self.device.commissioning_open != nil ? "checked" : ""))
|
||||
var commissioning_open_checked = self.device.commissioning_open != nil ? "checked" : ""
|
||||
webserver.content_send(f"<p><input id='comm' type='checkbox' name='comm' {commissioning_open_checked}>")
|
||||
webserver.content_send("<label for='comm'><b>Commissioning open</b></label></p>")
|
||||
end
|
||||
|
||||
@ -163,21 +164,20 @@ class Matter_UI
|
||||
#- ---------------------------------------------------------------------- -#
|
||||
def show_commissioning_info()
|
||||
import webserver
|
||||
import string
|
||||
|
||||
var seconds_left = (self.device.commissioning_open - tasmota.millis()) / 1000
|
||||
if seconds_left < 0 seconds_left = 0 end
|
||||
var min_left = (seconds_left + 30) / 60
|
||||
|
||||
webserver.content_send(string.format("<fieldset><legend><b> Commissioning open for %i min </b></legend><p></p>", min_left))
|
||||
webserver.content_send(f"<fieldset><legend><b> Commissioning open for {min_left:i} min </b></legend><p></p>")
|
||||
|
||||
var pairing_code = self.device.compute_manual_pairing_code()
|
||||
webserver.content_send(string.format("<p>Manual pairing code:<br><b>%s-%s-%s</b></p><hr>", pairing_code[0..3], pairing_code[4..6], pairing_code[7..]))
|
||||
webserver.content_send(f"<p>Manual pairing code:<br><b>{pairing_code[0..3]}-{pairing_code[4..6]}-{pairing_code[7..]}</b></p><hr>")
|
||||
|
||||
webserver.content_send("<div><center>")
|
||||
var qr_text = self.device.compute_qrcode_content()
|
||||
self.show_qrcode(qr_text)
|
||||
webserver.content_send(string.format("<p> %s</p>", qr_text))
|
||||
webserver.content_send(f"<p> {qr_text}</p>")
|
||||
webserver.content_send("</div><p></p></fieldset><p></p>")
|
||||
|
||||
end
|
||||
@ -187,16 +187,16 @@ class Matter_UI
|
||||
#- ---------------------------------------------------------------------- -#
|
||||
def show_passcode_form()
|
||||
import webserver
|
||||
import string
|
||||
|
||||
webserver.content_send("<fieldset><legend><b> Matter Advanced Configuration </b></legend><p></p>")
|
||||
#
|
||||
webserver.content_send("<form action='/matterc' method='post' onsubmit='return confirm(\"This will cause a restart.\");'>"
|
||||
"<p>Passcode:</p>")
|
||||
webserver.content_send(string.format("<input type='number' min='1' max='99999998' name='passcode' value='%i'>", self.device.root_passcode))
|
||||
webserver.content_send(f"<input type='number' min='1' max='99999998' name='passcode' value='{self.device.root_passcode:i}'>")
|
||||
webserver.content_send("<p>Distinguish id:</p>")
|
||||
webserver.content_send(string.format("<input type='number' min='0' max='4095' name='discriminator' value='%i'>", self.device.root_discriminator))
|
||||
webserver.content_send(string.format("<p><input type='checkbox' name='ipv4'%s>IPv4 only</p>", self.device.ipv4only ? " checked" : ""))
|
||||
webserver.content_send(f"<input type='number' min='0' max='4095' name='discriminator' value='{self.device.root_discriminator:i}'>")
|
||||
var ipv4only_checked = self.device.ipv4only ? " checked" : ""
|
||||
webserver.content_send(f"<p><input type='checkbox' name='ipv4'{ipv4only_checked}>IPv4 only</p>")
|
||||
webserver.content_send("<p></p><button name='passcode' class='button bgrn'>Change</button></form></p>"
|
||||
"<p></p></fieldset><p></p>")
|
||||
|
||||
@ -224,15 +224,15 @@ class Matter_UI
|
||||
if !label label = "<No label>" end
|
||||
label = webserver.html_escape(label) # protect against HTML injection
|
||||
|
||||
webserver.content_send(string.format("<fieldset><legend><b> #%i %s</b> (%s) </legend><p></p>", f.get_fabric_index(), label, f.get_admin_vendor_name()))
|
||||
webserver.content_send(f"<fieldset><legend><b> #{f.get_fabric_index():i} {label}</b> ({f.get_admin_vendor_name()}) </legend><p></p>")
|
||||
|
||||
var fabric_rev = f.get_fabric_id().copy().reverse()
|
||||
var deviceid_rev = f.get_device_id().copy().reverse()
|
||||
webserver.content_send(string.format("Fabric: %s<br>", fabric_rev.tohex()))
|
||||
webserver.content_send(string.format("Device: %s<br> ", deviceid_rev.tohex()))
|
||||
webserver.content_send(f"Fabric: {fabric_rev.tohex()}<br>")
|
||||
webserver.content_send(f"Device: {deviceid_rev.tohex()}<br> ")
|
||||
|
||||
webserver.content_send("<form action='/matterc' method='post' onsubmit='return confirm(\"Are you sure?\");'>")
|
||||
webserver.content_send(string.format("<input name='del_fabric' type='hidden' value='%i'>", f.get_fabric_index()))
|
||||
webserver.content_send(f"<input name='del_fabric' type='hidden' value='{f.get_fabric_index():i}'>")
|
||||
webserver.content_send("<button name='del' class='button bgrn'>Delete Fabric</button></form></p>")
|
||||
|
||||
webserver.content_send("<p></p></fieldset><p></p>")
|
||||
@ -252,8 +252,8 @@ class Matter_UI
|
||||
# var hl = ["Enter Relay number","Not used","Enter Filter pattern","Enter Switch number"];
|
||||
def show_plugins_hints_js(*class_list)
|
||||
import webserver
|
||||
import string
|
||||
import json
|
||||
import string
|
||||
|
||||
var class_types = []
|
||||
for cl: class_list
|
||||
@ -279,11 +279,11 @@ class Matter_UI
|
||||
end
|
||||
end
|
||||
|
||||
webserver.content_send(string.format(
|
||||
webserver.content_send(f""
|
||||
"<script type='text/javascript'>"
|
||||
"var hm=%s;"
|
||||
"var hl=%s;"
|
||||
"</script>", json.dump(hm), json.dump(hl)))
|
||||
"var hm={json.dump(hm)};"
|
||||
"var hl={json.dump(hl)};"
|
||||
"</script>")
|
||||
|
||||
webserver.content_send(matter._ADD_ENDPOINT_JS)
|
||||
|
||||
@ -332,17 +332,17 @@ class Matter_UI
|
||||
end
|
||||
|
||||
found = true
|
||||
webserver.content_send(string.format("<tr><td style='font-size:smaller;'><b>%i</b></td>", ep))
|
||||
webserver.content_send(string.format("<td style='font-size:smaller;'><input type='text' name='nam%i' size='1' value='%s'></td>",
|
||||
webserver.content_send(f"<tr><td style='font-size:smaller;'><b>{ep:i}</b></td>")
|
||||
webserver.content_send(format("<td style='font-size:smaller;'><input type='text' name='nam%i' size='1' value='%s'></td>",
|
||||
ep, webserver.html_escape(conf.find('name', ''))))
|
||||
webserver.content_send(string.format("<td style='font-size:smaller;'><b>%s</b></td>", self.plugin_name(conf.find('type', ''))))
|
||||
webserver.content_send(string.format("<td style='font-size:smaller;'><input type='text' name='arg%i' size='1' value='%s' placeholder='%s'></td>",
|
||||
webserver.content_send(f"<td style='font-size:smaller;'><b>{self.plugin_name(conf.find('type', ''))}</b></td>")
|
||||
webserver.content_send(format("<td style='font-size:smaller;'><input type='text' name='arg%i' size='1' value='%s' placeholder='%s'></td>",
|
||||
ep, webserver.html_escape(arg), cl ? webserver.html_escape(cl.ARG_HINT) : ''))
|
||||
webserver.content_send(string.format("<td style='text-align:center;'><button name='del%i' "
|
||||
webserver.content_send(f"<td style='text-align:center;'><button name='del{ep:i}' "
|
||||
"style='background:none;border:none;line-height:1;'"
|
||||
" onclick=\"return confirm('Confirm removing endpoint')\""
|
||||
">"
|
||||
"🔥</button></td></tr>", ep))
|
||||
"🔥</button></td></tr>")
|
||||
i += 1
|
||||
end
|
||||
webserver.content_send("</table>")
|
||||
@ -366,7 +366,8 @@ class Matter_UI
|
||||
|
||||
for remote: remotes
|
||||
|
||||
webserver.content_send(string.format("🔗 <a target='_blank' href=\"http://%s/?\">%s</a>", webserver.html_escape(remote), webserver.html_escape(remote)))
|
||||
var remote_html = webserver.html_escape(remote)
|
||||
webserver.content_send(f"🔗 <a target='_blank' href=\"http://{remote_html}/?\">{remote_html}</a>")
|
||||
webserver.content_send("<table style='width:100%'>")
|
||||
webserver.content_send("<tr>"
|
||||
"<td width='25'></td>"
|
||||
@ -396,18 +397,18 @@ class Matter_UI
|
||||
end
|
||||
|
||||
found = true
|
||||
webserver.content_send(string.format("<tr><td width='22' style='font-size:smaller;'><b>%i</b></td>", ep))
|
||||
webserver.content_send(string.format("<td width='78' style='font-size:smaller;'><input type='text' name='nam%i' size='1' value='%s' placeholder='(optional)'></td>",
|
||||
webserver.content_send(f"<tr><td width='22' style='font-size:smaller;'><b>{ep:i}</b></td>")
|
||||
webserver.content_send(format("<td width='78' style='font-size:smaller;'><input type='text' name='nam%i' size='1' value='%s' placeholder='(optional)'></td>",
|
||||
ep, webserver.html_escape(conf.find('name', ''))))
|
||||
|
||||
webserver.content_send(string.format("<td width='115' style='font-size:smaller;'><b>%s</b></select></td>", self.plugin_name(conf.find('type', ''))))
|
||||
webserver.content_send(string.format("<td style='font-size:smaller;'><input type='text' name='arg%i' size='8' value='%s'></td>",
|
||||
webserver.content_send(format("<td width='115' style='font-size:smaller;'><b>%s</b></select></td>", self.plugin_name(conf.find('type', ''))))
|
||||
webserver.content_send(format("<td style='font-size:smaller;'><input type='text' name='arg%i' size='8' value='%s'></td>",
|
||||
ep, webserver.html_escape(arg)))
|
||||
webserver.content_send(string.format("<td width='15' style='text-align:center;'><button name='del%i' "
|
||||
webserver.content_send(f"<td width='15' style='text-align:center;'><button name='del{ep:i}' "
|
||||
"style='background:none;border:none;line-height:1;'"
|
||||
" onclick=\"return confirm('Confirm removing endpoint')\""
|
||||
">"
|
||||
"🔥</button></td></tr>", ep))
|
||||
"🔥</button></td></tr>")
|
||||
i += 1
|
||||
end
|
||||
webserver.content_send("</table><p></p>")
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user