New modules.
Some checks are pending
WrenCI / linux (push) Waiting to run
WrenCI / mac (push) Waiting to run
WrenCI / windows (push) Waiting to run

This commit is contained in:
retoor 2026-01-24 19:35:43 +01:00
parent 1a08b3adf0
commit 236d69c2d7
118 changed files with 315687 additions and 6 deletions

1151
deps/cjson/cJSON.c vendored Normal file

File diff suppressed because it is too large Load Diff

162
deps/cjson/cJSON.h vendored Normal file
View File

@ -0,0 +1,162 @@
// retoor <retoor@molodetz.nl>
/*
Copyright (c) 2009-2017 Dave Gamble and cJSON contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#ifndef cJSON__h
#define cJSON__h
#ifdef __cplusplus
extern "C"
{
#endif
#include <stddef.h>
#define cJSON_Invalid (0)
#define cJSON_False (1 << 0)
#define cJSON_True (1 << 1)
#define cJSON_NULL (1 << 2)
#define cJSON_Number (1 << 3)
#define cJSON_String (1 << 4)
#define cJSON_Array (1 << 5)
#define cJSON_Object (1 << 6)
#define cJSON_Raw (1 << 7)
#define cJSON_IsReference 256
#define cJSON_StringIsConst 512
typedef struct cJSON
{
struct cJSON *next;
struct cJSON *prev;
struct cJSON *child;
int type;
char *valuestring;
int valueint;
double valuedouble;
char *string;
} cJSON;
typedef struct cJSON_Hooks
{
void *(*malloc_fn)(size_t sz);
void (*free_fn)(void *ptr);
} cJSON_Hooks;
extern void cJSON_InitHooks(cJSON_Hooks* hooks);
extern cJSON *cJSON_Parse(const char *value);
extern cJSON *cJSON_ParseWithLength(const char *value, size_t buffer_length);
extern char *cJSON_Print(const cJSON *item);
extern char *cJSON_PrintUnformatted(const cJSON *item);
extern char *cJSON_PrintBuffered(const cJSON *item, int prebuffer, int fmt);
extern void cJSON_Delete(cJSON *item);
extern int cJSON_GetArraySize(const cJSON *array);
extern cJSON *cJSON_GetArrayItem(const cJSON *array, int index);
extern cJSON *cJSON_GetObjectItem(const cJSON *object, const char *string);
extern cJSON *cJSON_GetObjectItemCaseSensitive(const cJSON *object, const char *string);
extern int cJSON_HasObjectItem(const cJSON *object, const char *string);
extern const char *cJSON_GetErrorPtr(void);
extern char *cJSON_GetStringValue(const cJSON *item);
extern double cJSON_GetNumberValue(const cJSON *item);
extern int cJSON_IsInvalid(const cJSON *item);
extern int cJSON_IsFalse(const cJSON *item);
extern int cJSON_IsTrue(const cJSON *item);
extern int cJSON_IsBool(const cJSON *item);
extern int cJSON_IsNull(const cJSON *item);
extern int cJSON_IsNumber(const cJSON *item);
extern int cJSON_IsString(const cJSON *item);
extern int cJSON_IsArray(const cJSON *item);
extern int cJSON_IsObject(const cJSON *item);
extern int cJSON_IsRaw(const cJSON *item);
extern cJSON *cJSON_CreateNull(void);
extern cJSON *cJSON_CreateTrue(void);
extern cJSON *cJSON_CreateFalse(void);
extern cJSON *cJSON_CreateBool(int boolean);
extern cJSON *cJSON_CreateNumber(double num);
extern cJSON *cJSON_CreateString(const char *string);
extern cJSON *cJSON_CreateRaw(const char *raw);
extern cJSON *cJSON_CreateArray(void);
extern cJSON *cJSON_CreateObject(void);
extern cJSON *cJSON_CreateStringReference(const char *string);
extern cJSON *cJSON_CreateObjectReference(const cJSON *child);
extern cJSON *cJSON_CreateArrayReference(const cJSON *child);
extern cJSON *cJSON_CreateIntArray(const int *numbers, int count);
extern cJSON *cJSON_CreateFloatArray(const float *numbers, int count);
extern cJSON *cJSON_CreateDoubleArray(const double *numbers, int count);
extern cJSON *cJSON_CreateStringArray(const char *const *strings, int count);
extern void cJSON_AddItemToArray(cJSON *array, cJSON *item);
extern void cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
extern void cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item);
extern void cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item);
extern void cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item);
extern cJSON *cJSON_DetachItemViaPointer(cJSON *parent, cJSON *item);
extern cJSON *cJSON_DetachItemFromArray(cJSON *array, int which);
extern void cJSON_DeleteItemFromArray(cJSON *array, int which);
extern cJSON *cJSON_DetachItemFromObject(cJSON *object, const char *string);
extern cJSON *cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string);
extern void cJSON_DeleteItemFromObject(cJSON *object, const char *string);
extern void cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string);
extern int cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem);
extern int cJSON_ReplaceItemViaPointer(cJSON *parent, cJSON *item, cJSON *replacement);
extern int cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem);
extern int cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem);
extern int cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem);
extern cJSON *cJSON_Duplicate(const cJSON *item, int recurse);
extern int cJSON_Compare(const cJSON *a, const cJSON *b, int case_sensitive);
extern void cJSON_Minify(char *json);
#define cJSON_AddNullToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateNull())
#define cJSON_AddTrueToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateTrue())
#define cJSON_AddFalseToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateFalse())
#define cJSON_AddBoolToObject(object,name,b) cJSON_AddItemToObject(object, name, cJSON_CreateBool(b))
#define cJSON_AddNumberToObject(object,name,n) cJSON_AddItemToObject(object, name, cJSON_CreateNumber(n))
#define cJSON_AddStringToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateString(s))
#define cJSON_AddRawToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateRaw(s))
#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number))
#define cJSON_SetNumberValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number))
#define cJSON_SetValuestring(object, string) ((object) ? (object)->valuestring = (string) : (string))
#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next)
extern void *cJSON_malloc(size_t size);
extern void cJSON_free(void *object);
#ifdef __cplusplus
}
#endif
#endif

29636
deps/sqlite/shell.c vendored Normal file

File diff suppressed because it is too large Load Diff

255636
deps/sqlite/sqlite3.c vendored Normal file

File diff suppressed because it is too large Load Diff

13355
deps/sqlite/sqlite3.h vendored Normal file

File diff suppressed because it is too large Load Diff

719
deps/sqlite/sqlite3ext.h vendored Normal file
View File

@ -0,0 +1,719 @@
/*
** 2006 June 7
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
** This header file defines the SQLite interface for use by
** shared libraries that want to be imported as extensions into
** an SQLite instance. Shared libraries that intend to be loaded
** as extensions by SQLite should #include this file instead of
** sqlite3.h.
*/
#ifndef SQLITE3EXT_H
#define SQLITE3EXT_H
#include "sqlite3.h"
/*
** The following structure holds pointers to all of the SQLite API
** routines.
**
** WARNING: In order to maintain backwards compatibility, add new
** interfaces to the end of this structure only. If you insert new
** interfaces in the middle of this structure, then older different
** versions of SQLite will not be able to load each other's shared
** libraries!
*/
struct sqlite3_api_routines {
void * (*aggregate_context)(sqlite3_context*,int nBytes);
int (*aggregate_count)(sqlite3_context*);
int (*bind_blob)(sqlite3_stmt*,int,const void*,int n,void(*)(void*));
int (*bind_double)(sqlite3_stmt*,int,double);
int (*bind_int)(sqlite3_stmt*,int,int);
int (*bind_int64)(sqlite3_stmt*,int,sqlite_int64);
int (*bind_null)(sqlite3_stmt*,int);
int (*bind_parameter_count)(sqlite3_stmt*);
int (*bind_parameter_index)(sqlite3_stmt*,const char*zName);
const char * (*bind_parameter_name)(sqlite3_stmt*,int);
int (*bind_text)(sqlite3_stmt*,int,const char*,int n,void(*)(void*));
int (*bind_text16)(sqlite3_stmt*,int,const void*,int,void(*)(void*));
int (*bind_value)(sqlite3_stmt*,int,const sqlite3_value*);
int (*busy_handler)(sqlite3*,int(*)(void*,int),void*);
int (*busy_timeout)(sqlite3*,int ms);
int (*changes)(sqlite3*);
int (*close)(sqlite3*);
int (*collation_needed)(sqlite3*,void*,void(*)(void*,sqlite3*,
int eTextRep,const char*));
int (*collation_needed16)(sqlite3*,void*,void(*)(void*,sqlite3*,
int eTextRep,const void*));
const void * (*column_blob)(sqlite3_stmt*,int iCol);
int (*column_bytes)(sqlite3_stmt*,int iCol);
int (*column_bytes16)(sqlite3_stmt*,int iCol);
int (*column_count)(sqlite3_stmt*pStmt);
const char * (*column_database_name)(sqlite3_stmt*,int);
const void * (*column_database_name16)(sqlite3_stmt*,int);
const char * (*column_decltype)(sqlite3_stmt*,int i);
const void * (*column_decltype16)(sqlite3_stmt*,int);
double (*column_double)(sqlite3_stmt*,int iCol);
int (*column_int)(sqlite3_stmt*,int iCol);
sqlite_int64 (*column_int64)(sqlite3_stmt*,int iCol);
const char * (*column_name)(sqlite3_stmt*,int);
const void * (*column_name16)(sqlite3_stmt*,int);
const char * (*column_origin_name)(sqlite3_stmt*,int);
const void * (*column_origin_name16)(sqlite3_stmt*,int);
const char * (*column_table_name)(sqlite3_stmt*,int);
const void * (*column_table_name16)(sqlite3_stmt*,int);
const unsigned char * (*column_text)(sqlite3_stmt*,int iCol);
const void * (*column_text16)(sqlite3_stmt*,int iCol);
int (*column_type)(sqlite3_stmt*,int iCol);
sqlite3_value* (*column_value)(sqlite3_stmt*,int iCol);
void * (*commit_hook)(sqlite3*,int(*)(void*),void*);
int (*complete)(const char*sql);
int (*complete16)(const void*sql);
int (*create_collation)(sqlite3*,const char*,int,void*,
int(*)(void*,int,const void*,int,const void*));
int (*create_collation16)(sqlite3*,const void*,int,void*,
int(*)(void*,int,const void*,int,const void*));
int (*create_function)(sqlite3*,const char*,int,int,void*,
void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
void (*xStep)(sqlite3_context*,int,sqlite3_value**),
void (*xFinal)(sqlite3_context*));
int (*create_function16)(sqlite3*,const void*,int,int,void*,
void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
void (*xStep)(sqlite3_context*,int,sqlite3_value**),
void (*xFinal)(sqlite3_context*));
int (*create_module)(sqlite3*,const char*,const sqlite3_module*,void*);
int (*data_count)(sqlite3_stmt*pStmt);
sqlite3 * (*db_handle)(sqlite3_stmt*);
int (*declare_vtab)(sqlite3*,const char*);
int (*enable_shared_cache)(int);
int (*errcode)(sqlite3*db);
const char * (*errmsg)(sqlite3*);
const void * (*errmsg16)(sqlite3*);
int (*exec)(sqlite3*,const char*,sqlite3_callback,void*,char**);
int (*expired)(sqlite3_stmt*);
int (*finalize)(sqlite3_stmt*pStmt);
void (*free)(void*);
void (*free_table)(char**result);
int (*get_autocommit)(sqlite3*);
void * (*get_auxdata)(sqlite3_context*,int);
int (*get_table)(sqlite3*,const char*,char***,int*,int*,char**);
int (*global_recover)(void);
void (*interruptx)(sqlite3*);
sqlite_int64 (*last_insert_rowid)(sqlite3*);
const char * (*libversion)(void);
int (*libversion_number)(void);
void *(*malloc)(int);
char * (*mprintf)(const char*,...);
int (*open)(const char*,sqlite3**);
int (*open16)(const void*,sqlite3**);
int (*prepare)(sqlite3*,const char*,int,sqlite3_stmt**,const char**);
int (*prepare16)(sqlite3*,const void*,int,sqlite3_stmt**,const void**);
void * (*profile)(sqlite3*,void(*)(void*,const char*,sqlite_uint64),void*);
void (*progress_handler)(sqlite3*,int,int(*)(void*),void*);
void *(*realloc)(void*,int);
int (*reset)(sqlite3_stmt*pStmt);
void (*result_blob)(sqlite3_context*,const void*,int,void(*)(void*));
void (*result_double)(sqlite3_context*,double);
void (*result_error)(sqlite3_context*,const char*,int);
void (*result_error16)(sqlite3_context*,const void*,int);
void (*result_int)(sqlite3_context*,int);
void (*result_int64)(sqlite3_context*,sqlite_int64);
void (*result_null)(sqlite3_context*);
void (*result_text)(sqlite3_context*,const char*,int,void(*)(void*));
void (*result_text16)(sqlite3_context*,const void*,int,void(*)(void*));
void (*result_text16be)(sqlite3_context*,const void*,int,void(*)(void*));
void (*result_text16le)(sqlite3_context*,const void*,int,void(*)(void*));
void (*result_value)(sqlite3_context*,sqlite3_value*);
void * (*rollback_hook)(sqlite3*,void(*)(void*),void*);
int (*set_authorizer)(sqlite3*,int(*)(void*,int,const char*,const char*,
const char*,const char*),void*);
void (*set_auxdata)(sqlite3_context*,int,void*,void (*)(void*));
char * (*xsnprintf)(int,char*,const char*,...);
int (*step)(sqlite3_stmt*);
int (*table_column_metadata)(sqlite3*,const char*,const char*,const char*,
char const**,char const**,int*,int*,int*);
void (*thread_cleanup)(void);
int (*total_changes)(sqlite3*);
void * (*trace)(sqlite3*,void(*xTrace)(void*,const char*),void*);
int (*transfer_bindings)(sqlite3_stmt*,sqlite3_stmt*);
void * (*update_hook)(sqlite3*,void(*)(void*,int ,char const*,char const*,
sqlite_int64),void*);
void * (*user_data)(sqlite3_context*);
const void * (*value_blob)(sqlite3_value*);
int (*value_bytes)(sqlite3_value*);
int (*value_bytes16)(sqlite3_value*);
double (*value_double)(sqlite3_value*);
int (*value_int)(sqlite3_value*);
sqlite_int64 (*value_int64)(sqlite3_value*);
int (*value_numeric_type)(sqlite3_value*);
const unsigned char * (*value_text)(sqlite3_value*);
const void * (*value_text16)(sqlite3_value*);
const void * (*value_text16be)(sqlite3_value*);
const void * (*value_text16le)(sqlite3_value*);
int (*value_type)(sqlite3_value*);
char *(*vmprintf)(const char*,va_list);
/* Added ??? */
int (*overload_function)(sqlite3*, const char *zFuncName, int nArg);
/* Added by 3.3.13 */
int (*prepare_v2)(sqlite3*,const char*,int,sqlite3_stmt**,const char**);
int (*prepare16_v2)(sqlite3*,const void*,int,sqlite3_stmt**,const void**);
int (*clear_bindings)(sqlite3_stmt*);
/* Added by 3.4.1 */
int (*create_module_v2)(sqlite3*,const char*,const sqlite3_module*,void*,
void (*xDestroy)(void *));
/* Added by 3.5.0 */
int (*bind_zeroblob)(sqlite3_stmt*,int,int);
int (*blob_bytes)(sqlite3_blob*);
int (*blob_close)(sqlite3_blob*);
int (*blob_open)(sqlite3*,const char*,const char*,const char*,sqlite3_int64,
int,sqlite3_blob**);
int (*blob_read)(sqlite3_blob*,void*,int,int);
int (*blob_write)(sqlite3_blob*,const void*,int,int);
int (*create_collation_v2)(sqlite3*,const char*,int,void*,
int(*)(void*,int,const void*,int,const void*),
void(*)(void*));
int (*file_control)(sqlite3*,const char*,int,void*);
sqlite3_int64 (*memory_highwater)(int);
sqlite3_int64 (*memory_used)(void);
sqlite3_mutex *(*mutex_alloc)(int);
void (*mutex_enter)(sqlite3_mutex*);
void (*mutex_free)(sqlite3_mutex*);
void (*mutex_leave)(sqlite3_mutex*);
int (*mutex_try)(sqlite3_mutex*);
int (*open_v2)(const char*,sqlite3**,int,const char*);
int (*release_memory)(int);
void (*result_error_nomem)(sqlite3_context*);
void (*result_error_toobig)(sqlite3_context*);
int (*sleep)(int);
void (*soft_heap_limit)(int);
sqlite3_vfs *(*vfs_find)(const char*);
int (*vfs_register)(sqlite3_vfs*,int);
int (*vfs_unregister)(sqlite3_vfs*);
int (*xthreadsafe)(void);
void (*result_zeroblob)(sqlite3_context*,int);
void (*result_error_code)(sqlite3_context*,int);
int (*test_control)(int, ...);
void (*randomness)(int,void*);
sqlite3 *(*context_db_handle)(sqlite3_context*);
int (*extended_result_codes)(sqlite3*,int);
int (*limit)(sqlite3*,int,int);
sqlite3_stmt *(*next_stmt)(sqlite3*,sqlite3_stmt*);
const char *(*sql)(sqlite3_stmt*);
int (*status)(int,int*,int*,int);
int (*backup_finish)(sqlite3_backup*);
sqlite3_backup *(*backup_init)(sqlite3*,const char*,sqlite3*,const char*);
int (*backup_pagecount)(sqlite3_backup*);
int (*backup_remaining)(sqlite3_backup*);
int (*backup_step)(sqlite3_backup*,int);
const char *(*compileoption_get)(int);
int (*compileoption_used)(const char*);
int (*create_function_v2)(sqlite3*,const char*,int,int,void*,
void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
void (*xStep)(sqlite3_context*,int,sqlite3_value**),
void (*xFinal)(sqlite3_context*),
void(*xDestroy)(void*));
int (*db_config)(sqlite3*,int,...);
sqlite3_mutex *(*db_mutex)(sqlite3*);
int (*db_status)(sqlite3*,int,int*,int*,int);
int (*extended_errcode)(sqlite3*);
void (*log)(int,const char*,...);
sqlite3_int64 (*soft_heap_limit64)(sqlite3_int64);
const char *(*sourceid)(void);
int (*stmt_status)(sqlite3_stmt*,int,int);
int (*strnicmp)(const char*,const char*,int);
int (*unlock_notify)(sqlite3*,void(*)(void**,int),void*);
int (*wal_autocheckpoint)(sqlite3*,int);
int (*wal_checkpoint)(sqlite3*,const char*);
void *(*wal_hook)(sqlite3*,int(*)(void*,sqlite3*,const char*,int),void*);
int (*blob_reopen)(sqlite3_blob*,sqlite3_int64);
int (*vtab_config)(sqlite3*,int op,...);
int (*vtab_on_conflict)(sqlite3*);
/* Version 3.7.16 and later */
int (*close_v2)(sqlite3*);
const char *(*db_filename)(sqlite3*,const char*);
int (*db_readonly)(sqlite3*,const char*);
int (*db_release_memory)(sqlite3*);
const char *(*errstr)(int);
int (*stmt_busy)(sqlite3_stmt*);
int (*stmt_readonly)(sqlite3_stmt*);
int (*stricmp)(const char*,const char*);
int (*uri_boolean)(const char*,const char*,int);
sqlite3_int64 (*uri_int64)(const char*,const char*,sqlite3_int64);
const char *(*uri_parameter)(const char*,const char*);
char *(*xvsnprintf)(int,char*,const char*,va_list);
int (*wal_checkpoint_v2)(sqlite3*,const char*,int,int*,int*);
/* Version 3.8.7 and later */
int (*auto_extension)(void(*)(void));
int (*bind_blob64)(sqlite3_stmt*,int,const void*,sqlite3_uint64,
void(*)(void*));
int (*bind_text64)(sqlite3_stmt*,int,const char*,sqlite3_uint64,
void(*)(void*),unsigned char);
int (*cancel_auto_extension)(void(*)(void));
int (*load_extension)(sqlite3*,const char*,const char*,char**);
void *(*malloc64)(sqlite3_uint64);
sqlite3_uint64 (*msize)(void*);
void *(*realloc64)(void*,sqlite3_uint64);
void (*reset_auto_extension)(void);
void (*result_blob64)(sqlite3_context*,const void*,sqlite3_uint64,
void(*)(void*));
void (*result_text64)(sqlite3_context*,const char*,sqlite3_uint64,
void(*)(void*), unsigned char);
int (*strglob)(const char*,const char*);
/* Version 3.8.11 and later */
sqlite3_value *(*value_dup)(const sqlite3_value*);
void (*value_free)(sqlite3_value*);
int (*result_zeroblob64)(sqlite3_context*,sqlite3_uint64);
int (*bind_zeroblob64)(sqlite3_stmt*, int, sqlite3_uint64);
/* Version 3.9.0 and later */
unsigned int (*value_subtype)(sqlite3_value*);
void (*result_subtype)(sqlite3_context*,unsigned int);
/* Version 3.10.0 and later */
int (*status64)(int,sqlite3_int64*,sqlite3_int64*,int);
int (*strlike)(const char*,const char*,unsigned int);
int (*db_cacheflush)(sqlite3*);
/* Version 3.12.0 and later */
int (*system_errno)(sqlite3*);
/* Version 3.14.0 and later */
int (*trace_v2)(sqlite3*,unsigned,int(*)(unsigned,void*,void*,void*),void*);
char *(*expanded_sql)(sqlite3_stmt*);
/* Version 3.18.0 and later */
void (*set_last_insert_rowid)(sqlite3*,sqlite3_int64);
/* Version 3.20.0 and later */
int (*prepare_v3)(sqlite3*,const char*,int,unsigned int,
sqlite3_stmt**,const char**);
int (*prepare16_v3)(sqlite3*,const void*,int,unsigned int,
sqlite3_stmt**,const void**);
int (*bind_pointer)(sqlite3_stmt*,int,void*,const char*,void(*)(void*));
void (*result_pointer)(sqlite3_context*,void*,const char*,void(*)(void*));
void *(*value_pointer)(sqlite3_value*,const char*);
int (*vtab_nochange)(sqlite3_context*);
int (*value_nochange)(sqlite3_value*);
const char *(*vtab_collation)(sqlite3_index_info*,int);
/* Version 3.24.0 and later */
int (*keyword_count)(void);
int (*keyword_name)(int,const char**,int*);
int (*keyword_check)(const char*,int);
sqlite3_str *(*str_new)(sqlite3*);
char *(*str_finish)(sqlite3_str*);
void (*str_appendf)(sqlite3_str*, const char *zFormat, ...);
void (*str_vappendf)(sqlite3_str*, const char *zFormat, va_list);
void (*str_append)(sqlite3_str*, const char *zIn, int N);
void (*str_appendall)(sqlite3_str*, const char *zIn);
void (*str_appendchar)(sqlite3_str*, int N, char C);
void (*str_reset)(sqlite3_str*);
int (*str_errcode)(sqlite3_str*);
int (*str_length)(sqlite3_str*);
char *(*str_value)(sqlite3_str*);
/* Version 3.25.0 and later */
int (*create_window_function)(sqlite3*,const char*,int,int,void*,
void (*xStep)(sqlite3_context*,int,sqlite3_value**),
void (*xFinal)(sqlite3_context*),
void (*xValue)(sqlite3_context*),
void (*xInv)(sqlite3_context*,int,sqlite3_value**),
void(*xDestroy)(void*));
/* Version 3.26.0 and later */
const char *(*normalized_sql)(sqlite3_stmt*);
/* Version 3.28.0 and later */
int (*stmt_isexplain)(sqlite3_stmt*);
int (*value_frombind)(sqlite3_value*);
/* Version 3.30.0 and later */
int (*drop_modules)(sqlite3*,const char**);
/* Version 3.31.0 and later */
sqlite3_int64 (*hard_heap_limit64)(sqlite3_int64);
const char *(*uri_key)(const char*,int);
const char *(*filename_database)(const char*);
const char *(*filename_journal)(const char*);
const char *(*filename_wal)(const char*);
/* Version 3.32.0 and later */
const char *(*create_filename)(const char*,const char*,const char*,
int,const char**);
void (*free_filename)(const char*);
sqlite3_file *(*database_file_object)(const char*);
/* Version 3.34.0 and later */
int (*txn_state)(sqlite3*,const char*);
/* Version 3.36.1 and later */
sqlite3_int64 (*changes64)(sqlite3*);
sqlite3_int64 (*total_changes64)(sqlite3*);
/* Version 3.37.0 and later */
int (*autovacuum_pages)(sqlite3*,
unsigned int(*)(void*,const char*,unsigned int,unsigned int,unsigned int),
void*, void(*)(void*));
/* Version 3.38.0 and later */
int (*error_offset)(sqlite3*);
int (*vtab_rhs_value)(sqlite3_index_info*,int,sqlite3_value**);
int (*vtab_distinct)(sqlite3_index_info*);
int (*vtab_in)(sqlite3_index_info*,int,int);
int (*vtab_in_first)(sqlite3_value*,sqlite3_value**);
int (*vtab_in_next)(sqlite3_value*,sqlite3_value**);
/* Version 3.39.0 and later */
int (*deserialize)(sqlite3*,const char*,unsigned char*,
sqlite3_int64,sqlite3_int64,unsigned);
unsigned char *(*serialize)(sqlite3*,const char *,sqlite3_int64*,
unsigned int);
const char *(*db_name)(sqlite3*,int);
/* Version 3.40.0 and later */
int (*value_encoding)(sqlite3_value*);
/* Version 3.41.0 and later */
int (*is_interrupted)(sqlite3*);
/* Version 3.43.0 and later */
int (*stmt_explain)(sqlite3_stmt*,int);
/* Version 3.44.0 and later */
void *(*get_clientdata)(sqlite3*,const char*);
int (*set_clientdata)(sqlite3*, const char*, void*, void(*)(void*));
};
/*
** This is the function signature used for all extension entry points. It
** is also defined in the file "loadext.c".
*/
typedef int (*sqlite3_loadext_entry)(
sqlite3 *db, /* Handle to the database. */
char **pzErrMsg, /* Used to set error string on failure. */
const sqlite3_api_routines *pThunk /* Extension API function pointers. */
);
/*
** The following macros redefine the API routines so that they are
** redirected through the global sqlite3_api structure.
**
** This header file is also used by the loadext.c source file
** (part of the main SQLite library - not an extension) so that
** it can get access to the sqlite3_api_routines structure
** definition. But the main library does not want to redefine
** the API. So the redefinition macros are only valid if the
** SQLITE_CORE macros is undefined.
*/
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
#define sqlite3_aggregate_context sqlite3_api->aggregate_context
#ifndef SQLITE_OMIT_DEPRECATED
#define sqlite3_aggregate_count sqlite3_api->aggregate_count
#endif
#define sqlite3_bind_blob sqlite3_api->bind_blob
#define sqlite3_bind_double sqlite3_api->bind_double
#define sqlite3_bind_int sqlite3_api->bind_int
#define sqlite3_bind_int64 sqlite3_api->bind_int64
#define sqlite3_bind_null sqlite3_api->bind_null
#define sqlite3_bind_parameter_count sqlite3_api->bind_parameter_count
#define sqlite3_bind_parameter_index sqlite3_api->bind_parameter_index
#define sqlite3_bind_parameter_name sqlite3_api->bind_parameter_name
#define sqlite3_bind_text sqlite3_api->bind_text
#define sqlite3_bind_text16 sqlite3_api->bind_text16
#define sqlite3_bind_value sqlite3_api->bind_value
#define sqlite3_busy_handler sqlite3_api->busy_handler
#define sqlite3_busy_timeout sqlite3_api->busy_timeout
#define sqlite3_changes sqlite3_api->changes
#define sqlite3_close sqlite3_api->close
#define sqlite3_collation_needed sqlite3_api->collation_needed
#define sqlite3_collation_needed16 sqlite3_api->collation_needed16
#define sqlite3_column_blob sqlite3_api->column_blob
#define sqlite3_column_bytes sqlite3_api->column_bytes
#define sqlite3_column_bytes16 sqlite3_api->column_bytes16
#define sqlite3_column_count sqlite3_api->column_count
#define sqlite3_column_database_name sqlite3_api->column_database_name
#define sqlite3_column_database_name16 sqlite3_api->column_database_name16
#define sqlite3_column_decltype sqlite3_api->column_decltype
#define sqlite3_column_decltype16 sqlite3_api->column_decltype16
#define sqlite3_column_double sqlite3_api->column_double
#define sqlite3_column_int sqlite3_api->column_int
#define sqlite3_column_int64 sqlite3_api->column_int64
#define sqlite3_column_name sqlite3_api->column_name
#define sqlite3_column_name16 sqlite3_api->column_name16
#define sqlite3_column_origin_name sqlite3_api->column_origin_name
#define sqlite3_column_origin_name16 sqlite3_api->column_origin_name16
#define sqlite3_column_table_name sqlite3_api->column_table_name
#define sqlite3_column_table_name16 sqlite3_api->column_table_name16
#define sqlite3_column_text sqlite3_api->column_text
#define sqlite3_column_text16 sqlite3_api->column_text16
#define sqlite3_column_type sqlite3_api->column_type
#define sqlite3_column_value sqlite3_api->column_value
#define sqlite3_commit_hook sqlite3_api->commit_hook
#define sqlite3_complete sqlite3_api->complete
#define sqlite3_complete16 sqlite3_api->complete16
#define sqlite3_create_collation sqlite3_api->create_collation
#define sqlite3_create_collation16 sqlite3_api->create_collation16
#define sqlite3_create_function sqlite3_api->create_function
#define sqlite3_create_function16 sqlite3_api->create_function16
#define sqlite3_create_module sqlite3_api->create_module
#define sqlite3_create_module_v2 sqlite3_api->create_module_v2
#define sqlite3_data_count sqlite3_api->data_count
#define sqlite3_db_handle sqlite3_api->db_handle
#define sqlite3_declare_vtab sqlite3_api->declare_vtab
#define sqlite3_enable_shared_cache sqlite3_api->enable_shared_cache
#define sqlite3_errcode sqlite3_api->errcode
#define sqlite3_errmsg sqlite3_api->errmsg
#define sqlite3_errmsg16 sqlite3_api->errmsg16
#define sqlite3_exec sqlite3_api->exec
#ifndef SQLITE_OMIT_DEPRECATED
#define sqlite3_expired sqlite3_api->expired
#endif
#define sqlite3_finalize sqlite3_api->finalize
#define sqlite3_free sqlite3_api->free
#define sqlite3_free_table sqlite3_api->free_table
#define sqlite3_get_autocommit sqlite3_api->get_autocommit
#define sqlite3_get_auxdata sqlite3_api->get_auxdata
#define sqlite3_get_table sqlite3_api->get_table
#ifndef SQLITE_OMIT_DEPRECATED
#define sqlite3_global_recover sqlite3_api->global_recover
#endif
#define sqlite3_interrupt sqlite3_api->interruptx
#define sqlite3_last_insert_rowid sqlite3_api->last_insert_rowid
#define sqlite3_libversion sqlite3_api->libversion
#define sqlite3_libversion_number sqlite3_api->libversion_number
#define sqlite3_malloc sqlite3_api->malloc
#define sqlite3_mprintf sqlite3_api->mprintf
#define sqlite3_open sqlite3_api->open
#define sqlite3_open16 sqlite3_api->open16
#define sqlite3_prepare sqlite3_api->prepare
#define sqlite3_prepare16 sqlite3_api->prepare16
#define sqlite3_prepare_v2 sqlite3_api->prepare_v2
#define sqlite3_prepare16_v2 sqlite3_api->prepare16_v2
#define sqlite3_profile sqlite3_api->profile
#define sqlite3_progress_handler sqlite3_api->progress_handler
#define sqlite3_realloc sqlite3_api->realloc
#define sqlite3_reset sqlite3_api->reset
#define sqlite3_result_blob sqlite3_api->result_blob
#define sqlite3_result_double sqlite3_api->result_double
#define sqlite3_result_error sqlite3_api->result_error
#define sqlite3_result_error16 sqlite3_api->result_error16
#define sqlite3_result_int sqlite3_api->result_int
#define sqlite3_result_int64 sqlite3_api->result_int64
#define sqlite3_result_null sqlite3_api->result_null
#define sqlite3_result_text sqlite3_api->result_text
#define sqlite3_result_text16 sqlite3_api->result_text16
#define sqlite3_result_text16be sqlite3_api->result_text16be
#define sqlite3_result_text16le sqlite3_api->result_text16le
#define sqlite3_result_value sqlite3_api->result_value
#define sqlite3_rollback_hook sqlite3_api->rollback_hook
#define sqlite3_set_authorizer sqlite3_api->set_authorizer
#define sqlite3_set_auxdata sqlite3_api->set_auxdata
#define sqlite3_snprintf sqlite3_api->xsnprintf
#define sqlite3_step sqlite3_api->step
#define sqlite3_table_column_metadata sqlite3_api->table_column_metadata
#define sqlite3_thread_cleanup sqlite3_api->thread_cleanup
#define sqlite3_total_changes sqlite3_api->total_changes
#define sqlite3_trace sqlite3_api->trace
#ifndef SQLITE_OMIT_DEPRECATED
#define sqlite3_transfer_bindings sqlite3_api->transfer_bindings
#endif
#define sqlite3_update_hook sqlite3_api->update_hook
#define sqlite3_user_data sqlite3_api->user_data
#define sqlite3_value_blob sqlite3_api->value_blob
#define sqlite3_value_bytes sqlite3_api->value_bytes
#define sqlite3_value_bytes16 sqlite3_api->value_bytes16
#define sqlite3_value_double sqlite3_api->value_double
#define sqlite3_value_int sqlite3_api->value_int
#define sqlite3_value_int64 sqlite3_api->value_int64
#define sqlite3_value_numeric_type sqlite3_api->value_numeric_type
#define sqlite3_value_text sqlite3_api->value_text
#define sqlite3_value_text16 sqlite3_api->value_text16
#define sqlite3_value_text16be sqlite3_api->value_text16be
#define sqlite3_value_text16le sqlite3_api->value_text16le
#define sqlite3_value_type sqlite3_api->value_type
#define sqlite3_vmprintf sqlite3_api->vmprintf
#define sqlite3_vsnprintf sqlite3_api->xvsnprintf
#define sqlite3_overload_function sqlite3_api->overload_function
#define sqlite3_prepare_v2 sqlite3_api->prepare_v2
#define sqlite3_prepare16_v2 sqlite3_api->prepare16_v2
#define sqlite3_clear_bindings sqlite3_api->clear_bindings
#define sqlite3_bind_zeroblob sqlite3_api->bind_zeroblob
#define sqlite3_blob_bytes sqlite3_api->blob_bytes
#define sqlite3_blob_close sqlite3_api->blob_close
#define sqlite3_blob_open sqlite3_api->blob_open
#define sqlite3_blob_read sqlite3_api->blob_read
#define sqlite3_blob_write sqlite3_api->blob_write
#define sqlite3_create_collation_v2 sqlite3_api->create_collation_v2
#define sqlite3_file_control sqlite3_api->file_control
#define sqlite3_memory_highwater sqlite3_api->memory_highwater
#define sqlite3_memory_used sqlite3_api->memory_used
#define sqlite3_mutex_alloc sqlite3_api->mutex_alloc
#define sqlite3_mutex_enter sqlite3_api->mutex_enter
#define sqlite3_mutex_free sqlite3_api->mutex_free
#define sqlite3_mutex_leave sqlite3_api->mutex_leave
#define sqlite3_mutex_try sqlite3_api->mutex_try
#define sqlite3_open_v2 sqlite3_api->open_v2
#define sqlite3_release_memory sqlite3_api->release_memory
#define sqlite3_result_error_nomem sqlite3_api->result_error_nomem
#define sqlite3_result_error_toobig sqlite3_api->result_error_toobig
#define sqlite3_sleep sqlite3_api->sleep
#define sqlite3_soft_heap_limit sqlite3_api->soft_heap_limit
#define sqlite3_vfs_find sqlite3_api->vfs_find
#define sqlite3_vfs_register sqlite3_api->vfs_register
#define sqlite3_vfs_unregister sqlite3_api->vfs_unregister
#define sqlite3_threadsafe sqlite3_api->xthreadsafe
#define sqlite3_result_zeroblob sqlite3_api->result_zeroblob
#define sqlite3_result_error_code sqlite3_api->result_error_code
#define sqlite3_test_control sqlite3_api->test_control
#define sqlite3_randomness sqlite3_api->randomness
#define sqlite3_context_db_handle sqlite3_api->context_db_handle
#define sqlite3_extended_result_codes sqlite3_api->extended_result_codes
#define sqlite3_limit sqlite3_api->limit
#define sqlite3_next_stmt sqlite3_api->next_stmt
#define sqlite3_sql sqlite3_api->sql
#define sqlite3_status sqlite3_api->status
#define sqlite3_backup_finish sqlite3_api->backup_finish
#define sqlite3_backup_init sqlite3_api->backup_init
#define sqlite3_backup_pagecount sqlite3_api->backup_pagecount
#define sqlite3_backup_remaining sqlite3_api->backup_remaining
#define sqlite3_backup_step sqlite3_api->backup_step
#define sqlite3_compileoption_get sqlite3_api->compileoption_get
#define sqlite3_compileoption_used sqlite3_api->compileoption_used
#define sqlite3_create_function_v2 sqlite3_api->create_function_v2
#define sqlite3_db_config sqlite3_api->db_config
#define sqlite3_db_mutex sqlite3_api->db_mutex
#define sqlite3_db_status sqlite3_api->db_status
#define sqlite3_extended_errcode sqlite3_api->extended_errcode
#define sqlite3_log sqlite3_api->log
#define sqlite3_soft_heap_limit64 sqlite3_api->soft_heap_limit64
#define sqlite3_sourceid sqlite3_api->sourceid
#define sqlite3_stmt_status sqlite3_api->stmt_status
#define sqlite3_strnicmp sqlite3_api->strnicmp
#define sqlite3_unlock_notify sqlite3_api->unlock_notify
#define sqlite3_wal_autocheckpoint sqlite3_api->wal_autocheckpoint
#define sqlite3_wal_checkpoint sqlite3_api->wal_checkpoint
#define sqlite3_wal_hook sqlite3_api->wal_hook
#define sqlite3_blob_reopen sqlite3_api->blob_reopen
#define sqlite3_vtab_config sqlite3_api->vtab_config
#define sqlite3_vtab_on_conflict sqlite3_api->vtab_on_conflict
/* Version 3.7.16 and later */
#define sqlite3_close_v2 sqlite3_api->close_v2
#define sqlite3_db_filename sqlite3_api->db_filename
#define sqlite3_db_readonly sqlite3_api->db_readonly
#define sqlite3_db_release_memory sqlite3_api->db_release_memory
#define sqlite3_errstr sqlite3_api->errstr
#define sqlite3_stmt_busy sqlite3_api->stmt_busy
#define sqlite3_stmt_readonly sqlite3_api->stmt_readonly
#define sqlite3_stricmp sqlite3_api->stricmp
#define sqlite3_uri_boolean sqlite3_api->uri_boolean
#define sqlite3_uri_int64 sqlite3_api->uri_int64
#define sqlite3_uri_parameter sqlite3_api->uri_parameter
#define sqlite3_uri_vsnprintf sqlite3_api->xvsnprintf
#define sqlite3_wal_checkpoint_v2 sqlite3_api->wal_checkpoint_v2
/* Version 3.8.7 and later */
#define sqlite3_auto_extension sqlite3_api->auto_extension
#define sqlite3_bind_blob64 sqlite3_api->bind_blob64
#define sqlite3_bind_text64 sqlite3_api->bind_text64
#define sqlite3_cancel_auto_extension sqlite3_api->cancel_auto_extension
#define sqlite3_load_extension sqlite3_api->load_extension
#define sqlite3_malloc64 sqlite3_api->malloc64
#define sqlite3_msize sqlite3_api->msize
#define sqlite3_realloc64 sqlite3_api->realloc64
#define sqlite3_reset_auto_extension sqlite3_api->reset_auto_extension
#define sqlite3_result_blob64 sqlite3_api->result_blob64
#define sqlite3_result_text64 sqlite3_api->result_text64
#define sqlite3_strglob sqlite3_api->strglob
/* Version 3.8.11 and later */
#define sqlite3_value_dup sqlite3_api->value_dup
#define sqlite3_value_free sqlite3_api->value_free
#define sqlite3_result_zeroblob64 sqlite3_api->result_zeroblob64
#define sqlite3_bind_zeroblob64 sqlite3_api->bind_zeroblob64
/* Version 3.9.0 and later */
#define sqlite3_value_subtype sqlite3_api->value_subtype
#define sqlite3_result_subtype sqlite3_api->result_subtype
/* Version 3.10.0 and later */
#define sqlite3_status64 sqlite3_api->status64
#define sqlite3_strlike sqlite3_api->strlike
#define sqlite3_db_cacheflush sqlite3_api->db_cacheflush
/* Version 3.12.0 and later */
#define sqlite3_system_errno sqlite3_api->system_errno
/* Version 3.14.0 and later */
#define sqlite3_trace_v2 sqlite3_api->trace_v2
#define sqlite3_expanded_sql sqlite3_api->expanded_sql
/* Version 3.18.0 and later */
#define sqlite3_set_last_insert_rowid sqlite3_api->set_last_insert_rowid
/* Version 3.20.0 and later */
#define sqlite3_prepare_v3 sqlite3_api->prepare_v3
#define sqlite3_prepare16_v3 sqlite3_api->prepare16_v3
#define sqlite3_bind_pointer sqlite3_api->bind_pointer
#define sqlite3_result_pointer sqlite3_api->result_pointer
#define sqlite3_value_pointer sqlite3_api->value_pointer
/* Version 3.22.0 and later */
#define sqlite3_vtab_nochange sqlite3_api->vtab_nochange
#define sqlite3_value_nochange sqlite3_api->value_nochange
#define sqlite3_vtab_collation sqlite3_api->vtab_collation
/* Version 3.24.0 and later */
#define sqlite3_keyword_count sqlite3_api->keyword_count
#define sqlite3_keyword_name sqlite3_api->keyword_name
#define sqlite3_keyword_check sqlite3_api->keyword_check
#define sqlite3_str_new sqlite3_api->str_new
#define sqlite3_str_finish sqlite3_api->str_finish
#define sqlite3_str_appendf sqlite3_api->str_appendf
#define sqlite3_str_vappendf sqlite3_api->str_vappendf
#define sqlite3_str_append sqlite3_api->str_append
#define sqlite3_str_appendall sqlite3_api->str_appendall
#define sqlite3_str_appendchar sqlite3_api->str_appendchar
#define sqlite3_str_reset sqlite3_api->str_reset
#define sqlite3_str_errcode sqlite3_api->str_errcode
#define sqlite3_str_length sqlite3_api->str_length
#define sqlite3_str_value sqlite3_api->str_value
/* Version 3.25.0 and later */
#define sqlite3_create_window_function sqlite3_api->create_window_function
/* Version 3.26.0 and later */
#define sqlite3_normalized_sql sqlite3_api->normalized_sql
/* Version 3.28.0 and later */
#define sqlite3_stmt_isexplain sqlite3_api->stmt_isexplain
#define sqlite3_value_frombind sqlite3_api->value_frombind
/* Version 3.30.0 and later */
#define sqlite3_drop_modules sqlite3_api->drop_modules
/* Version 3.31.0 and later */
#define sqlite3_hard_heap_limit64 sqlite3_api->hard_heap_limit64
#define sqlite3_uri_key sqlite3_api->uri_key
#define sqlite3_filename_database sqlite3_api->filename_database
#define sqlite3_filename_journal sqlite3_api->filename_journal
#define sqlite3_filename_wal sqlite3_api->filename_wal
/* Version 3.32.0 and later */
#define sqlite3_create_filename sqlite3_api->create_filename
#define sqlite3_free_filename sqlite3_api->free_filename
#define sqlite3_database_file_object sqlite3_api->database_file_object
/* Version 3.34.0 and later */
#define sqlite3_txn_state sqlite3_api->txn_state
/* Version 3.36.1 and later */
#define sqlite3_changes64 sqlite3_api->changes64
#define sqlite3_total_changes64 sqlite3_api->total_changes64
/* Version 3.37.0 and later */
#define sqlite3_autovacuum_pages sqlite3_api->autovacuum_pages
/* Version 3.38.0 and later */
#define sqlite3_error_offset sqlite3_api->error_offset
#define sqlite3_vtab_rhs_value sqlite3_api->vtab_rhs_value
#define sqlite3_vtab_distinct sqlite3_api->vtab_distinct
#define sqlite3_vtab_in sqlite3_api->vtab_in
#define sqlite3_vtab_in_first sqlite3_api->vtab_in_first
#define sqlite3_vtab_in_next sqlite3_api->vtab_in_next
/* Version 3.39.0 and later */
#ifndef SQLITE_OMIT_DESERIALIZE
#define sqlite3_deserialize sqlite3_api->deserialize
#define sqlite3_serialize sqlite3_api->serialize
#endif
#define sqlite3_db_name sqlite3_api->db_name
/* Version 3.40.0 and later */
#define sqlite3_value_encoding sqlite3_api->value_encoding
/* Version 3.41.0 and later */
#define sqlite3_is_interrupted sqlite3_api->is_interrupted
/* Version 3.43.0 and later */
#define sqlite3_stmt_explain sqlite3_api->stmt_explain
/* Version 3.44.0 and later */
#define sqlite3_get_clientdata sqlite3_api->get_clientdata
#define sqlite3_set_clientdata sqlite3_api->set_clientdata
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
/* This case when the file really is being compiled as a loadable
** extension */
# define SQLITE_EXTENSION_INIT1 const sqlite3_api_routines *sqlite3_api=0;
# define SQLITE_EXTENSION_INIT2(v) sqlite3_api=v;
# define SQLITE_EXTENSION_INIT3 \
extern const sqlite3_api_routines *sqlite3_api;
#else
/* This case when the file is being statically linked into the
** application */
# define SQLITE_EXTENSION_INIT1 /*no-op*/
# define SQLITE_EXTENSION_INIT2(v) (void)v; /* unused parameter */
# define SQLITE_EXTENSION_INIT3 /*no-op*/
#endif
#endif /* SQLITE3EXT_H */

68
example/base64_demo.wren vendored Normal file
View File

@ -0,0 +1,68 @@
// retoor <retoor@molodetz.nl>
import "base64" for Base64
System.print("=== Base64 Module Demo ===\n")
System.print("--- Basic Encoding ---")
var text = "Hello, World!"
var encoded = Base64.encode(text)
System.print("Original: %(text)")
System.print("Encoded: %(encoded)")
System.print("\n--- Basic Decoding ---")
var decoded = Base64.decode(encoded)
System.print("Decoded: %(decoded)")
System.print("\n--- Various Strings ---")
var samples = [
"Hello",
"The quick brown fox jumps over the lazy dog",
"12345",
"Special chars: !@#^&*()",
"Unicode: cafe resume naive"
]
for (sample in samples) {
var enc = Base64.encode(sample)
var dec = Base64.decode(enc)
System.print("%(sample)")
System.print(" -> %(enc)")
System.print(" <- %(dec)")
System.print("")
}
System.print("--- URL-Safe Encoding ---")
var urlData = "Hello+World/Test=End"
var urlEncoded = Base64.encodeUrl(urlData)
var standardEncoded = Base64.encode(urlData)
System.print("Original: %(urlData)")
System.print("Standard Base64: %(standardEncoded)")
System.print("URL-Safe Base64: %(urlEncoded)")
System.print("\n--- URL-Safe Decoding ---")
var urlDecoded = Base64.decodeUrl(urlEncoded)
System.print("Decoded from URL-safe: %(urlDecoded)")
System.print("\n--- Binary-like Data ---")
var binaryText = "Binary: \x00\x01\x02\xFF"
var binaryEncoded = Base64.encode(binaryText)
System.print("Binary-like string encoded: %(binaryEncoded)")
System.print("\n--- Empty String ---")
System.print("Empty encoded: %(Base64.encode(""))")
System.print("Empty decoded: %(Base64.decode(""))")
System.print("\n--- Practical Example: Basic Auth Header ---")
var username = "admin"
var password = "secret123"
var credentials = "%(username):%(password)"
var authHeader = "Basic " + Base64.encode(credentials)
System.print("Credentials: %(credentials)")
System.print("Authorization header: %(authHeader)")
System.print("\n--- Practical Example: Data URI ---")
var jsonData = "{\"message\":\"Hello\"}"
var dataUri = "data:application/json;base64," + Base64.encode(jsonData)
System.print("JSON: %(jsonData)")
System.print("Data URI: %(dataUri)")

89
example/crypto_demo.wren vendored Normal file
View File

@ -0,0 +1,89 @@
// retoor <retoor@molodetz.nl>
import "crypto" for Crypto, Hash
System.print("=== Crypto Module Demo ===\n")
System.print("--- Random Bytes ---")
var bytes = Crypto.randomBytes(16)
System.print("16 random bytes: %(bytes)")
System.print("As hex: %(Hash.toHex(bytes))")
System.print("\n--- Random Integers ---")
System.print("Random integers between 1 and 100:")
for (i in 0...5) {
var num = Crypto.randomInt(1, 101)
System.print(" %(num)")
}
System.print("\n--- Random Integers (different ranges) ---")
System.print("1-10: %(Crypto.randomInt(1, 11))")
System.print("0-255: %(Crypto.randomInt(0, 256))")
System.print("1000-9999: %(Crypto.randomInt(1000, 10000))")
System.print("\n--- MD5 Hashing ---")
var text = "Hello, World!"
var md5Bytes = Hash.md5(text)
var md5Hex = Hash.toHex(md5Bytes)
System.print("Text: %(text)")
System.print("MD5: %(md5Hex)")
System.print("\n--- SHA1 Hashing ---")
var sha1Bytes = Hash.sha1(text)
var sha1Hex = Hash.toHex(sha1Bytes)
System.print("SHA1: %(sha1Hex)")
System.print("\n--- SHA256 Hashing ---")
var sha256Bytes = Hash.sha256(text)
var sha256Hex = Hash.toHex(sha256Bytes)
System.print("SHA256: %(sha256Hex)")
System.print("\n--- Hashing Different Inputs ---")
var inputs = [
"",
"test",
"The quick brown fox jumps over the lazy dog",
"password123"
]
for (input in inputs) {
var hash = Hash.toHex(Hash.sha256(input))
var displayInput = input
if (input == "") displayInput = "(empty string)"
if (input.count > 30) displayInput = input[0...30] + "..."
System.print("%(displayInput)")
System.print(" -> %(hash)")
}
System.print("\n--- Password Hashing Example ---")
var password = "mysecretpassword"
var passwordHash = Hash.toHex(Hash.sha256(password))
System.print("Password: %(password)")
System.print("Hash: %(passwordHash)")
System.print("\n--- Generating Random Token ---")
var tokenBytes = Crypto.randomBytes(32)
var token = Hash.toHex(tokenBytes)
System.print("Random token (64 hex chars): %(token)")
System.print("\n--- Generating Random Session ID ---")
var sessionBytes = Crypto.randomBytes(16)
var sessionId = Hash.toHex(sessionBytes)
System.print("Session ID: %(sessionId)")
System.print("\n--- Hash Comparison (constant input) ---")
var data = "consistent input"
var hash1 = Hash.toHex(Hash.sha256(data))
var hash2 = Hash.toHex(Hash.sha256(data))
System.print("Same input produces same hash: %(hash1 == hash2)")
System.print("\n--- Practical Example: File Checksum ---")
var fileContent = "This is the content of a file.\nLine 2.\nLine 3."
var checksum = Hash.toHex(Hash.sha256(fileContent))
System.print("File content checksum (SHA256):")
System.print(" %(checksum)")
System.print("\n--- Practical Example: API Key Generation ---")
var apiKeyBytes = Crypto.randomBytes(24)
var apiKey = Hash.toHex(apiKeyBytes)
System.print("Generated API key: %(apiKey)")

86
example/datetime_demo.wren vendored Normal file
View File

@ -0,0 +1,86 @@
// retoor <retoor@molodetz.nl>
import "datetime" for DateTime, Duration
System.print("=== DateTime Module Demo ===\n")
System.print("--- Current Date/Time ---")
var now = DateTime.now()
System.print("Now: %(now)")
System.print("Timestamp: %(now.timestamp)")
System.print("\n--- Date Components ---")
System.print("Year: %(now.year)")
System.print("Month: %(now.month)")
System.print("Day: %(now.day)")
System.print("Hour: %(now.hour)")
System.print("Minute: %(now.minute)")
System.print("Second: %(now.second)")
System.print("Day of Week: %(now.dayOfWeek) (0=Sunday)")
System.print("Day of Year: %(now.dayOfYear)")
System.print("Is DST: %(now.isDst)")
System.print("\n--- Creating from Timestamp ---")
var epoch = DateTime.fromTimestamp(0)
System.print("Unix Epoch: %(epoch)")
var specificTime = DateTime.fromTimestamp(1704067200)
System.print("Jan 1, 2024 00:00 UTC: %(specificTime)")
System.print("\n--- Formatting with strftime ---")
System.print("ISO 8601: %(now.toIso8601)")
System.print("Date (Y-m-d): %(now.format("\%Y-\%m-\%d"))")
System.print("Time (H:M:S): %(now.format("\%H:\%M:\%S"))")
System.print("Full date: %(now.format("\%A, \%B \%d, \%Y"))")
System.print("Time with AM/PM: %(now.format("\%I:\%M \%p"))")
System.print("\n--- Duration Class ---")
var oneHour = Duration.fromHours(1)
var twoMinutes = Duration.fromMinutes(2)
var thirtySeconds = Duration.fromSeconds(30)
var oneDay = Duration.fromDays(1)
System.print("1 hour in seconds: %(oneHour.seconds)")
System.print("2 minutes in milliseconds: %(twoMinutes.milliseconds)")
System.print("30 seconds in minutes: %(thirtySeconds.minutes)")
System.print("1 day in hours: %(oneDay.hours)")
System.print("\n--- Duration Arithmetic ---")
var combined = oneHour + twoMinutes + thirtySeconds
System.print("1 hour + 2 minutes + 30 seconds: %(combined)")
System.print(" in seconds: %(combined.seconds)")
var doubled = oneHour * 2
System.print("1 hour * 2: %(doubled)")
var diff = oneHour - twoMinutes
System.print("1 hour - 2 minutes: %(diff)")
System.print("\n--- DateTime Arithmetic ---")
var tomorrow = now + Duration.fromDays(1)
System.print("Now: %(now)")
System.print("Tomorrow: %(tomorrow)")
var nextHour = now + Duration.fromHours(1)
System.print("Next hour: %(nextHour)")
var yesterday = now - Duration.fromDays(1)
System.print("Yesterday: %(yesterday)")
System.print("\n--- DateTime Comparison ---")
System.print("tomorrow > now: %(tomorrow > now)")
System.print("yesterday < now: %(yesterday < now)")
System.print("now == now: %(now == now)")
System.print("\n--- Difference Between DateTimes ---")
var timeDiff = tomorrow - now
System.print("Difference (tomorrow - now): %(timeDiff)")
System.print("\n--- Practical Example: Event Timing ---")
var eventStart = DateTime.now()
var eventDuration = Duration.fromHours(2) + Duration.fromMinutes(30)
var eventEnd = eventStart + eventDuration
System.print("Event starts: %(eventStart.format("\%H:\%M"))")
System.print("Event duration: %(eventDuration)")
System.print("Event ends: %(eventEnd.format("\%H:\%M"))")

47
example/dns_demo.wren vendored Normal file
View File

@ -0,0 +1,47 @@
// retoor <retoor@molodetz.nl>
import "dns" for Dns
System.print("=== DNS Module Demo ===\n")
System.print("--- Basic DNS Lookup ---")
var hostname = "example.com"
System.print("Looking up: %(hostname)")
var addresses = Dns.lookup(hostname)
System.print("Addresses: %(addresses)")
System.print("\n--- IPv4 Only Lookup ---")
System.print("Looking up %(hostname) (IPv4 only)")
var ipv4Addresses = Dns.lookup(hostname, 4)
System.print("IPv4 addresses: %(ipv4Addresses)")
System.print("\n--- IPv6 Only Lookup ---")
System.print("Looking up %(hostname) (IPv6 only)")
var ipv6Addresses = Dns.lookup(hostname, 6)
System.print("IPv6 addresses: %(ipv6Addresses)")
System.print("\n--- Multiple Hostname Lookups ---")
var hosts = ["google.com", "github.com", "cloudflare.com"]
for (host in hosts) {
var addrs = Dns.lookup(host, 4)
if (addrs.count > 0) {
System.print("%(host) -> %(addrs[0])")
} else {
System.print("%(host) -> (no addresses found)")
}
}
System.print("\n--- Localhost Lookup ---")
var localhost = Dns.lookup("localhost", 4)
System.print("localhost (IPv4): %(localhost)")
System.print("\n--- Practical Example: Server Connection ---")
var serverHost = "httpbin.org"
System.print("Resolving server: %(serverHost)")
var serverAddrs = Dns.lookup(serverHost, 4)
if (serverAddrs.count > 0) {
System.print("Server IP: %(serverAddrs[0])")
System.print("Ready to connect to %(serverAddrs[0]):80")
} else {
System.print("Could not resolve %(serverHost)")
}

51
example/env_demo.wren vendored Normal file
View File

@ -0,0 +1,51 @@
// retoor <retoor@molodetz.nl>
import "env" for Environment
System.print("=== Environment Module Demo ===\n")
System.print("--- Reading Environment Variables ---")
var home = Environment.get("HOME")
var path = Environment.get("PATH")
var user = Environment.get("USER")
System.print("HOME: %(home)")
System.print("USER: %(user)")
System.print("PATH (first 80 chars): %(path[0...80])...")
System.print("\n--- Reading Non-existent Variable ---")
var missing = Environment.get("WREN_NONEXISTENT_VAR_12345")
System.print("Missing variable: %(missing)")
System.print("\n--- Setting Environment Variables ---")
Environment.set("WREN_DEMO_VAR", "Hello from Wren!")
var demoVar = Environment.get("WREN_DEMO_VAR")
System.print("WREN_DEMO_VAR: %(demoVar)")
Environment.set("WREN_DEMO_NUMBER", "42")
System.print("WREN_DEMO_NUMBER: %(Environment.get("WREN_DEMO_NUMBER"))")
System.print("\n--- Updating Environment Variables ---")
Environment.set("WREN_DEMO_VAR", "Updated value")
System.print("WREN_DEMO_VAR (updated): %(Environment.get("WREN_DEMO_VAR"))")
System.print("\n--- Deleting Environment Variables ---")
Environment.delete("WREN_DEMO_VAR")
var afterDelete = Environment.get("WREN_DEMO_VAR")
System.print("WREN_DEMO_VAR (after delete): %(afterDelete)")
System.print("\n--- Listing All Environment Variables ---")
var all = Environment.all
System.print("Total environment variables: %(all.count)")
System.print("\nFirst 10 environment variables:")
var count = 0
for (entry in all) {
if (count >= 10) break
var value = entry.value
if (value.count > 40) value = value[0...40] + "..."
System.print(" %(entry.key) = %(value)")
count = count + 1
}
Environment.delete("WREN_DEMO_NUMBER")

View File

@ -36,7 +36,9 @@ var host = "127.0.0.1"
var port = 8080 // Try to connect to our own http_server example
var path = "/"
while(true) {
var result = HttpClient.get(host, port, path)
System.print("\n--- Response Received ---
")
System.print(result)
}

94
example/http_demo.wren vendored Normal file
View File

@ -0,0 +1,94 @@
// retoor <retoor@molodetz.nl>
import "http" for Http, HttpResponse, Url
System.print("=== HTTP Module Demo ===\n")
System.print("--- URL Parsing ---")
var urls = [
"http://example.com",
"http://example.com:8080",
"http://example.com/path/to/resource",
"http://example.com/search?q=hello&limit=10"
]
for (urlStr in urls) {
var url = Url.parse(urlStr)
System.print("URL: %(urlStr)")
System.print(" scheme: %(url.scheme), host: %(url.host), port: %(url.port)")
System.print(" path: %(url.path), query: %(url.query)")
System.print("")
}
System.print("--- Simple GET Request ---")
System.print("Fetching http://httpbin.org/get...")
var response = Http.get("http://httpbin.org/get")
System.print("Status: %(response.statusCode) %(response.statusText)")
System.print("OK: %(response.ok)")
System.print("\n--- Response Headers ---")
var count = 0
for (header in response.headers) {
if (count < 5) System.print(" %(header.key): %(header.value)")
count = count + 1
}
System.print("\n--- Response Body (first 300 chars) ---")
var body = response.body
if (body.count > 300) body = body[0...300] + "..."
System.print(body)
System.print("\n--- Getting Specific Header ---")
var contentType = response.header("Content-Type")
System.print("Content-Type: %(contentType)")
System.print("\n--- GET with Query Parameters ---")
var searchResponse = Http.get("http://httpbin.org/get?name=wren&version=1.0")
System.print("Status: %(searchResponse.statusCode)")
System.print("Body contains 'wren': %(searchResponse.body.contains("wren"))")
System.print("\n--- POST Request ---")
System.print("Posting to httpbin.org...")
var postResponse = Http.post("http://httpbin.org/post", "Hello from Wren!")
System.print("Status: %(postResponse.statusCode)")
System.print("Body contains 'Hello': %(postResponse.body.contains("Hello"))")
System.print("\n--- Custom Headers ---")
var headersResponse = Http.get("http://httpbin.org/headers", {
"X-Custom-Header": "WrenTest",
"Accept": "application/json"
})
System.print("Status: %(headersResponse.statusCode)")
System.print("Body contains 'WrenTest': %(headersResponse.body.contains("WrenTest"))")
System.print("\n--- Checking Response Status ---")
var notFoundResponse = Http.get("http://httpbin.org/status/404")
System.print("404 response OK: %(notFoundResponse.ok)")
System.print("Status code: %(notFoundResponse.statusCode)")
var okResponse = Http.get("http://httpbin.org/status/200")
System.print("200 response OK: %(okResponse.ok)")
System.print("\n--- HTTP Response Object ---")
System.print(response.toString)
System.print("\n--- Practical Example: Get UUID ---")
var uuidResponse = Http.get("http://httpbin.org/uuid")
System.print("Status: %(uuidResponse.statusCode)")
var uuidBody = uuidResponse.body
if (uuidBody.contains("uuid")) {
System.print("UUID response received successfully")
System.print("Body: %(uuidBody)")
}
System.print("\n--- HTTPS Request ---")
System.print("Fetching https://httpbin.org/get...")
var httpsResponse = Http.get("https://httpbin.org/get")
System.print("Status: %(httpsResponse.statusCode) %(httpsResponse.statusText)")
System.print("OK: %(httpsResponse.ok)")
System.print("Body contains 'origin': %(httpsResponse.body.contains("origin"))")
System.print("\n--- HTTPS POST Request ---")
var httpsPost = Http.post("https://httpbin.org/post", {"message": "Hello from Wren over HTTPS!"})
System.print("Status: %(httpsPost.statusCode)")
System.print("Body contains 'Hello': %(httpsPost.body.contains("Hello"))")

161
example/jinja_basics.wren vendored Normal file
View File

@ -0,0 +1,161 @@
// retoor <retoor@molodetz.nl>
import "jinja" for Environment, DictLoader
System.print("=" * 60)
System.print("JINJA BASICS - Variables, Filters, and Expressions")
System.print("=" * 60)
var templates = {
"variable_simple": "Hello, {{ name }}!",
"variable_nested": "User: {{ user.name }}, Age: {{ user.age }}, City: {{ user.address.city }}",
"variable_subscript": "First item: {{ items[0] }}, Last item: {{ items[-1] }}",
"variable_default": "Name: {{ name|default(\"Anonymous\") }}, Role: {{ role|default(\"Guest\") }}",
"literal_string": "String: {{ \"hello world\" }}",
"literal_number": "Number: {{ 42 }}, Float: {{ 3.14159 }}",
"literal_bool": "True: {{ true }}, False: {{ false }}",
"literal_list": "List: {{ [1, 2, 3]|join(\", \") }}",
"expr_math": "Addition: {{ 10 + 5 }}, Subtraction: {{ 10 - 5 }}, Multiplication: {{ 10 * 5 }}, Division: {{ 10 / 4 }}",
"expr_modulo": "Modulo: {{ 17 \% 5 }}",
"expr_negative": "Negative: {{ -42 }}, Double negative: {{ --42 }}",
"expr_parens": "With parens: {{ (2 + 3) * 4 }}, Without: {{ 2 + 3 * 4 }}",
"expr_concat": "Concat: {{ first ~ \" \" ~ last }}",
"expr_compare": "Equal: {{ 5 == 5 }}, Not equal: {{ 5 != 3 }}, Less: {{ 3 < 5 }}, Greater: {{ 5 > 3 }}",
"expr_logic": "And: {{ true and false }}, Or: {{ true or false }}, Not: {{ not false }}",
"expr_in": "In list: {{ 2 in [1, 2, 3] }}, In string: {{ \"o\" in \"hello\" }}",
"filter_upper": "{{ text|upper }}",
"filter_lower": "{{ text|lower }}",
"filter_capitalize": "{{ text|capitalize }}",
"filter_title": "{{ text|title }}",
"filter_trim": "[{{ text|trim }}]",
"filter_length": "Length: {{ items|length }}",
"filter_first": "First: {{ items|first }}",
"filter_last": "Last: {{ items|last }}",
"filter_reverse": "Reversed: {{ items|reverse|join(\", \") }}",
"filter_sort": "Sorted: {{ items|sort|join(\", \") }}",
"filter_unique": "Unique: {{ items|unique|join(\", \") }}",
"filter_join": "Joined: {{ items|join(\" | \") }}",
"filter_replace": "{{ text|replace(\"world\", \"universe\") }}",
"filter_truncate": "{{ text|truncate(20) }}",
"filter_abs": "Absolute: {{ num|abs }}",
"filter_round": "Rounded: {{ num|round(2) }}",
"filter_int": "Integer: {{ num|int }}",
"filter_chain": "{{ text|trim|upper|replace(\"HELLO\", \"HI\") }}",
"filter_batch": "{\% for row in items|batch(3) \%}[{\% for i in row \%}{{ i }}{\% endfor \%}]{\% endfor \%}",
"filter_map": "Names: {{ users|map(\"name\")|join(\", \") }}",
"filter_sum": "Sum: {{ numbers|sum }}",
"filter_min": "Min: {{ numbers|min }}",
"filter_max": "Max: {{ numbers|max }}",
"filter_striptags": "{{ html|striptags }}",
"filter_urlencode": "{{ text|urlencode }}",
"filter_wordcount": "Word count: {{ text|wordcount }}",
"filter_center": "[{{ text|center(20) }}]",
"escape_auto": "Auto-escaped: {{ html }}",
"escape_safe": "Safe (no escape): {{ html|safe }}",
"tojson_simple": "{{ data|tojson }}",
"tojson_complex": "{{ complex|tojson }}"
}
var env = Environment.new(DictLoader.new(templates))
System.print("\n--- Simple Variables ---")
System.print(env.getTemplate("variable_simple").render({"name": "World"}))
System.print(env.getTemplate("variable_nested").render({
"user": {
"name": "Alice",
"age": 28,
"address": {"city": "New York", "zip": "10001"}
}
}))
System.print(env.getTemplate("variable_subscript").render({"items": ["apple", "banana", "cherry", "date"]}))
System.print(env.getTemplate("variable_default").render({"name": "Bob"}))
System.print("\n--- Literals ---")
System.print(env.getTemplate("literal_string").render({}))
System.print(env.getTemplate("literal_number").render({}))
System.print(env.getTemplate("literal_bool").render({}))
System.print(env.getTemplate("literal_list").render({}))
System.print("\n--- Mathematical Expressions ---")
System.print(env.getTemplate("expr_math").render({}))
System.print(env.getTemplate("expr_modulo").render({}))
System.print(env.getTemplate("expr_negative").render({}))
System.print(env.getTemplate("expr_parens").render({}))
System.print(env.getTemplate("expr_concat").render({"first": "John", "last": "Doe"}))
System.print("\n--- Comparison and Logic ---")
System.print(env.getTemplate("expr_compare").render({}))
System.print(env.getTemplate("expr_logic").render({}))
System.print(env.getTemplate("expr_in").render({}))
System.print("\n--- String Filters ---")
System.print(env.getTemplate("filter_upper").render({"text": "hello world"}))
System.print(env.getTemplate("filter_lower").render({"text": "HELLO WORLD"}))
System.print(env.getTemplate("filter_capitalize").render({"text": "hello world"}))
System.print(env.getTemplate("filter_title").render({"text": "hello world"}))
System.print(env.getTemplate("filter_trim").render({"text": " padded string "}))
System.print(env.getTemplate("filter_replace").render({"text": "hello world"}))
System.print(env.getTemplate("filter_truncate").render({"text": "This is a very long string that should be truncated"}))
System.print(env.getTemplate("filter_chain").render({"text": " hello world "}))
System.print("\n--- List Filters ---")
System.print(env.getTemplate("filter_length").render({"items": [1, 2, 3, 4, 5]}))
System.print(env.getTemplate("filter_first").render({"items": ["a", "b", "c"]}))
System.print(env.getTemplate("filter_last").render({"items": ["a", "b", "c"]}))
System.print(env.getTemplate("filter_reverse").render({"items": ["a", "b", "c"]}))
System.print(env.getTemplate("filter_sort").render({"items": ["cherry", "apple", "banana"]}))
System.print(env.getTemplate("filter_unique").render({"items": ["a", "b", "a", "c", "b", "d"]}))
System.print(env.getTemplate("filter_join").render({"items": ["one", "two", "three"]}))
System.print(env.getTemplate("filter_batch").render({"items": [1, 2, 3, 4, 5, 6, 7, 8, 9]}))
System.print("\n--- Numeric Filters ---")
System.print(env.getTemplate("filter_abs").render({"num": -42}))
System.print(env.getTemplate("filter_round").render({"num": 3.14159}))
System.print(env.getTemplate("filter_int").render({"num": 3.99}))
System.print(env.getTemplate("filter_sum").render({"numbers": [1, 2, 3, 4, 5]}))
System.print(env.getTemplate("filter_min").render({"numbers": [5, 2, 8, 1, 9]}))
System.print(env.getTemplate("filter_max").render({"numbers": [5, 2, 8, 1, 9]}))
System.print("\n--- Object Filters ---")
System.print(env.getTemplate("filter_map").render({
"users": [
{"name": "Alice", "age": 25},
{"name": "Bob", "age": 30},
{"name": "Charlie", "age": 35}
]
}))
System.print("\n--- HTML/Text Filters ---")
System.print(env.getTemplate("filter_striptags").render({"html": "<p>Hello <b>World</b></p>"}))
System.print(env.getTemplate("filter_urlencode").render({"text": "hello world & more"}))
System.print(env.getTemplate("filter_wordcount").render({"text": "The quick brown fox jumps"}))
System.print(env.getTemplate("filter_center").render({"text": "Hi"}))
System.print("\n--- HTML Escaping ---")
System.print(env.getTemplate("escape_auto").render({"html": "<script>alert('XSS')</script>"}))
System.print(env.getTemplate("escape_safe").render({"html": "<b>Bold text</b>"}))
System.print("\n--- JSON Output ---")
System.print(env.getTemplate("tojson_simple").render({"data": {"name": "Test", "value": 42}}))
System.print(env.getTemplate("tojson_complex").render({
"complex": {
"string": "hello",
"number": 123,
"bool": true,
"null_val": null,
"array": [1, 2, 3],
"nested": {"a": 1, "b": 2}
}
}))
System.print("\n" + "=" * 60)
System.print("End of Basics Demo")
System.print("=" * 60)

188
example/jinja_control_flow.wren vendored Normal file
View File

@ -0,0 +1,188 @@
// retoor <retoor@molodetz.nl>
import "jinja" for Environment, DictLoader
System.print("=" * 60)
System.print("JINJA CONTROL FLOW - Conditionals, Loops, and Tests")
System.print("=" * 60)
var templates = {
"if_simple": "{\% if show \%}Visible{\% endif \%}",
"if_else": "{\% if logged_in \%}Welcome back!{\% else \%}Please log in{\% endif \%}",
"if_elif": "{\% if score >= 90 \%}A{\% elif score >= 80 \%}B{\% elif score >= 70 \%}C{\% elif score >= 60 \%}D{\% else \%}F{\% endif \%}",
"if_nested": "{\% if user \%}{\% if user.admin \%}Admin: {{ user.name }}{\% else \%}User: {{ user.name }}{\% endif \%}{\% else \%}Guest{\% endif \%}",
"if_and": "{\% if a and b \%}Both true{\% else \%}At least one false{\% endif \%}",
"if_or": "{\% if a or b \%}At least one true{\% else \%}Both false{\% endif \%}",
"if_not": "{\% if not hidden \%}Shown{\% else \%}Hidden{\% endif \%}",
"if_complex": "{\% if (a > 5 and b < 10) or c == \"special\" \%}Match!{\% else \%}No match{\% endif \%}",
"if_in_list": "{\% if item in items \%}Found '{{ item }}'{\% else \%}'{{ item }}' not found{\% endif \%}",
"if_in_string": "{\% if char in text \%}Contains '{{ char }}'{\% else \%}Does not contain '{{ char }}'{\% endif \%}",
"if_not_in": "{\% if item not in items \%}Not in list{\% else \%}In list{\% endif \%}",
"test_defined": "{\% if var is defined \%}Defined: {{ var }}{\% else \%}Undefined{\% endif \%}",
"test_undefined": "{\% if var is undefined \%}Undefined{\% else \%}Defined: {{ var }}{\% endif \%}",
"test_none": "{\% if var is none \%}Is none{\% else \%}Not none: {{ var }}{\% endif \%}",
"test_even": "{\% if num is even \%}{{ num }} is even{\% else \%}{{ num }} is odd{\% endif \%}",
"test_odd": "{\% if num is odd \%}{{ num }} is odd{\% else \%}{{ num }} is even{\% endif \%}",
"test_divisible": "{\% if num is divisibleby 3 \%}{{ num }} is divisible by 3{\% else \%}{{ num }} is not divisible by 3{\% endif \%}",
"test_sequence": "{\% if items is sequence \%}Is a sequence{\% else \%}Not a sequence{\% endif \%}",
"test_string": "{\% if val is string \%}Is a string{\% else \%}Not a string{\% endif \%}",
"test_number": "{\% if val is number \%}Is a number{\% else \%}Not a number{\% endif \%}",
"test_mapping": "{\% if val is mapping \%}Is a mapping{\% else \%}Not a mapping{\% endif \%}",
"ternary": "Status: {{ \"Active\" if active else \"Inactive\" }}",
"ternary_complex": "{{ user.name if user else \"Anonymous\" }} ({{ \"Admin\" if user and user.admin else \"User\" }})",
"for_simple": "{\% for item in items \%}{{ item }} {\% endfor \%}",
"for_index": "{\% for item in items \%}{{ loop.index }}. {{ item }}\n{\% endfor \%}",
"for_index0": "{\% for item in items \%}[{{ loop.index0 }}] {{ item }}\n{\% endfor \%}",
"for_first_last": "{\% for item in items \%}{{ \"[\" if loop.first else \"\" }}{{ item }}{{ \"]\" if loop.last else \", \" }}{\% endfor \%}",
"for_length": "{\% for item in items \%}Item {{ loop.index }} of {{ loop.length }}: {{ item }}\n{\% endfor \%}",
"for_revindex": "{\% for item in items \%}{{ loop.revindex }} remaining: {{ item }}\n{\% endfor \%}",
"for_else": "{\% for item in items \%}{{ item }}{\% else \%}No items found{\% endfor \%}",
"for_nested": "{\% for row in matrix \%}Row {{ loop.index }}: {\% for col in row \%}{{ col }}{\% if not loop.last \%}, {\% endif \%}{\% endfor \%}\n{\% endfor \%}",
"for_dict": "{\% for key, value in data \%}{{ key }}: {{ value }}\n{\% endfor \%}",
"for_dict_items": "{\% for key in data \%}{{ key }} = {{ data[key] }}\n{\% endfor \%}",
"for_range": "{\% for i in range(5) \%}{{ i }} {\% endfor \%}",
"for_range_start": "{\% for i in range(1, 6) \%}{{ i }} {\% endfor \%}",
"for_range_step": "{\% for i in range(0, 10, 2) \%}{{ i }} {\% endfor \%}",
"for_string": "{\% for char in text \%}{{ char }}-{\% endfor \%}",
"for_filtered": "{\% for item in items|sort \%}{{ item }} {\% endfor \%}",
"for_reversed": "{\% for item in items|reverse \%}{{ item }} {\% endfor \%}",
"for_conditional": "{\% for item in items \%}{\% if item > 5 \%}{{ item }} is greater than 5\n{\% endif \%}{\% endfor \%}",
"set_simple": "{\% set x = 10 \%}x = {{ x }}",
"set_expression": "{\% set result = a + b * c \%}Result: {{ result }}",
"set_from_filter": "{\% set upper_name = name|upper \%}Upper: {{ upper_name }}",
"set_in_loop": "{\% set total = 0 \%}{\% for n in numbers \%}{\% set total = total + n \%}{\% endfor \%}Total: {{ total }}",
"combined_example": "{\% for user in users \%}{\% if user.active \%}{{ loop.index }}. {{ user.name|title }} ({{ \"Admin\" if user.admin else \"User\" }})\n{\% endif \%}{\% else \%}No active users{\% endfor \%}"
}
var env = Environment.new(DictLoader.new(templates))
System.print("\n--- Simple Conditionals ---")
System.print("show=true: " + env.getTemplate("if_simple").render({"show": true}))
System.print("show=false: " + env.getTemplate("if_simple").render({"show": false}))
System.print("logged_in=true: " + env.getTemplate("if_else").render({"logged_in": true}))
System.print("logged_in=false: " + env.getTemplate("if_else").render({"logged_in": false}))
System.print("\n--- If-Elif-Else Chain ---")
for (score in [95, 85, 75, 65, 50]) {
System.print("Score %(score): " + env.getTemplate("if_elif").render({"score": score}))
}
System.print("\n--- Nested Conditionals ---")
System.print(env.getTemplate("if_nested").render({"user": {"name": "Alice", "admin": true}}))
System.print(env.getTemplate("if_nested").render({"user": {"name": "Bob", "admin": false}}))
System.print(env.getTemplate("if_nested").render({"user": null}))
System.print("\n--- Logical Operators ---")
System.print("a=true, b=true: " + env.getTemplate("if_and").render({"a": true, "b": true}))
System.print("a=true, b=false: " + env.getTemplate("if_and").render({"a": true, "b": false}))
System.print("a=false, b=false: " + env.getTemplate("if_or").render({"a": false, "b": false}))
System.print("a=true, b=false: " + env.getTemplate("if_or").render({"a": true, "b": false}))
System.print("hidden=false: " + env.getTemplate("if_not").render({"hidden": false}))
System.print("hidden=true: " + env.getTemplate("if_not").render({"hidden": true}))
System.print("\n--- Complex Conditions ---")
System.print(env.getTemplate("if_complex").render({"a": 10, "b": 5, "c": "normal"}))
System.print(env.getTemplate("if_complex").render({"a": 3, "b": 5, "c": "special"}))
System.print(env.getTemplate("if_complex").render({"a": 3, "b": 15, "c": "normal"}))
System.print("\n--- In Operator ---")
System.print(env.getTemplate("if_in_list").render({"item": "banana", "items": ["apple", "banana", "cherry"]}))
System.print(env.getTemplate("if_in_list").render({"item": "grape", "items": ["apple", "banana", "cherry"]}))
System.print(env.getTemplate("if_in_string").render({"char": "o", "text": "hello"}))
System.print(env.getTemplate("if_not_in").render({"item": "x", "items": ["a", "b", "c"]}))
System.print("\n--- Tests ---")
System.print(env.getTemplate("test_defined").render({"var": "Hello"}))
System.print(env.getTemplate("test_defined").render({}))
System.print(env.getTemplate("test_none").render({"var": null}))
System.print(env.getTemplate("test_none").render({"var": "something"}))
System.print(env.getTemplate("test_even").render({"num": 4}))
System.print(env.getTemplate("test_even").render({"num": 7}))
System.print(env.getTemplate("test_divisible").render({"num": 9}))
System.print(env.getTemplate("test_divisible").render({"num": 10}))
System.print(env.getTemplate("test_sequence").render({"items": [1, 2, 3]}))
System.print(env.getTemplate("test_string").render({"val": "hello"}))
System.print(env.getTemplate("test_number").render({"val": 42}))
System.print(env.getTemplate("test_mapping").render({"val": {"a": 1}}))
System.print("\n--- Ternary Expressions ---")
System.print(env.getTemplate("ternary").render({"active": true}))
System.print(env.getTemplate("ternary").render({"active": false}))
System.print(env.getTemplate("ternary_complex").render({"user": {"name": "Alice", "admin": true}}))
System.print(env.getTemplate("ternary_complex").render({"user": null}))
System.print("\n--- Basic For Loops ---")
System.print(env.getTemplate("for_simple").render({"items": ["a", "b", "c", "d"]}))
System.print("\n--- Loop Variables ---")
System.print("loop.index:")
System.print(env.getTemplate("for_index").render({"items": ["Apple", "Banana", "Cherry"]}))
System.print("loop.index0:")
System.print(env.getTemplate("for_index0").render({"items": ["Apple", "Banana", "Cherry"]}))
System.print("loop.first/last:")
System.print(env.getTemplate("for_first_last").render({"items": ["Apple", "Banana", "Cherry"]}))
System.print("loop.length:")
System.print(env.getTemplate("for_length").render({"items": ["A", "B", "C"]}))
System.print("loop.revindex:")
System.print(env.getTemplate("for_revindex").render({"items": ["A", "B", "C"]}))
System.print("\n--- For-Else ---")
System.print("With items: " + env.getTemplate("for_else").render({"items": ["x", "y", "z"]}))
System.print("Empty: " + env.getTemplate("for_else").render({"items": []}))
System.print("\n--- Nested Loops ---")
System.print(env.getTemplate("for_nested").render({
"matrix": [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
}))
System.print("\n--- Dictionary Iteration ---")
System.print(env.getTemplate("for_dict").render({"data": {"name": "Alice", "age": 30, "city": "NYC"}}))
System.print("\n--- Range Function ---")
System.print("range(5): " + env.getTemplate("for_range").render({}))
System.print("range(1, 6): " + env.getTemplate("for_range_start").render({}))
System.print("range(0, 10, 2): " + env.getTemplate("for_range_step").render({}))
System.print("\n--- Iterating Strings ---")
System.print(env.getTemplate("for_string").render({"text": "Hello"}))
System.print("\n--- Filtered Loops ---")
System.print("Sorted: " + env.getTemplate("for_filtered").render({"items": ["cherry", "apple", "banana"]}))
System.print("Reversed: " + env.getTemplate("for_reversed").render({"items": [1, 2, 3, 4, 5]}))
System.print("\n--- Conditionals in Loops ---")
System.print(env.getTemplate("for_conditional").render({"items": [3, 7, 2, 9, 4, 8]}))
System.print("\n--- Set Variable ---")
System.print(env.getTemplate("set_simple").render({}))
System.print(env.getTemplate("set_expression").render({"a": 2, "b": 3, "c": 4}))
System.print(env.getTemplate("set_from_filter").render({"name": "alice"}))
System.print("\n--- Combined Example ---")
System.print(env.getTemplate("combined_example").render({
"users": [
{"name": "alice smith", "active": true, "admin": true},
{"name": "bob jones", "active": false, "admin": false},
{"name": "charlie brown", "active": true, "admin": false},
{"name": "diana ross", "active": true, "admin": true}
]
}))
System.print("\n" + "=" * 60)
System.print("End of Control Flow Demo")
System.print("=" * 60)

107
example/jinja_demo.wren vendored Normal file
View File

@ -0,0 +1,107 @@
// retoor <retoor@molodetz.nl>
import "jinja" for Environment, DictLoader
System.print("=== Jinja2-like Templating Demo ===\n")
var templates = {
"base.html": "<!DOCTYPE html>\n<html>\n<head><title>{\% block title \%}Default{\% endblock \%}</title></head>\n<body>\n{\% block content \%}{\% endblock \%}\n</body>\n</html>",
"page.html": "{\% extends \"base.html\" \%}{\% block title \%}{{ page_title }}{\% endblock \%}{\% block content \%}<h1>{{ heading }}</h1>\n<p>{{ message }}</p>{\% endblock \%}",
"list.html": "<ul>\n{\% for item in items \%}<li>{{ loop.index }}. {{ item }}</li>\n{\% else \%}<li>No items</li>\n{\% endfor \%}</ul>",
"user_card.html": "{\% macro user_card(user) \%}<div class=\"card\">\n <h3>{{ user.name|title }}</h3>\n <p>Email: {{ user.email|default(\"N/A\") }}</p>\n <p>Age: {{ user.age|default(\"Unknown\") }}</p>\n</div>{\% endmacro \%}{\% for u in users \%}{{ user_card(u) }}\n{\% endfor \%}",
"table.html": "<table>\n{\% for row in data|batch(3) \%}<tr>{\% for cell in row \%}<td>{{ cell }}</td>{\% endfor \%}</tr>\n{\% endfor \%}</table>",
"conditional.html": "{\% if user.role == \"admin\" \%}Admin Panel{\% elif user.role == \"moderator\" \%}Mod Panel{\% else \%}User Panel{\% endif \%}",
"filters.html": "Original: {{ text }}\nUpper: {{ text|upper }}\nTrimmed: {{ text|trim }}\nLength: {{ text|length }}\nTruncated: {{ text|truncate(10) }}"
}
var env = Environment.new(DictLoader.new(templates))
System.print("--- Template Inheritance ---")
var page = env.getTemplate("page.html")
System.print(page.render({
"page_title": "Welcome",
"heading": "Hello, World!",
"message": "This demonstrates template inheritance."
}))
System.print("\n--- Loop with Index ---")
var list = env.getTemplate("list.html")
System.print(list.render({"items": ["Apple", "Banana", "Cherry"]}))
System.print("\n--- Empty List (else clause) ---")
System.print(list.render({"items": []}))
System.print("\n--- Macros ---")
var cards = env.getTemplate("user_card.html")
System.print(cards.render({
"users": [
{"name": "alice smith", "email": "alice@example.com", "age": 28},
{"name": "bob jones", "age": 35}
]
}))
System.print("\n--- Batch Filter ---")
var table = env.getTemplate("table.html")
System.print(table.render({"data": [1, 2, 3, 4, 5, 6, 7, 8, 9]}))
System.print("\n--- Conditionals ---")
var cond = env.getTemplate("conditional.html")
System.print("Admin: " + cond.render({"user": {"role": "admin"}}))
System.print("Mod: " + cond.render({"user": {"role": "moderator"}}))
System.print("User: " + cond.render({"user": {"role": "guest"}}))
System.print("\n--- Filters Demo ---")
var filters = env.getTemplate("filters.html")
System.print(filters.render({"text": " Hello World! "}))
System.print("\n--- From String ---")
var inline = env.fromString("{\% for n in range(5) \%}{{ n }}{\% if not loop.last \%}, {\% endif \%}{\% endfor \%}")
System.print(inline.render({}))
System.print("\n--- Nested Data Access ---")
var nested = env.fromString("{{ users[0].address.city }}")
System.print(nested.render({
"users": [
{"name": "Test", "address": {"city": "New York", "zip": "10001"}}
]
}))
System.print("\n--- Expression Tests ---")
var tests = env.fromString("{\% if items is sequence \%}Sequence{\% endif \%} {\% if count is even \%}Even{\% endif \%} {\% if name is defined \%}Defined{\% endif \%}")
System.print(tests.render({"items": [1, 2, 3], "count": 4, "name": "test"}))
System.print("\n--- JSON Output ---")
var json = env.fromString("{{ data|tojson }}")
System.print(json.render({"data": {"name": "Test", "values": [1, 2, 3]}}))
System.print("\n--- Whitespace Control ---")
var ws = env.fromString("Items:{\% for i in items \%}\n {{- i -}}\n{\% endfor \%}")
System.print(ws.render({"items": ["a", "b", "c"]}))
System.print("\n--- Raw Block ---")
var raw = env.fromString("{\% raw \%}Use {{ variable }} for output{\% endraw \%}")
System.print(raw.render({}))
System.print("\n--- Set Variable ---")
var setVar = env.fromString("{\% set greeting = \"Hello\" \%}{\% set name = \"World\" \%}{{ greeting }}, {{ name }}!")
System.print(setVar.render({}))
System.print("\n--- Comments ---")
var comments = env.fromString("Visible{# This is hidden #}Text")
System.print(comments.render({}))
System.print("\n--- Complex Example ---")
var complex = env.fromString("{\% for product in products \%}{{ product.name|title }}: ${{ product.price|round(2) }}{\% if product.sale \%} (SALE!){\% endif \%}\n{\% endfor \%}Total: {{ products|map(\"price\")|sum|round(2) }}")
System.print(complex.render({
"products": [
{"name": "widget", "price": 9.99, "sale": true},
{"name": "gadget", "price": 24.50, "sale": false},
{"name": "gizmo", "price": 15.75, "sale": true}
]
}))

367
example/jinja_inheritance.wren vendored Normal file
View File

@ -0,0 +1,367 @@
// retoor <retoor@molodetz.nl>
import "jinja" for Environment, DictLoader
System.print("=" * 60)
System.print("JINJA TEMPLATE INHERITANCE - Extends, Blocks, and Includes")
System.print("=" * 60)
var templates = {
"base.html": "<!DOCTYPE html>
<html lang=\"en\">
<head>
<meta charset=\"UTF-8\">
<title>{\% block title \%}Default Title{\% endblock \%}</title>
<style>
{\% block styles \%}
body { font-family: sans-serif; margin: 20px; }
{\% endblock \%}
</style>
</head>
<body>
<header>
{\% block header \%}
<h1>My Website</h1>
<nav>{\% block nav \%}<a href=\"/\">Home</a>{\% endblock \%}</nav>
{\% endblock \%}
</header>
<main>
{\% block content \%}
<p>Default content goes here.</p>
{\% endblock \%}
</main>
<footer>
{\% block footer \%}
<p>Copyright 2024</p>
{\% endblock \%}
</footer>
</body>
</html>",
"page.html": "{\% extends \"base.html\" \%}
{\% block title \%}{{ page_title }} - My Website{\% endblock \%}
{\% block content \%}
<article>
<h2>{{ heading }}</h2>
<p>{{ content }}</p>
</article>
{\% endblock \%}",
"blog_base.html": "{\% extends \"base.html\" \%}
{\% block nav \%}
{{ super() }}
| <a href=\"/blog\">Blog</a>
| <a href=\"/about\">About</a>
{\% endblock \%}
{\% block styles \%}
{{ super() }}
.blog-post { border: 1px solid #ccc; padding: 10px; margin: 10px 0; }
.blog-meta { color: #666; font-size: 0.9em; }
{\% endblock \%}
{\% block content \%}
<section class=\"blog\">
{\% block blog_content \%}
<p>Blog posts will appear here.</p>
{\% endblock \%}
</section>
{\% endblock \%}",
"blog_post.html": "{\% extends \"blog_base.html\" \%}
{\% block title \%}{{ post.title }} - Blog{\% endblock \%}
{\% block blog_content \%}
<article class=\"blog-post\">
<h2>{{ post.title }}</h2>
<p class=\"blog-meta\">By {{ post.author }} on {{ post.date }}</p>
<div class=\"blog-body\">
{{ post.body }}
</div>
{\% if post.tags \%}
<p class=\"blog-tags\">Tags: {{ post.tags|join(\", \") }}</p>
{\% endif \%}
</article>
{\% endblock \%}",
"blog_list.html": "{\% extends \"blog_base.html\" \%}
{\% block title \%}All Posts - Blog{\% endblock \%}
{\% block blog_content \%}
<h2>Recent Posts</h2>
{\% for post in posts \%}
<article class=\"blog-post\">
<h3>{{ post.title }}</h3>
<p class=\"blog-meta\">{{ post.date }} by {{ post.author }}</p>
<p>{{ post.excerpt }}</p>
<a href=\"/blog/{{ post.slug }}\">Read more...</a>
</article>
{\% else \%}
<p>No posts yet.</p>
{\% endfor \%}
{\% endblock \%}",
"admin_base.html": "{\% extends \"base.html\" \%}
{\% block header \%}
<h1>Admin Panel</h1>
<nav>
<a href=\"/admin\">Dashboard</a> |
<a href=\"/admin/users\">Users</a> |
<a href=\"/admin/posts\">Posts</a> |
<a href=\"/admin/settings\">Settings</a>
</nav>
{\% endblock \%}
{\% block styles \%}
{{ super() }}
.admin-panel { background: #f5f5f5; padding: 20px; }
.admin-table { width: 100\%; border-collapse: collapse; }
.admin-table th, .admin-table td { border: 1px solid #ddd; padding: 8px; text-align: left; }
{\% endblock \%}
{\% block content \%}
<div class=\"admin-panel\">
{\% block admin_content \%}
<p>Welcome to the admin panel.</p>
{\% endblock \%}
</div>
{\% endblock \%}
{\% block footer \%}
<p>Admin Panel v1.0 | <a href=\"/\">Back to site</a></p>
{\% endblock \%}",
"admin_users.html": "{\% extends \"admin_base.html\" \%}
{\% block title \%}User Management - Admin{\% endblock \%}
{\% block admin_content \%}
<h2>User Management</h2>
<table class=\"admin-table\">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
<th>Role</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{\% for user in users \%}
<tr>
<td>{{ user.id }}</td>
<td>{{ user.name }}</td>
<td>{{ user.email }}</td>
<td>{{ user.role|title }}</td>
<td>{{ \"Active\" if user.active else \"Inactive\" }}</td>
</tr>
{\% endfor \%}
</tbody>
</table>
<p>Total users: {{ users|length }}</p>
{\% endblock \%}",
"sidebar.html": "<aside class=\"sidebar\">
<h3>{{ title|default(\"Sidebar\") }}</h3>
{\% if items \%}
<ul>
{\% for item in items \%}
<li><a href=\"{{ item.url }}\">{{ item.label }}</a></li>
{\% endfor \%}
</ul>
{\% endif \%}
</aside>",
"alert.html": "<div class=\"alert alert-{{ type|default(\"info\") }}\">
{\% if dismissible \%}<button class=\"close\">&times;</button>{\% endif \%}
<strong>{{ title }}:</strong> {{ message }}
</div>",
"page_with_includes.html": "{\% extends \"base.html\" \%}
{\% block title \%}Page with Includes{\% endblock \%}
{\% block content \%}
<div class=\"container\">
<div class=\"main-content\">
{\% include \"alert.html\" \%}
<h2>Main Content</h2>
<p>{{ content }}</p>
</div>
{\% include \"sidebar.html\" \%}
</div>
{\% endblock \%}",
"email_base.html": "From: {{ from_email }}
To: {{ to_email }}
Subject: {\% block subject \%}No Subject{\% endblock \%}
{\% block greeting \%}Hello,{\% endblock \%}
{\% block body \%}
This is the email body.
{\% endblock \%}
{\% block signature \%}
Best regards,
{{ sender_name }}
{\% endblock \%}",
"welcome_email.html": "{\% extends \"email_base.html\" \%}
{\% block subject \%}Welcome to Our Service, {{ user.name }}!{\% endblock \%}
{\% block greeting \%}Dear {{ user.name }},{\% endblock \%}
{\% block body \%}
Thank you for signing up for our service!
Your account details:
- Username: {{ user.username }}
- Email: {{ user.email }}
- Plan: {{ user.plan|title }}
To get started, please verify your email by clicking the link below:
{{ verification_url }}
If you have any questions, feel free to contact our support team.
{\% endblock \%}"
}
var env = Environment.new(DictLoader.new(templates))
System.print("\n" + "-" * 60)
System.print("1. BASIC PAGE (extends base.html)")
System.print("-" * 60)
System.print(env.getTemplate("page.html").render({
"page_title": "Welcome",
"heading": "Welcome to Our Website",
"content": "This is a simple page that extends the base template."
}))
System.print("\n" + "-" * 60)
System.print("2. BLOG POST (three-level inheritance: blog_post -> blog_base -> base)")
System.print("-" * 60)
System.print(env.getTemplate("blog_post.html").render({
"post": {
"title": "Getting Started with Jinja Templates",
"author": "Alice Smith",
"date": "2024-01-15",
"body": "Template inheritance is a powerful feature that allows you to build a base skeleton template containing common elements of your site and defines blocks that child templates can override.",
"tags": ["jinja", "templates", "tutorial"]
}
}))
System.print("\n" + "-" * 60)
System.print("3. BLOG LIST (loops within inherited template)")
System.print("-" * 60)
System.print(env.getTemplate("blog_list.html").render({
"posts": [
{"title": "First Post", "date": "2024-01-15", "author": "Alice", "excerpt": "This is my first post...", "slug": "first-post"},
{"title": "Second Post", "date": "2024-01-14", "author": "Bob", "excerpt": "Another great article...", "slug": "second-post"},
{"title": "Third Post", "date": "2024-01-13", "author": "Charlie", "excerpt": "More content here...", "slug": "third-post"}
]
}))
System.print("\n" + "-" * 60)
System.print("4. ADMIN USERS PAGE (admin template hierarchy)")
System.print("-" * 60)
System.print(env.getTemplate("admin_users.html").render({
"users": [
{"id": 1, "name": "Alice Smith", "email": "alice@example.com", "role": "admin", "active": true},
{"id": 2, "name": "Bob Jones", "email": "bob@example.com", "role": "editor", "active": true},
{"id": 3, "name": "Charlie Brown", "email": "charlie@example.com", "role": "user", "active": false},
{"id": 4, "name": "Diana Ross", "email": "diana@example.com", "role": "user", "active": true}
]
}))
System.print("\n" + "-" * 60)
System.print("5. PAGE WITH INCLUDES")
System.print("-" * 60)
System.print(env.getTemplate("page_with_includes.html").render({
"type": "warning",
"title": "Notice",
"message": "This page demonstrates the include feature.",
"dismissible": true,
"content": "This is the main content of the page.",
"title": "Quick Links",
"items": [
{"label": "Home", "url": "/"},
{"label": "About", "url": "/about"},
{"label": "Contact", "url": "/contact"}
]
}))
System.print("\n" + "-" * 60)
System.print("6. STANDALONE INCLUDE (sidebar)")
System.print("-" * 60)
System.print(env.getTemplate("sidebar.html").render({
"title": "Navigation",
"items": [
{"label": "Dashboard", "url": "/dashboard"},
{"label": "Profile", "url": "/profile"},
{"label": "Settings", "url": "/settings"},
{"label": "Logout", "url": "/logout"}
]
}))
System.print("\n" + "-" * 60)
System.print("7. EMAIL TEMPLATE (text-based inheritance)")
System.print("-" * 60)
System.print(env.getTemplate("welcome_email.html").render({
"from_email": "noreply@example.com",
"to_email": "newuser@example.com",
"sender_name": "The Example Team",
"user": {
"name": "John Doe",
"username": "johndoe",
"email": "newuser@example.com",
"plan": "premium"
},
"verification_url": "https://example.com/verify?token=abc123"
}))
System.print("\n" + "-" * 60)
System.print("8. SUPER() DEMONSTRATION")
System.print("-" * 60)
var superDemo = {
"level1": "[L1-START]{\% block content \%}Level1-Content{\% endblock \%}[L1-END]",
"level2": "{\% extends \"level1\" \%}{\% block content \%}{{ super() }}+Level2{\% endblock \%}",
"level3": "{\% extends \"level2\" \%}{\% block content \%}{{ super() }}+Level3{\% endblock \%}"
}
var superEnv = Environment.new(DictLoader.new(superDemo))
System.print("Level 1: " + superEnv.getTemplate("level1").render({}))
System.print("Level 2: " + superEnv.getTemplate("level2").render({}))
System.print("Level 3: " + superEnv.getTemplate("level3").render({}))
System.print("\n" + "-" * 60)
System.print("9. MULTIPLE BLOCKS INHERITANCE")
System.print("-" * 60)
var multiBlock = {
"base": "A:{\% block a \%}base-a{\% endblock \%} B:{\% block b \%}base-b{\% endblock \%} C:{\% block c \%}base-c{\% endblock \%}",
"override_a": "{\% extends \"base\" \%}{\% block a \%}OVERRIDE-A{\% endblock \%}",
"override_bc": "{\% extends \"base\" \%}{\% block b \%}OVERRIDE-B{\% endblock \%}{\% block c \%}OVERRIDE-C{\% endblock \%}",
"override_all": "{\% extends \"base\" \%}{\% block a \%}NEW-A{\% endblock \%}{\% block b \%}NEW-B{\% endblock \%}{\% block c \%}NEW-C{\% endblock \%}"
}
var multiEnv = Environment.new(DictLoader.new(multiBlock))
System.print("Base: " + multiEnv.getTemplate("base").render({}))
System.print("Override A: " + multiEnv.getTemplate("override_a").render({}))
System.print("Override BC:" + multiEnv.getTemplate("override_bc").render({}))
System.print("Override All:" + multiEnv.getTemplate("override_all").render({}))
System.print("\n" + "=" * 60)
System.print("End of Inheritance Demo")
System.print("=" * 60)

363
example/jinja_macros.wren vendored Normal file
View File

@ -0,0 +1,363 @@
// retoor <retoor@molodetz.nl>
import "jinja" for Environment, DictLoader
System.print("=" * 60)
System.print("JINJA MACROS - Reusable Template Components")
System.print("=" * 60)
var templates = {
"macro_simple": "{\% macro greet(name) \%}Hello, {{ name }}!{\% endmacro \%}
{{ greet(\"World\") }}
{{ greet(\"Alice\") }}
{{ greet(\"Bob\") }}",
"macro_default_args": "{\% macro button(text, type=\"primary\", size=\"medium\", disabled=false) \%}
<button class=\"btn btn-{{ type }} btn-{{ size }}\"{{ \" disabled\" if disabled else \"\" }}>{{ text }}</button>
{\% endmacro \%}
{{ button(\"Submit\") }}
{{ button(\"Cancel\", \"secondary\") }}
{{ button(\"Delete\", \"danger\", \"small\") }}
{{ button(\"Disabled\", \"primary\", \"medium\", true) }}",
"macro_with_content": "{\% macro card(title, footer=\"\") \%}
<div class=\"card\">
<div class=\"card-header\">{{ title }}</div>
<div class=\"card-body\">
{\% for item in content \%}{{ item }}{\% endfor \%}
</div>
{\% if footer \%}<div class=\"card-footer\">{{ footer }}</div>{\% endif \%}
</div>
{\% endmacro \%}
{{ card(\"My Card\", \"Footer text\") }}",
"macro_input": "{\% macro input(name, type, value, placeholder, required, label) \%}
<div class=\"form-group\">
{\% if label \%}<label for=\"{{ name }}\">{{ label }}{\% if required \%} *{\% endif \%}</label>{\% endif \%}
<input type=\"{{ type|default(\"text\") }}\" id=\"{{ name }}\" name=\"{{ name }}\" value=\"{{ value }}\" placeholder=\"{{ placeholder }}\"{\% if required \%} required{\% endif \%} class=\"form-control\">
</div>
{\% endmacro \%}
{{ input(\"username\", \"text\", \"\", \"Enter username\", true, \"Username\") }}
{{ input(\"email\", \"email\", \"\", \"\", true, \"Email Address\") }}
{{ input(\"password\", \"password\", \"\", \"\", true, \"Password\") }}
{{ input(\"bio\", \"text\", \"\", \"Tell us about yourself\", false, \"\") }}",
"macro_select": "{\% macro select(name, options, selected=\"\", label=\"\", required=false) \%}
<div class=\"form-group\">
{\% if label \%}<label for=\"{{ name }}\">{{ label }}{\% if required \%} *{\% endif \%}</label>{\% endif \%}
<select id=\"{{ name }}\" name=\"{{ name }}\" class=\"form-control\"{\% if required \%} required{\% endif \%}>
<option value=\"\">-- Select --</option>
{\% for opt in options \%}
<option value=\"{{ opt.value }}\"{{ \" selected\" if opt.value == selected else \"\" }}>{{ opt.label }}</option>
{\% endfor \%}
</select>
</div>
{\% endmacro \%}
{{ select(\"country\", countries, \"us\", \"Country\", true) }}",
"macro_table": "{\% macro table(headers, rows, striped=true, bordered=true) \%}
<table class=\"table{\% if striped \%} table-striped{\% endif \%}{\% if bordered \%} table-bordered{\% endif \%}\">
<thead>
<tr>
{\% for header in headers \%}
<th>{{ header }}</th>
{\% endfor \%}
</tr>
</thead>
<tbody>
{\% for row in rows \%}
<tr>
{\% for cell in row \%}
<td>{{ cell }}</td>
{\% endfor \%}
</tr>
{\% else \%}
<tr><td colspan=\"{{ headers|length }}\">No data available</td></tr>
{\% endfor \%}
</tbody>
</table>
{\% endmacro \%}
{{ table(headers, data) }}",
"macro_pagination": "{\% macro pagination(current, total, url_pattern=\"?page=\") \%}
<nav class=\"pagination\">
{\% if current > 1 \%}
<a href=\"{{ url_pattern }}{{ current - 1 }}\" class=\"prev\">&laquo; Previous</a>
{\% endif \%}
{\% for page in range(1, total + 1) \%}
{\% if page == current \%}
<span class=\"current\">{{ page }}</span>
{\% else \%}
<a href=\"{{ url_pattern }}{{ page }}\">{{ page }}</a>
{\% endif \%}
{\% endfor \%}
{\% if current < total \%}
<a href=\"{{ url_pattern }}{{ current + 1 }}\" class=\"next\">Next &raquo;</a>
{\% endif \%}
</nav>
{\% endmacro \%}
{{ pagination(3, 5) }}",
"macro_breadcrumb": "{\% macro breadcrumb(items) \%}
<nav class=\"breadcrumb\">
{\% for item in items \%}
{\% if loop.last \%}
<span class=\"current\">{{ item.label }}</span>
{\% else \%}
<a href=\"{{ item.url }}\">{{ item.label }}</a>
<span class=\"separator\">/</span>
{\% endif \%}
{\% endfor \%}
</nav>
{\% endmacro \%}
{{ breadcrumb(crumbs) }}",
"macro_alert": "{\% macro alert(message, type=\"info\", title=\"\", dismissible=true) \%}
<div class=\"alert alert-{{ type }}{\% if dismissible \%} alert-dismissible{\% endif \%}\" role=\"alert\">
{\% if dismissible \%}<button type=\"button\" class=\"close\">&times;</button>{\% endif \%}
{\% if title \%}<strong>{{ title }}:</strong> {\% endif \%}{{ message }}
</div>
{\% endmacro \%}
{{ alert(\"Operation completed successfully!\", \"success\", \"Success\") }}
{{ alert(\"Please check your input.\", \"warning\", \"Warning\") }}
{{ alert(\"An error occurred.\", \"danger\", \"Error\") }}
{{ alert(\"Here is some helpful information.\", \"info\") }}",
"macro_user_card": "{\% macro user_card(user) \%}
<div class=\"user-card\">
<div class=\"avatar\">
<img src=\"{{ user.avatar|default(\"/default-avatar.png\") }}\" alt=\"{{ user.name }}\">
</div>
<div class=\"info\">
<h4>{{ user.name|title }}</h4>
<p class=\"role\">{{ user.role|default(\"Member\")|title }}</p>
{\% if user.email \%}<p class=\"email\">{{ user.email }}</p>{\% endif \%}
{\% if user.bio \%}<p class=\"bio\">{{ user.bio|truncate(100) }}</p>{\% endif \%}
<div class=\"stats\">
<span>{{ user.posts|default(0) }} posts</span>
<span>{{ user.followers|default(0) }} followers</span>
</div>
</div>
</div>
{\% endmacro \%}
{\% for user in users \%}
{{ user_card(user) }}
{\% endfor \%}",
"macro_nav_menu": "{\% macro nav_menu(items, current_path) \%}
<nav class=\"main-nav\">
<ul class=\"nav-list\">
{\% for item in items \%}
<li class=\"nav-item{\% if item.url == current_path \%} active{\% endif \%}{\% if item.children \%} has-children{\% endif \%}\">
<a href=\"{{ item.url }}\" class=\"nav-link\">
{\% if item.icon \%}<i class=\"icon-{{ item.icon }}\"></i> {\% endif \%}
{{ item.label }}
</a>
{\% if item.children \%}
<ul class=\"sub-menu\">
{\% for child in item.children \%}
<li class=\"nav-item{\% if child.url == current_path \%} active{\% endif \%}\">
<a href=\"{{ child.url }}\" class=\"nav-link\">{{ child.label }}</a>
</li>
{\% endfor \%}
</ul>
{\% endif \%}
</li>
{\% endfor \%}
</ul>
</nav>
{\% endmacro \%}
{{ nav_menu(menu_items, \"/products\") }}",
"macro_form": "{\% macro form_start(action, method=\"POST\", id=\"\", classes=\"\") \%}
<form action=\"{{ action }}\" method=\"{{ method }}\"{\% if id \%} id=\"{{ id }}\"{\% endif \%}{\% if classes \%} class=\"{{ classes }}\"{\% endif \%}>
{\% endmacro \%}
{\% macro form_end() \%}
</form>
{\% endmacro \%}
{\% macro hidden(name, value) \%}
<input type=\"hidden\" name=\"{{ name }}\" value=\"{{ value }}\">
{\% endmacro \%}
{\% macro submit(text=\"Submit\", type=\"primary\") \%}
<button type=\"submit\" class=\"btn btn-{{ type }}\">{{ text }}</button>
{\% endmacro \%}
{{ form_start(\"/api/contact\", \"POST\", \"contact-form\", \"contact-form\") }}
{{ hidden(\"csrf_token\", token) }}
<div class=\"form-row\">
<label>Name: <input type=\"text\" name=\"name\" required></label>
</div>
<div class=\"form-row\">
<label>Email: <input type=\"email\" name=\"email\" required></label>
</div>
<div class=\"form-row\">
<label>Message: <textarea name=\"message\" rows=\"4\"></textarea></label>
</div>
{{ submit(\"Send Message\", \"success\") }}
{{ form_end() }}",
"macro_star_rating": "{\% macro stars(rating, max=5) \%}
<div class=\"star-rating\" title=\"{{ rating }}/{{ max }}\">
{\% for i in range(1, max + 1) \%}
{\% if i <= rating \%}
<span class=\"star filled\">&#9733;</span>
{\% else \%}
<span class=\"star empty\">&#9734;</span>
{\% endif \%}
{\% endfor \%}
</div>
{\% endmacro \%}
Rating 1: {{ stars(1) }}
Rating 3: {{ stars(3) }}
Rating 5: {{ stars(5) }}
Rating 4/10: {{ stars(4, 10) }}",
"macro_progress": "{\% macro progress(value, max=100, label=\"\", color=\"primary\") \%}
{\% set percentage = (value / max * 100)|round \%}
<div class=\"progress-container\">
{\% if label \%}<label>{{ label }}: {{ value }}/{{ max }}</label>{\% endif \%}
<div class=\"progress\">
<div class=\"progress-bar bg-{{ color }}\" style=\"width: {{ percentage }}\%\">
{{ percentage }}\%
</div>
</div>
</div>
{\% endmacro \%}
{{ progress(25, 100, \"Downloads\") }}
{{ progress(75, 100, \"Upload\", \"success\") }}
{{ progress(50, 100, \"Processing\", \"warning\") }}
{{ progress(3, 10, \"Steps\", \"info\") }}"
}
var env = Environment.new(DictLoader.new(templates))
System.print("\n" + "-" * 60)
System.print("1. SIMPLE MACRO")
System.print("-" * 60)
System.print(env.getTemplate("macro_simple").render({}))
System.print("\n" + "-" * 60)
System.print("2. MACRO WITH DEFAULT ARGUMENTS")
System.print("-" * 60)
System.print(env.getTemplate("macro_default_args").render({}))
System.print("\n" + "-" * 60)
System.print("3. FORM INPUT MACRO")
System.print("-" * 60)
System.print(env.getTemplate("macro_input").render({}))
System.print("\n" + "-" * 60)
System.print("4. SELECT DROPDOWN MACRO")
System.print("-" * 60)
System.print(env.getTemplate("macro_select").render({
"countries": [
{"value": "us", "label": "United States"},
{"value": "uk", "label": "United Kingdom"},
{"value": "ca", "label": "Canada"},
{"value": "au", "label": "Australia"}
]
}))
System.print("\n" + "-" * 60)
System.print("5. TABLE MACRO")
System.print("-" * 60)
System.print(env.getTemplate("macro_table").render({
"headers": ["ID", "Name", "Email", "Status"],
"data": [
[1, "Alice", "alice@example.com", "Active"],
[2, "Bob", "bob@example.com", "Active"],
[3, "Charlie", "charlie@example.com", "Inactive"]
]
}))
System.print("\n" + "-" * 60)
System.print("6. PAGINATION MACRO")
System.print("-" * 60)
System.print(env.getTemplate("macro_pagination").render({}))
System.print("\n" + "-" * 60)
System.print("7. BREADCRUMB MACRO")
System.print("-" * 60)
System.print(env.getTemplate("macro_breadcrumb").render({
"crumbs": [
{"label": "Home", "url": "/"},
{"label": "Products", "url": "/products"},
{"label": "Electronics", "url": "/products/electronics"},
{"label": "Laptops", "url": "/products/electronics/laptops"}
]
}))
System.print("\n" + "-" * 60)
System.print("8. ALERT MACRO")
System.print("-" * 60)
System.print(env.getTemplate("macro_alert").render({}))
System.print("\n" + "-" * 60)
System.print("9. USER CARD MACRO")
System.print("-" * 60)
System.print(env.getTemplate("macro_user_card").render({
"users": [
{
"name": "alice smith",
"role": "admin",
"email": "alice@example.com",
"avatar": "/avatars/alice.jpg",
"bio": "Software engineer with 10 years of experience in web development and cloud architecture.",
"posts": 42,
"followers": 1250
},
{
"name": "bob jones",
"email": "bob@example.com",
"posts": 15,
"followers": 300
}
]
}))
System.print("\n" + "-" * 60)
System.print("10. NAVIGATION MENU MACRO (RECURSIVE)")
System.print("-" * 60)
System.print(env.getTemplate("macro_nav_menu").render({
"menu_items": [
{"label": "Home", "url": "/", "icon": "home"},
{
"label": "Products",
"url": "/products",
"icon": "box",
"children": [
{"label": "Electronics", "url": "/products/electronics"},
{"label": "Clothing", "url": "/products/clothing"},
{"label": "Books", "url": "/products/books"}
]
},
{"label": "About", "url": "/about", "icon": "info"},
{"label": "Contact", "url": "/contact", "icon": "envelope"}
]
}))
System.print("\n" + "-" * 60)
System.print("11. FORM HELPERS")
System.print("-" * 60)
System.print(env.getTemplate("macro_form").render({
"token": "abc123xyz789"
}))
System.print("\n" + "-" * 60)
System.print("12. STAR RATING MACRO")
System.print("-" * 60)
System.print(env.getTemplate("macro_star_rating").render({}))
System.print("\n" + "-" * 60)
System.print("13. PROGRESS BAR MACRO")
System.print("-" * 60)
System.print(env.getTemplate("macro_progress").render({}))
System.print("\n" + "=" * 60)
System.print("End of Macros Demo")
System.print("=" * 60)

698
example/jinja_real_world.wren vendored Normal file
View File

@ -0,0 +1,698 @@
// retoor <retoor@molodetz.nl>
import "jinja" for Environment, DictLoader
System.print("=" * 60)
System.print("JINJA REAL-WORLD EXAMPLES")
System.print("=" * 60)
var templates = {
"invoice.html": "<!DOCTYPE html>
<html>
<head>
<title>Invoice #{{ invoice.number }}</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.header { border-bottom: 2px solid #333; padding-bottom: 20px; }
.company-info { float: left; }
.invoice-info { float: right; text-align: right; }
.clear { clear: both; }
.addresses { margin: 30px 0; }
.from, .to { width: 45\%; display: inline-block; vertical-align: top; }
table { width: 100\%; border-collapse: collapse; margin: 20px 0; }
th, td { border: 1px solid #ddd; padding: 10px; text-align: left; }
th { background: #f5f5f5; }
.amount { text-align: right; }
.totals { width: 300px; float: right; }
.totals td { border: none; padding: 5px 10px; }
.totals .total { font-weight: bold; font-size: 1.2em; border-top: 2px solid #333; }
.footer { margin-top: 50px; padding-top: 20px; border-top: 1px solid #ddd; font-size: 0.9em; color: #666; }
</style>
</head>
<body>
<div class=\"header\">
<div class=\"company-info\">
<h1>{{ company.name }}</h1>
<p>{{ company.address }}<br>
{{ company.city }}, {{ company.country }}<br>
Tel: {{ company.phone }}<br>
Email: {{ company.email }}</p>
</div>
<div class=\"invoice-info\">
<h2>INVOICE</h2>
<p><strong>Invoice #:</strong> {{ invoice.number }}<br>
<strong>Date:</strong> {{ invoice.date }}<br>
<strong>Due Date:</strong> {{ invoice.due_date }}<br>
<strong>Status:</strong> {{ invoice.status|upper }}</p>
</div>
<div class=\"clear\"></div>
</div>
<div class=\"addresses\">
<div class=\"from\">
<h3>From:</h3>
<p>{{ company.name }}<br>
{{ company.address }}<br>
{{ company.city }}, {{ company.country }}</p>
</div>
<div class=\"to\">
<h3>Bill To:</h3>
<p><strong>{{ client.name }}</strong><br>
{{ client.address }}<br>
{{ client.city }}, {{ client.country }}<br>
{\% if client.vat \%}VAT: {{ client.vat }}{\% endif \%}</p>
</div>
</div>
<table>
<thead>
<tr>
<th>#</th>
<th>Description</th>
<th>Quantity</th>
<th class=\"amount\">Unit Price</th>
<th class=\"amount\">Amount</th>
</tr>
</thead>
<tbody>
{\% for item in invoice.items \%}
<tr>
<td>{{ loop.index }}</td>
<td>{{ item.description }}</td>
<td>{{ item.quantity }}</td>
<td class=\"amount\">${{ item.price|round(2) }}</td>
<td class=\"amount\">${{ (item.quantity * item.price)|round(2) }}</td>
</tr>
{\% endfor \%}
</tbody>
</table>
<table class=\"totals\">
<tr>
<td>Subtotal:</td>
<td class=\"amount\">${{ invoice.subtotal|round(2) }}</td>
</tr>
{\% if invoice.discount \%}
<tr>
<td>Discount ({{ invoice.discount_percent }}\%):</td>
<td class=\"amount\">-${{ invoice.discount|round(2) }}</td>
</tr>
{\% endif \%}
<tr>
<td>Tax ({{ invoice.tax_rate }}\%):</td>
<td class=\"amount\">${{ invoice.tax|round(2) }}</td>
</tr>
<tr class=\"total\">
<td>Total:</td>
<td class=\"amount\">${{ invoice.total|round(2) }}</td>
</tr>
</table>
<div class=\"clear\"></div>
{\% if invoice.notes \%}
<div class=\"notes\">
<h3>Notes:</h3>
<p>{{ invoice.notes }}</p>
</div>
{\% endif \%}
<div class=\"footer\">
<p>Payment Terms: {{ invoice.payment_terms|default(\"Net 30\") }}</p>
<p>Please make payment to: {{ company.bank_account }}</p>
<p>Thank you for your business!</p>
</div>
</body>
</html>",
"report.md": "# {{ report.title }}
**Generated:** {{ report.date }}
**Author:** {{ report.author }}
**Period:** {{ report.period_start }} to {{ report.period_end }}
---
## Executive Summary
{{ report.summary }}
## Key Metrics
| Metric | Value | Change |
|--------|-------|--------|
{\% for metric in report.metrics \%}
| {{ metric.name }} | {{ metric.value }} | {{ \"+\" if metric.change > 0 else \"\" }}{{ metric.change }}\% |
{\% endfor \%}
## Performance by Category
{\% for category in report.categories \%}
### {{ category.name }}
- **Revenue:** ${{ category.revenue|round(2) }}
- **Units Sold:** {{ category.units }}
- **Growth:** {{ category.growth }}\%
{\% if category.top_products \%}
Top products:
{\% for product in category.top_products \%}
{{ loop.index }}. {{ product.name }} (${{ product.revenue|round(2) }})
{\% endfor \%}
{\% endif \%}
{\% endfor \%}
## Recommendations
{\% for rec in report.recommendations \%}
{{ loop.index }}. **{{ rec.priority|upper }}**: {{ rec.text }}
{\% endfor \%}
---
*This report was automatically generated.*",
"api_response.json": "{
\"status\": \"{{ response.status }}\",
\"code\": {{ response.code }},
\"message\": \"{{ response.message }}\",
\"timestamp\": \"{{ response.timestamp }}\",
\"data\": {
{\% if response.data.user \%}
\"user\": {
\"id\": {{ response.data.user.id }},
\"name\": \"{{ response.data.user.name }}\",
\"email\": \"{{ response.data.user.email }}\",
\"role\": \"{{ response.data.user.role }}\",
\"created_at\": \"{{ response.data.user.created_at }}\"
},
{\% endif \%}
{\% if response.data.items \%}
\"items\": [
{\% for item in response.data.items \%}
{
\"id\": {{ item.id }},
\"name\": \"{{ item.name }}\",
\"price\": {{ item.price }},
\"available\": {{ \"true\" if item.available else \"false\" }}
}{{ \",\" if not loop.last else \"\" }}
{\% endfor \%}
],
\"total\": {{ response.data.items|length }},
{\% endif \%}
\"pagination\": {
\"page\": {{ response.pagination.page }},
\"per_page\": {{ response.pagination.per_page }},
\"total_pages\": {{ response.pagination.total_pages }},
\"total_items\": {{ response.pagination.total_items }}
}
}
}",
"config.yaml": "# Application Configuration
# Generated: {{ timestamp }}
app:
name: {{ config.app.name }}
version: {{ config.app.version }}
environment: {{ config.app.environment }}
debug: {{ \"true\" if config.app.debug else \"false\" }}
server:
host: {{ config.server.host }}
port: {{ config.server.port }}
workers: {{ config.server.workers }}
timeout: {{ config.server.timeout }}
database:
driver: {{ config.database.driver }}
host: {{ config.database.host }}
port: {{ config.database.port }}
name: {{ config.database.name }}
user: {{ config.database.user }}
pool_size: {{ config.database.pool_size }}
cache:
driver: {{ config.cache.driver }}
host: {{ config.cache.host }}
port: {{ config.cache.port }}
ttl: {{ config.cache.ttl }}
logging:
level: {{ config.logging.level }}
format: \"{{ config.logging.format }}\"
handlers:
{\% for handler in config.logging.handlers \%}
- type: {{ handler.type }}
{\% if handler.path \%}path: {{ handler.path }}{\% endif \%}
{\% if handler.max_size \%}max_size: {{ handler.max_size }}{\% endif \%}
{\% endfor \%}
features:
{\% for feature, enabled in config.features \%}
{{ feature }}: {{ \"true\" if enabled else \"false\" }}
{\% endfor \%}",
"sitemap.xml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">
{\% for page in pages \%}
<url>
<loc>{{ base_url }}{{ page.url }}</loc>
<lastmod>{{ page.last_modified }}</lastmod>
<changefreq>{{ page.change_freq|default(\"weekly\") }}</changefreq>
<priority>{{ page.priority|default(\"0.5\") }}</priority>
</url>
{\% endfor \%}
</urlset>",
"dockerfile": "# Dockerfile for {{ app.name }}
# Generated automatically - do not edit manually
FROM {{ app.base_image }}
LABEL maintainer=\"{{ app.maintainer }}\"
LABEL version=\"{{ app.version }}\"
# Set environment variables
{\% for key, value in app.env_vars \%}
ENV {{ key }}={{ value }}
{\% endfor \%}
# Set working directory
WORKDIR {{ app.workdir }}
# Install system dependencies
{\% if app.system_packages \%}
RUN apt-get update && apt-get install -y \\
{\% for pkg in app.system_packages \%}
{{ pkg }}{\% if not loop.last \%} \\{\% endif \%}
{\% endfor \%}
&& rm -rf /var/lib/apt/lists/*
{\% endif \%}
# Copy dependency files
{\% for file in app.dependency_files \%}
COPY {{ file }} .
{\% endfor \%}
# Install application dependencies
{\% if app.install_cmd \%}
RUN {{ app.install_cmd }}
{\% endif \%}
# Copy application code
COPY . .
# Expose port
EXPOSE {{ app.port }}
# Health check
{\% if app.healthcheck \%}
HEALTHCHECK --interval={{ app.healthcheck.interval }} --timeout={{ app.healthcheck.timeout }} \\
CMD {{ app.healthcheck.cmd }}
{\% endif \%}
# Run the application
CMD [\"{{ app.cmd }}\"]",
"readme.md": "# {{ project.name }}
{{ project.description }}
## Features
{\% for feature in project.features \%}
- {{ feature }}
{\% endfor \%}
## Installation
```bash
{{ project.install_cmd }}
```
## Quick Start
```{{ project.code_lang }}
{{ project.quick_start }}
```
## Configuration
| Option | Type | Default | Description |
|--------|------|---------|-------------|
{\% for opt in project.config_options \%}
| `{{ opt.name }}` | {{ opt.type }} | {{ opt.default|default(\"N/A\") }} | {{ opt.description }} |
{\% endfor \%}
## API Reference
{\% for endpoint in project.api_endpoints \%}
### {{ endpoint.method }} {{ endpoint.path }}
{{ endpoint.description }}
**Parameters:**
{\% for param in endpoint.params \%}
- `{{ param.name }}` ({{ param.type }}{\% if param.required \%}, required{\% endif \%}): {{ param.description }}
{\% endfor \%}
**Response:**
```json
{{ endpoint.response_example }}
```
{\% endfor \%}
## License
{{ project.license }}
## Author
{{ project.author }} - {{ project.author_email }}",
"changelog.md": "# Changelog
All notable changes to {{ project_name }} will be documented in this file.
{\% for release in releases \%}
## [{{ release.version }}] - {{ release.date }}
{\% if release.breaking \%}
### Breaking Changes
{\% for item in release.breaking \%}
- {{ item }}
{\% endfor \%}
{\% endif \%}
{\% if release.added \%}
### Added
{\% for item in release.added \%}
- {{ item }}
{\% endfor \%}
{\% endif \%}
{\% if release.changed \%}
### Changed
{\% for item in release.changed \%}
- {{ item }}
{\% endfor \%}
{\% endif \%}
{\% if release.deprecated \%}
### Deprecated
{\% for item in release.deprecated \%}
- {{ item }}
{\% endfor \%}
{\% endif \%}
{\% if release.removed \%}
### Removed
{\% for item in release.removed \%}
- {{ item }}
{\% endfor \%}
{\% endif \%}
{\% if release.fixed \%}
### Fixed
{\% for item in release.fixed \%}
- {{ item }}
{\% endfor \%}
{\% endif \%}
{\% if release.security \%}
### Security
{\% for item in release.security \%}
- {{ item }}
{\% endfor \%}
{\% endif \%}
{\% endfor \%}"
}
var env = Environment.new(DictLoader.new(templates))
System.print("\n" + "-" * 60)
System.print("1. INVOICE (HTML)")
System.print("-" * 60)
System.print(env.getTemplate("invoice.html").render({
"company": {
"name": "Acme Corporation",
"address": "123 Business Street",
"city": "New York, NY 10001",
"country": "USA",
"phone": "+1 (555) 123-4567",
"email": "billing@acme.com",
"bank_account": "Bank of America - 1234567890"
},
"client": {
"name": "TechStart Inc.",
"address": "456 Innovation Ave",
"city": "San Francisco, CA 94102",
"country": "USA",
"vat": "US123456789"
},
"invoice": {
"number": "INV-2024-001",
"date": "2024-01-15",
"due_date": "2024-02-14",
"status": "pending",
"items": [
{"description": "Web Development Services", "quantity": 40, "price": 150},
{"description": "UI/UX Design", "quantity": 20, "price": 125},
{"description": "Server Setup & Configuration", "quantity": 8, "price": 200},
{"description": "Monthly Maintenance (Jan)", "quantity": 1, "price": 500}
],
"subtotal": 9600,
"discount": 480,
"discount_percent": 5,
"tax_rate": 8.5,
"tax": 775.2,
"total": 9895.2,
"notes": "Thank you for choosing Acme Corporation. Payment is due within 30 days.",
"payment_terms": "Net 30"
}
}))
System.print("\n" + "-" * 60)
System.print("2. BUSINESS REPORT (Markdown)")
System.print("-" * 60)
System.print(env.getTemplate("report.md").render({
"report": {
"title": "Q4 2024 Sales Report",
"date": "2024-01-05",
"author": "Analytics Team",
"period_start": "2024-10-01",
"period_end": "2024-12-31",
"summary": "Q4 2024 showed strong performance with overall revenue growth of 15\% compared to Q3. The Electronics category led growth while we saw moderate decline in Accessories.",
"metrics": [
{"name": "Total Revenue", "value": "$1.2M", "change": 15},
{"name": "Orders", "value": "8,450", "change": 12},
{"name": "Avg Order Value", "value": "$142", "change": 3},
{"name": "Customer Retention", "value": "78\%", "change": -2}
],
"categories": [
{
"name": "Electronics",
"revenue": 650000,
"units": 4200,
"growth": 22,
"top_products": [
{"name": "Laptop Pro X", "revenue": 180000},
{"name": "Wireless Headphones", "revenue": 95000},
{"name": "Smart Watch V3", "revenue": 72000}
]
},
{
"name": "Clothing",
"revenue": 380000,
"units": 12500,
"growth": 8,
"top_products": [
{"name": "Winter Jacket", "revenue": 65000},
{"name": "Running Shoes", "revenue": 48000}
]
}
],
"recommendations": [
{"priority": "high", "text": "Increase inventory for Electronics category to meet Q1 demand"},
{"priority": "medium", "text": "Launch targeted campaign for Accessories to reverse decline"},
{"priority": "low", "text": "Consider expanding Clothing line with spring collection"}
]
}
}))
System.print("\n" + "-" * 60)
System.print("3. API RESPONSE (JSON)")
System.print("-" * 60)
System.print(env.getTemplate("api_response.json").render({
"response": {
"status": "success",
"code": 200,
"message": "Data retrieved successfully",
"timestamp": "2024-01-15T10:30:00Z",
"data": {
"user": {
"id": 12345,
"name": "John Doe",
"email": "john@example.com",
"role": "admin",
"created_at": "2023-06-15T08:00:00Z"
},
"items": [
{"id": 1, "name": "Widget A", "price": 29.99, "available": true},
{"id": 2, "name": "Widget B", "price": 49.99, "available": true},
{"id": 3, "name": "Widget C", "price": 19.99, "available": false}
]
},
"pagination": {
"page": 1,
"per_page": 10,
"total_pages": 5,
"total_items": 47
}
}
}))
System.print("\n" + "-" * 60)
System.print("4. CONFIG FILE (YAML)")
System.print("-" * 60)
System.print(env.getTemplate("config.yaml").render({
"timestamp": "2024-01-15 10:30:00",
"config": {
"app": {
"name": "MyApp",
"version": "2.1.0",
"environment": "production",
"debug": false
},
"server": {
"host": "0.0.0.0",
"port": 8080,
"workers": 4,
"timeout": 30
},
"database": {
"driver": "postgresql",
"host": "db.example.com",
"port": 5432,
"name": "myapp_prod",
"user": "app_user",
"pool_size": 20
},
"cache": {
"driver": "redis",
"host": "cache.example.com",
"port": 6379,
"ttl": 3600
},
"logging": {
"level": "INFO",
"format": "\%(asctime)s - \%(name)s - \%(levelname)s - \%(message)s",
"handlers": [
{"type": "console"},
{"type": "file", "path": "/var/log/myapp.log", "max_size": "100MB"}
]
},
"features": {
"new_dashboard": true,
"beta_api": false,
"analytics": true
}
}
}))
System.print("\n" + "-" * 60)
System.print("5. SITEMAP (XML)")
System.print("-" * 60)
System.print(env.getTemplate("sitemap.xml").render({
"base_url": "https://example.com",
"pages": [
{"url": "/", "last_modified": "2024-01-15", "change_freq": "daily", "priority": "1.0"},
{"url": "/about", "last_modified": "2024-01-10", "change_freq": "monthly", "priority": "0.8"},
{"url": "/products", "last_modified": "2024-01-14", "change_freq": "weekly", "priority": "0.9"},
{"url": "/blog", "last_modified": "2024-01-15", "change_freq": "daily", "priority": "0.7"},
{"url": "/contact", "last_modified": "2024-01-01", "change_freq": "yearly", "priority": "0.5"}
]
}))
System.print("\n" + "-" * 60)
System.print("6. DOCKERFILE")
System.print("-" * 60)
System.print(env.getTemplate("dockerfile").render({
"app": {
"name": "my-python-app",
"base_image": "python:3.11-slim",
"maintainer": "dev@example.com",
"version": "1.0.0",
"workdir": "/app",
"port": 8000,
"env_vars": {
"PYTHONUNBUFFERED": "1",
"PYTHONDONTWRITEBYTECODE": "1",
"APP_ENV": "production"
},
"system_packages": ["gcc", "libpq-dev", "curl"],
"dependency_files": ["requirements.txt", "setup.py"],
"install_cmd": "pip install --no-cache-dir -r requirements.txt",
"healthcheck": {
"interval": "30s",
"timeout": "10s",
"cmd": "curl -f http://localhost:8000/health || exit 1"
},
"cmd": "gunicorn -w 4 -b 0.0.0.0:8000 app:app"
}
}))
System.print("\n" + "-" * 60)
System.print("7. CHANGELOG")
System.print("-" * 60)
System.print(env.getTemplate("changelog.md").render({
"project_name": "MyAwesomeLib",
"releases": [
{
"version": "2.0.0",
"date": "2024-01-15",
"breaking": [
"Removed deprecated `oldMethod()` function",
"Changed default timeout from 30s to 60s"
],
"added": [
"New async API support",
"Plugin system for extensibility",
"TypeScript type definitions"
],
"changed": [
"Improved error messages",
"Updated dependencies to latest versions"
],
"fixed": [
"Memory leak in connection pool",
"Race condition in concurrent requests"
]
},
{
"version": "1.5.0",
"date": "2023-12-01",
"added": [
"Caching layer for improved performance",
"Retry mechanism with exponential backoff"
],
"deprecated": [
"`oldMethod()` - use `newMethod()` instead"
],
"fixed": [
"Timeout handling in slow networks",
"Unicode encoding issues"
],
"security": [
"Updated crypto library to patch CVE-2023-XXXXX"
]
}
]
}))
System.print("\n" + "=" * 60)
System.print("End of Real-World Examples")
System.print("=" * 60)

61
example/json_demo.wren vendored Normal file
View File

@ -0,0 +1,61 @@
// retoor <retoor@molodetz.nl>
import "json" for Json
System.print("=== Json Module Demo ===\n")
System.print("--- Parsing JSON ---")
var jsonString = "{\"name\":\"Alice\",\"age\":30,\"active\":true}"
var obj = Json.parse(jsonString)
System.print("Parsed: %(obj)")
System.print("Name: %(obj["name"])")
System.print("Age: %(obj["age"])")
System.print("Active: %(obj["active"])")
System.print("\n--- Parsing Arrays ---")
var arrayJson = "[1, 2, 3, \"four\", true, null]"
var arr = Json.parse(arrayJson)
System.print("Parsed array: %(arr)")
System.print("Third element: %(arr[2])")
System.print("\n--- Nested Objects ---")
var nestedJson = "{\"user\":{\"name\":\"Bob\",\"address\":{\"city\":\"New York\",\"zip\":\"10001\"}},\"tags\":[\"admin\",\"user\"]}"
var nested = Json.parse(nestedJson)
System.print("City: %(nested["user"]["address"]["city"])")
System.print("First tag: %(nested["tags"][0])")
System.print("\n--- Stringify (compact) ---")
var data = {
"name": "Charlie",
"scores": [85, 92, 78],
"metadata": {
"created": "2024-01-15",
"version": 1
}
}
System.print(Json.stringify(data))
System.print("\n--- Stringify (pretty, 2 spaces) ---")
System.print(Json.stringify(data, 2))
System.print("\n--- Stringify (pretty, 4 spaces) ---")
System.print(Json.stringify(data, 4))
System.print("\n--- Special Values ---")
var special = {
"string": "Hello\nWorld",
"number": 42.5,
"boolean": false,
"null": null,
"empty_array": [],
"empty_object": {}
}
System.print(Json.stringify(special, 2))
System.print("\n--- Round-trip ---")
var original = {"key": "value", "list": [1, 2, 3]}
var jsonified = Json.stringify(original)
var restored = Json.parse(jsonified)
System.print("Original: %(original)")
System.print("JSON: %(jsonified)")
System.print("Restored: %(restored)")

78
example/math_demo.wren vendored Normal file
View File

@ -0,0 +1,78 @@
// retoor <retoor@molodetz.nl>
import "math" for Math
System.print("=== Math Module Demo ===\n")
System.print("--- Constants ---")
System.print("pi: %(Math.pi)")
System.print("e: %(Math.e)")
System.print("tau: %(Math.tau)")
System.print("infinity: %(Math.infinity)")
System.print("nan: %(Math.nan)")
System.print("\n--- Trigonometric Functions ---")
var angle = Math.pi / 4
System.print("Angle: pi/4 radians (45 degrees)")
System.print("sin(pi/4): %(Math.sin(angle))")
System.print("cos(pi/4): %(Math.cos(angle))")
System.print("tan(pi/4): %(Math.tan(angle))")
System.print("\n--- Inverse Trigonometric Functions ---")
System.print("asin(0.5): %(Math.asin(0.5)) radians")
System.print("acos(0.5): %(Math.acos(0.5)) radians")
System.print("atan(1): %(Math.atan(1)) radians")
System.print("atan2(1, 1): %(Math.atan2(1, 1)) radians")
System.print("\n--- Hyperbolic Functions ---")
System.print("sinh(1): %(Math.sinh(1))")
System.print("cosh(1): %(Math.cosh(1))")
System.print("tanh(1): %(Math.tanh(1))")
System.print("\n--- Logarithmic Functions ---")
System.print("log(e): %(Math.log(Math.e))")
System.print("log10(100): %(Math.log10(100))")
System.print("log2(8): %(Math.log2(8))")
System.print("exp(1): %(Math.exp(1))")
System.print("\n--- Power and Roots ---")
System.print("pow(2, 10): %(Math.pow(2, 10))")
System.print("sqrt(16): %(Math.sqrt(16))")
System.print("cbrt(27): %(Math.cbrt(27))")
System.print("\n--- Rounding Functions ---")
System.print("ceil(4.3): %(Math.ceil(4.3))")
System.print("floor(4.7): %(Math.floor(4.7))")
System.print("round(4.5): %(Math.round(4.5))")
System.print("abs(-42): %(Math.abs(-42))")
System.print("\n--- Min, Max, Clamp ---")
System.print("min(5, 3): %(Math.min(5, 3))")
System.print("max(5, 3): %(Math.max(5, 3))")
System.print("clamp(15, 0, 10): %(Math.clamp(15, 0, 10))")
System.print("clamp(5, 0, 10): %(Math.clamp(5, 0, 10))")
System.print("clamp(-5, 0, 10): %(Math.clamp(-5, 0, 10))")
System.print("\n--- Sign Function ---")
System.print("sign(42): %(Math.sign(42))")
System.print("sign(-42): %(Math.sign(-42))")
System.print("sign(0): %(Math.sign(0))")
System.print("\n--- Linear Interpolation ---")
System.print("lerp(0, 100, 0.0): %(Math.lerp(0, 100, 0))")
System.print("lerp(0, 100, 0.5): %(Math.lerp(0, 100, 0.5))")
System.print("lerp(0, 100, 1.0): %(Math.lerp(0, 100, 1))")
System.print("\n--- Angle Conversion ---")
System.print("degrees(pi): %(Math.degrees(Math.pi))")
System.print("degrees(pi/2): %(Math.degrees(Math.pi / 2))")
System.print("radians(180): %(Math.radians(180))")
System.print("radians(90): %(Math.radians(90))")
System.print("\n--- Practical Example: Distance Calculation ---")
var x1 = 0
var y1 = 0
var x2 = 3
var y2 = 4
var distance = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2))
System.print("Distance from (%(x1),%(y1)) to (%(x2),%(y2)): %(distance)")

138
example/openrouter_chat.wren vendored Normal file
View File

@ -0,0 +1,138 @@
// retoor <retoor@molodetz.nl>
import "http" for Http
import "json" for Json
import "env" for Environment
import "io" for Stdin, Stdout
class Chat {
construct new(model) {
_model = model
_messages = []
_apiKey = Environment.get("OPENROUTER_API_KEY")
if (_apiKey == null || _apiKey.count == 0) {
Fiber.abort("OPENROUTER_API_KEY environment variable not set")
}
}
addSystem(content) {
_messages.add({"role": "system", "content": content})
}
send(content) {
_messages.add({"role": "user", "content": content})
var requestBody = {
"model": _model,
"messages": _messages,
"stream": false
}
var headers = {
"Authorization": "Bearer " + _apiKey,
"Content-Type": "application/json"
}
var response = Http.post("https://openrouter.ai/api/v1/chat/completions", requestBody, headers)
if (!response.ok) {
var error = "API error: %(response.statusCode)"
if (response.body.count > 0) {
var data = Json.parse(response.body)
if (data["error"] != null && data["error"]["message"] != null) {
error = data["error"]["message"]
}
}
return {"error": error}
}
var data = Json.parse(response.body)
if (data["choices"] != null && data["choices"].count > 0) {
var message = data["choices"][0]["message"]
if (message != null) {
_messages.add(message)
return {
"content": message["content"],
"model": data["model"],
"usage": data["usage"]
}
}
}
return {"error": "Invalid response format"}
}
clear() {
var system = _messages.count > 0 && _messages[0]["role"] == "system" ? _messages[0] : null
_messages = []
if (system != null) _messages.add(system)
}
model { _model }
model=(value) { _model = value }
messageCount { _messages.count }
}
var model = "x-ai/grok-code-fast-1"
var chat = Chat.new(model)
chat.addSystem("You are a helpful assistant. Be concise in your responses.")
System.print("OpenRouter Chat")
System.print("Model: %(model)")
System.print("Commands: /quit, /clear, /model <name>, /history")
System.print("-" * 50)
while (true) {
System.write("\nYou: ")
Stdout.flush()
var input = Stdin.readLine()
if (input == null || input.trim() == "/quit" || input.trim() == "/exit") {
System.print("Goodbye!")
break
}
input = input.trim()
if (input.count == 0) continue
if (input == "/clear") {
chat.clear()
System.print("[Conversation cleared]")
continue
}
if (input == "/history") {
System.print("[%(chat.messageCount) messages in history]")
continue
}
if (input.startsWith("/model ")) {
var newModel = input[7..-1].trim()
if (newModel.count > 0) {
chat.model = newModel
System.print("[Model changed to: %(newModel)]")
}
continue
}
if (input.startsWith("/")) {
System.print("[Unknown command: %(input)]")
continue
}
System.write("\nAssistant: ")
Stdout.flush()
var result = chat.send(input)
if (result["error"] != null) {
System.print("[Error: %(result["error"])]")
} else {
System.print(result["content"])
if (result["usage"] != null) {
var usage = result["usage"]
System.print("\n[tokens: %(usage["prompt_tokens"]) prompt, %(usage["completion_tokens"]) completion]")
}
}
}

55
example/openrouter_demo.wren vendored Normal file
View File

@ -0,0 +1,55 @@
// retoor <retoor@molodetz.nl>
import "http" for Http
import "json" for Json
import "env" for Environment
var apiKey = Environment.get("OPENROUTER_API_KEY")
if (apiKey == null || apiKey.count == 0) {
System.print("Error: OPENROUTER_API_KEY environment variable not set")
Fiber.abort("Missing API key")
}
System.print("=== OpenRouter API Demo ===\n")
var model = "x-ai/grok-code-fast-1"
var prompt = "What is 2 + 2? Reply with just the number."
System.print("Model: %(model)")
System.print("Prompt: %(prompt)")
System.print("")
var requestBody = {
"model": model,
"messages": [
{"role": "user", "content": prompt}
]
}
var headers = {
"Authorization": "Bearer " + apiKey,
"Content-Type": "application/json"
}
System.print("Sending request to OpenRouter...")
var response = Http.post("https://openrouter.ai/api/v1/chat/completions", requestBody, headers)
System.print("Status: %(response.statusCode)")
System.print("")
if (response.ok) {
var data = Json.parse(response.body)
if (data != null && data["choices"] != null && data["choices"].count > 0) {
var message = data["choices"][0]["message"]
if (message != null && message["content"] != null) {
System.print("Response: %(message["content"])")
}
}
System.print("\nModel used: %(data["model"])")
if (data["usage"] != null) {
System.print("Tokens - prompt: %(data["usage"]["prompt_tokens"]), completion: %(data["usage"]["completion_tokens"])")
}
} else {
System.print("Error: %(response.body)")
}

130
example/regex_demo.wren vendored Normal file
View File

@ -0,0 +1,130 @@
// retoor <retoor@molodetz.nl>
import "regex" for Regex
System.print("=== Regex Module Demo ===\n")
System.print("--- Basic Pattern Matching ---")
var pattern = Regex.new("hello")
System.print("Pattern: %(pattern.pattern)")
System.print("Test 'hello world': %(pattern.test("hello world"))")
System.print("Test 'HELLO world': %(pattern.test("HELLO world"))")
System.print("Test 'goodbye': %(pattern.test("goodbye"))")
System.print("\n--- Case Insensitive Matching ---")
var insensitive = Regex.new("hello", "i")
System.print("Pattern: %(insensitive.pattern) (flags: %(insensitive.flags))")
System.print("Test 'HELLO world': %(insensitive.test("HELLO world"))")
System.print("Test 'HeLLo': %(insensitive.test("HeLLo"))")
System.print("\n--- Digit Patterns ---")
var digitPattern = Regex.new("[0-9]+")
System.print("Pattern: [0-9]+")
System.print("Test '123': %(digitPattern.test("123"))")
System.print("Test 'abc': %(digitPattern.test("abc"))")
System.print("Test 'abc123': %(digitPattern.test("abc123"))")
System.print("\n--- Replace First Match ---")
var text = "hello hello hello"
var helloPattern = Regex.new("hello")
var replaced = helloPattern.replace(text, "hi")
System.print("Original: %(text)")
System.print("Replaced first: %(replaced)")
System.print("\n--- Replace All Matches ---")
var replacedAll = helloPattern.replaceAll(text, "hi")
System.print("Replaced all: %(replacedAll)")
System.print("\n--- Split by Pattern ---")
var csv = "apple,banana,cherry,date"
var commaPattern = Regex.new(",")
var parts = commaPattern.split(csv)
System.print("CSV: %(csv)")
System.print("Split parts: %(parts)")
System.print("\n--- Split by Multiple Commas ---")
var csvMultiple = "apple,,banana,,,cherry"
var multiComma = Regex.new(",+")
var cleanParts = multiComma.split(csvMultiple)
System.print("CSV: %(csvMultiple)")
System.print("Split parts: %(cleanParts)")
System.print("\n--- Word Pattern ---")
var wordPattern = Regex.new("[a-zA-Z]+")
System.print("Test 'hello123': %(wordPattern.test("hello123"))")
System.print("Test '12345': %(wordPattern.test("12345"))")
System.print("\n--- Whitespace Handling ---")
var whitespace = Regex.new("\\s+")
var messy = "too many spaces"
var cleaned = whitespace.replaceAll(messy, " ")
System.print("Original: '%(messy)'")
System.print("Cleaned: '%(cleaned)'")
System.print("\n--- Special Character Escapes ---")
var digitEscape = Regex.new("\\d+")
System.print("Pattern: \\d+ (digits)")
System.print("Test 'abc123def': %(digitEscape.test("abc123def"))")
var wordEscape = Regex.new("\\w+")
System.print("Pattern: \\w+ (word chars)")
System.print("Test 'hello_world': %(wordEscape.test("hello_world"))")
var spaceEscape = Regex.new("\\s")
System.print("Pattern: \\s (whitespace)")
System.print("Test 'hello world': %(spaceEscape.test("hello world"))")
System.print("\n--- Anchors ---")
var startAnchor = Regex.new("^hello")
System.print("Pattern: ^hello (start anchor)")
System.print("Test 'hello world': %(startAnchor.test("hello world"))")
System.print("Test 'say hello': %(startAnchor.test("say hello"))")
var endAnchor = Regex.new("world$")
System.print("Pattern: world$ (end anchor)")
System.print("Test 'hello world': %(endAnchor.test("hello world"))")
System.print("Test 'world hello': %(endAnchor.test("world hello"))")
System.print("\n--- Quantifiers ---")
var optional = Regex.new("colou?r")
System.print("Pattern: colou?r (optional u)")
System.print("Test 'color': %(optional.test("color"))")
System.print("Test 'colour': %(optional.test("colour"))")
var oneOrMore = Regex.new("a+")
System.print("Pattern: a+ (one or more a)")
System.print("Test 'aaa': %(oneOrMore.test("aaa"))")
System.print("Test 'bbb': %(oneOrMore.test("bbb"))")
var zeroOrMore = Regex.new("ba*b")
System.print("Pattern: ba*b (zero or more a)")
System.print("Test 'bb': %(zeroOrMore.test("bb"))")
System.print("Test 'bab': %(zeroOrMore.test("bab"))")
System.print("Test 'baaab': %(zeroOrMore.test("baaab"))")
System.print("\n--- Character Classes ---")
var vowels = Regex.new("[aeiou]")
System.print("Pattern: [aeiou] (vowels)")
System.print("Test 'hello': %(vowels.test("hello"))")
System.print("Test 'xyz': %(vowels.test("xyz"))")
var notDigit = Regex.new("[^0-9]+")
System.print("Pattern: [^0-9]+ (non-digits)")
var numbersRemoved = notDigit.replaceAll("abc123def456", "")
System.print("Remove non-digits from 'abc123def456': '%(numbersRemoved)'")
System.print("\n--- Practical: Trim Whitespace ---")
var leadingSpace = Regex.new("^\\s+")
var trailingSpace = Regex.new("\\s+$")
var padded = " hello world "
var trimmed = leadingSpace.replace(padded, "")
trimmed = trailingSpace.replace(trimmed, "")
System.print("Original: '[%(padded)]'")
System.print("Trimmed: '[%(trimmed)]'")
System.print("\n--- Practical: Simple Tokenizer ---")
var tokenSeparator = Regex.new("[\\s,;]+")
var sentence = "hello, world; foo bar"
var tokens = tokenSeparator.split(sentence)
System.print("Input: %(sentence)")
System.print("Tokens: %(tokens)")

39
example/signal_demo.wren vendored Normal file
View File

@ -0,0 +1,39 @@
// retoor <retoor@molodetz.nl>
import "signal" for Signal
System.print("=== Signal Module Demo ===\n")
System.print("--- Available Signal Constants ---")
System.print("SIGHUP: %(Signal.SIGHUP)")
System.print("SIGINT: %(Signal.SIGINT)")
System.print("SIGQUIT: %(Signal.SIGQUIT)")
System.print("SIGTERM: %(Signal.SIGTERM)")
System.print("SIGUSR1: %(Signal.SIGUSR1)")
System.print("SIGUSR2: %(Signal.SIGUSR2)")
System.print("\n--- Signal API Overview ---")
System.print("The Signal module provides:")
System.print(" Signal.trap(signum, fn) - Set a handler for a signal")
System.print(" Signal.ignore(signum) - Ignore a signal")
System.print(" Signal.reset(signum) - Reset to default behavior")
System.print("\n--- Example Usage ---")
System.print("To handle SIGINT (Ctrl+C):")
System.print(" Signal.trap(Signal.SIGINT) {")
System.print(" System.print(\"Interrupted!\")")
System.print(" }")
System.print("\nTo ignore SIGTERM:")
System.print(" Signal.ignore(Signal.SIGTERM)")
System.print("\nTo reset SIGHUP to default:")
System.print(" Signal.reset(Signal.SIGHUP)")
System.print("\n--- Notes ---")
System.print("Signal handling is useful for:")
System.print(" - Graceful shutdown on SIGTERM/SIGINT")
System.print(" - Reloading configuration on SIGHUP")
System.print(" - Custom actions on SIGUSR1/SIGUSR2")
System.print("\n--- Demo Complete ---")

112
example/sqlite_demo.wren vendored Normal file
View File

@ -0,0 +1,112 @@
// retoor <retoor@molodetz.nl>
import "sqlite" for Database
System.print("=== SQLite Module Demo ===\n")
System.print("--- Creating In-Memory Database ---")
var db = Database.memory()
System.print("Database opened successfully")
System.print("\n--- Creating Table ---")
db.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT, age INTEGER)")
System.print("Table 'users' created")
System.print("\n--- Inserting Data ---")
db.execute("INSERT INTO users (name, email, age) VALUES ('Alice', 'alice@example.com', 30)")
System.print("Last insert ID: %(db.lastInsertId)")
db.execute("INSERT INTO users (name, email, age) VALUES ('Bob', 'bob@example.com', 25)")
System.print("Last insert ID: %(db.lastInsertId)")
db.execute("INSERT INTO users (name, email, age) VALUES ('Charlie', 'charlie@example.com', 35)")
System.print("Last insert ID: %(db.lastInsertId)")
System.print("Rows changed: %(db.changes)")
System.print("\n--- Parameterized Insert ---")
db.execute("INSERT INTO users (name, email, age) VALUES (?, ?, ?)", ["Diana", "diana@example.com", 28])
System.print("Inserted Diana with ID: %(db.lastInsertId)")
System.print("\n--- Querying All Data ---")
var allUsers = db.query("SELECT * FROM users")
System.print("All users:")
for (user in allUsers) {
System.print(" %(user["id"]): %(user["name"]) <%(user["email"])> age %(user["age"])")
}
System.print("\n--- Parameterized Query ---")
var olderUsers = db.query("SELECT * FROM users WHERE age > ?", [27])
System.print("Users older than 27:")
for (user in olderUsers) {
System.print(" %(user["name"]) (age %(user["age"]))")
}
System.print("\n--- Single Result Query ---")
var singleUser = db.query("SELECT * FROM users WHERE name = ?", ["Alice"])
if (singleUser.count > 0) {
System.print("Found Alice: %(singleUser[0]["email"])")
}
System.print("\n--- Aggregate Query ---")
var avgAge = db.query("SELECT AVG(age) as avg_age, COUNT(*) as count FROM users")
System.print("Average age: %(avgAge[0]["avg_age"])")
System.print("Total users: %(avgAge[0]["count"])")
System.print("\n--- Updating Data ---")
db.execute("UPDATE users SET age = ? WHERE name = ?", [31, "Alice"])
System.print("Rows updated: %(db.changes)")
var updated = db.query("SELECT age FROM users WHERE name = 'Alice'")
System.print("Alice's new age: %(updated[0]["age"])")
System.print("\n--- Deleting Data ---")
db.execute("DELETE FROM users WHERE name = ?", ["Charlie"])
System.print("Rows deleted: %(db.changes)")
System.print("\n--- Remaining Users ---")
var remaining = db.query("SELECT name FROM users ORDER BY name")
System.print("Users: %(remaining)")
System.print("\n--- Creating Another Table ---")
db.execute("CREATE TABLE orders (id INTEGER PRIMARY KEY, user_id INTEGER, product TEXT, amount REAL)")
db.execute("INSERT INTO orders (user_id, product, amount) VALUES (1, 'Widget', 19.99)")
db.execute("INSERT INTO orders (user_id, product, amount) VALUES (1, 'Gadget', 29.99)")
db.execute("INSERT INTO orders (user_id, product, amount) VALUES (2, 'Widget', 19.99)")
System.print("\n--- Join Query ---")
var ordersWithUsers = db.query("SELECT u.name, o.product, o.amount FROM users u JOIN orders o ON u.id = o.user_id")
System.print("Orders with user names:")
for (row in ordersWithUsers) {
System.print(" %(row["name"]) ordered %(row["product"]) for $%(row["amount"])")
}
System.print("\n--- Transaction Example ---")
db.execute("BEGIN TRANSACTION")
db.execute("INSERT INTO users (name, email, age) VALUES (?, ?, ?)", ["Eve", "eve@example.com", 22])
db.execute("INSERT INTO users (name, email, age) VALUES (?, ?, ?)", ["Frank", "frank@example.com", 45])
db.execute("COMMIT")
System.print("Transaction committed")
var afterTransaction = db.query("SELECT COUNT(*) as count FROM users")
System.print("Total users after transaction: %(afterTransaction[0]["count"])")
System.print("\n--- Closing Database ---")
db.close()
System.print("Database closed")
System.print("\n--- File-based Database Example ---")
var fileDb = Database.open("/tmp/wren_demo.db")
fileDb.execute("CREATE TABLE IF NOT EXISTS settings (key TEXT PRIMARY KEY, value TEXT)")
fileDb.execute("INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)", ["version", "1.0.0"])
fileDb.execute("INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)", ["theme", "dark"])
var settings = fileDb.query("SELECT * FROM settings")
System.print("Settings:")
for (s in settings) {
System.print(" %(s["key"]) = %(s["value"])")
}
fileDb.close()
System.print("File database closed")
System.print("Database saved to: /tmp/wren_demo.db")

70
example/subprocess_demo.wren vendored Normal file
View File

@ -0,0 +1,70 @@
// retoor <retoor@molodetz.nl>
import "subprocess" for Subprocess, ProcessResult
System.print("=== Subprocess Module Demo ===\n")
System.print("--- Running Shell Commands ---")
var result = Subprocess.run("echo 'Hello from subprocess!'")
System.print("stdout: %(result.stdout.trim())")
System.print("exit code: %(result.exitCode)")
System.print("success: %(result.success)")
System.print("\n--- Running with Arguments List ---")
var lsResult = Subprocess.run(["/bin/ls", "-la", "/tmp"])
System.print("Directory listing (first 200 chars):")
var output = lsResult.stdout
if (output.count > 200) output = output[0...200] + "..."
System.print(output)
System.print("\n--- Getting Current Directory ---")
var pwdResult = Subprocess.run("pwd")
System.print("Current directory: %(pwdResult.stdout.trim())")
System.print("\n--- Environment Variables ---")
var envResult = Subprocess.run("echo $USER@$HOSTNAME")
System.print("User info: %(envResult.stdout.trim())")
System.print("\n--- Piped Commands ---")
var pipeResult = Subprocess.run("echo 'apple\nbanana\ncherry' | sort -r")
System.print("Sorted output:")
System.print(pipeResult.stdout)
System.print("--- Command with Stderr ---")
var errResult = Subprocess.run("ls /nonexistent_directory_12345 2>&1")
System.print("Output: %(errResult.stdout.trim())")
System.print("Exit code: %(errResult.exitCode)")
System.print("Success: %(errResult.success)")
System.print("\n--- Capturing Both Stdout and Stderr ---")
var mixedResult = Subprocess.run("echo 'stdout message' && echo 'stderr message' >&2")
System.print("stdout: %(mixedResult.stdout.trim())")
System.print("stderr: %(mixedResult.stderr.trim())")
System.print("\n--- Running Python (if available) ---")
var pyResult = Subprocess.run("python3 -c \"print('Hello from Python!')\" 2>/dev/null || echo 'Python not available'")
System.print("Python: %(pyResult.stdout.trim())")
System.print("\n--- Getting System Info ---")
var unameResult = Subprocess.run("uname -a")
System.print("System: %(unameResult.stdout.trim())")
System.print("\n--- Date and Time ---")
var dateResult = Subprocess.run("date '+\%Y-\%m-\%d \%H:\%M:\%S'")
System.print("Current time: %(dateResult.stdout.trim())")
System.print("\n--- Checking Exit Codes ---")
var trueResult = Subprocess.run("true")
var falseResult = Subprocess.run("false")
System.print("'true' exit code: %(trueResult.exitCode)")
System.print("'false' exit code: %(falseResult.exitCode)")
System.print("\n--- Practical Example: Git Status ---")
var gitResult = Subprocess.run("git status --short 2>/dev/null || echo 'Not a git repository'")
System.print("Git status:")
var gitOutput = gitResult.stdout.trim()
if (gitOutput.count == 0) {
System.print(" (clean working directory)")
} else {
System.print(gitOutput)
}

49
example/websocket_chat_client.wren vendored Normal file
View File

@ -0,0 +1,49 @@
// retoor <retoor@molodetz.nl>
import "websocket" for WebSocket
import "scheduler" for Scheduler
import "timer" for Timer
import "io" for Stdin
var host = "ws://127.0.0.1:8080"
System.print("=== WebSocket Chat Client ===")
System.print("Connecting to %(host)...")
var ws = WebSocket.connect(host)
System.print("Connected!")
System.print("Commands: /name <name>, /who, /quit")
System.print("Type a message and press Enter to send.\n")
var running = true
Scheduler.add {
while (running && ws.isOpen) {
var msg = ws.receive()
if (msg == null || msg.isClose) {
running = false
System.print("\nConnection closed.")
break
}
if (msg.isText) {
System.print(msg.text)
}
}
}
Timer.sleep(100)
while (running && ws.isOpen) {
System.write("> ")
var line = Stdin.readLine()
if (line == null || line == "/quit") {
running = false
break
}
if (line.count > 0) {
ws.send(line)
}
}
ws.close()
System.print("Disconnected.")

95
example/websocket_chat_server.wren vendored Normal file
View File

@ -0,0 +1,95 @@
// retoor <retoor@molodetz.nl>
import "websocket" for WebSocket, WebSocketServer
import "scheduler" for Scheduler
import "timer" for Timer
var port = 8080
var clients = []
var clientId = 0
System.print("=== WebSocket Chat Server ===")
System.print("Listening on ws://0.0.0.0:%(port)")
System.print("Clients can send messages that get broadcast to all others.\n")
var broadcast = Fn.new { |sender, message|
var i = 0
while (i < clients.count) {
var client = clients[i]
if (client["ws"].isOpen && client["id"] != sender) {
client["ws"].send(message)
}
i = i + 1
}
}
var removeClient = Fn.new { |id|
var i = 0
while (i < clients.count) {
if (clients[i]["id"] == id) {
clients.removeAt(i)
break
}
i = i + 1
}
}
var handleClient = Fn.new { |ws, id|
var client = {"ws": ws, "id": id, "name": "User%(id)"}
clients.add(client)
System.print("[%(client["name"])] Connected (%(clients.count) clients online)")
broadcast.call(id, "*** %(client["name"]) joined the chat ***")
while (ws.isOpen) {
var msg = ws.receive()
if (msg == null || msg.isClose) {
break
}
if (msg.isText) {
var text = msg.text
if (text.startsWith("/name ")) {
var oldName = client["name"]
client["name"] = text[6..-1]
broadcast.call(-1, "*** %(oldName) is now known as %(client["name"]) ***")
ws.send("Name changed to %(client["name"])")
} else if (text == "/who") {
var names = []
for (c in clients) {
names.add(c["name"])
}
ws.send("Online: " + names.join(", "))
} else if (text == "/quit") {
ws.send("Goodbye!")
break
} else {
var chatMsg = "[%(client["name"])]: %(text)"
System.print(chatMsg)
broadcast.call(id, chatMsg)
ws.send(chatMsg)
}
}
}
removeClient.call(id)
System.print("[%(client["name"])] Disconnected (%(clients.count) clients online)")
broadcast.call(id, "*** %(client["name"]) left the chat ***")
}
var server = WebSocketServer.bind("0.0.0.0", port)
while (true) {
var ws = server.accept()
if (ws != null) {
clientId = clientId + 1
var id = clientId
Scheduler.add {
handleClient.call(ws, id)
}
}
}

View File

@ -0,0 +1,69 @@
// retoor <retoor@molodetz.nl>
import "websocket" for WebSocket, WebSocketServer
import "scheduler" for Scheduler
import "timer" for Timer
var port = 8080
var clientCount = 0
var messageCount = 0
System.print("=== Concurrent WebSocket Server ===")
System.print("Listening on ws://0.0.0.0:%(port)")
System.print("Each client connection runs in its own fiber.\n")
var server = WebSocketServer.bind("0.0.0.0", port)
var handleClient = Fn.new { |ws, clientId|
System.print("[Client %(clientId)] Connected")
while (ws.isOpen) {
var msg = ws.receive()
if (msg == null) {
System.print("[Client %(clientId)] Connection lost")
break
}
if (msg.isClose) {
System.print("[Client %(clientId)] Sent close frame")
break
}
if (msg.isText) {
messageCount = messageCount + 1
var text = msg.text
System.print("[Client %(clientId)] Received: %(text) (total messages: %(messageCount))")
if (text == "ping") {
ws.send("pong")
} else if (text == "stats") {
ws.send("clients: %(clientCount), messages: %(messageCount)")
} else if (text == "broadcast") {
ws.send("broadcast not implemented in this example")
} else {
ws.send("echo: %(text)")
}
} else if (msg.isBinary) {
messageCount = messageCount + 1
System.print("[Client %(clientId)] Received binary: %(msg.bytes.count) bytes")
ws.sendBinary(msg.bytes)
}
}
clientCount = clientCount - 1
System.print("[Client %(clientId)] Disconnected (%(clientCount) clients remaining)")
}
while (true) {
var ws = server.accept()
if (ws != null) {
clientCount = clientCount + 1
var clientId = clientCount
Scheduler.add {
handleClient.call(ws, clientId)
}
}
}

23
example/websocket_demo.wren vendored Normal file
View File

@ -0,0 +1,23 @@
// retoor <retoor@molodetz.nl>
import "websocket" for WebSocket
System.print("=== WebSocket Client Demo ===\n")
System.print("Connecting to ws://127.0.0.1:8080...")
var ws = WebSocket.connect("ws://127.0.0.1:8080")
System.print("Connected\n")
System.print("--- Sending Text Message ---")
ws.send("Hello WebSocket!")
var msg = ws.receive()
System.print("Received: %(msg.text)")
System.print("\n--- Sending Binary Message ---")
ws.sendBinary([72, 101, 108, 108, 111])
msg = ws.receive()
System.print("Received bytes: %(msg.bytes)")
System.print("\n--- Closing Connection ---")
ws.close(1000, "Client closing")
System.print("Connection closed")

91
example/websocket_load_test.wren vendored Normal file
View File

@ -0,0 +1,91 @@
// retoor <retoor@molodetz.nl>
import "websocket" for WebSocket
import "io" for Stdout
var host = "ws://127.0.0.1:8080"
var totalMessages = 50000
var warmupMessages = 100
System.print("=== WebSocket Extreme Load Test ===")
System.print("Target: %(host)")
System.print("Messages: %(totalMessages) (warmup: %(warmupMessages))")
System.print("")
Stdout.flush()
var ws = WebSocket.connect(host)
System.print("Connected")
Stdout.flush()
var i = 0
while (i < warmupMessages) {
ws.send("warmup%(i)")
ws.receive()
i = i + 1
}
System.print("Warmup complete")
Stdout.flush()
var errors = 0
var minTime = 999999
var maxTime = 0
var totalTime = 0
var times = []
var startTime = System.clock
i = 0
while (i < totalMessages) {
var msgStart = System.clock
ws.send("msg%(i)")
var response = ws.receive()
var msgEnd = System.clock
var elapsed = msgEnd - msgStart
if (response == null || !response.isText) {
errors = errors + 1
} else {
times.add(elapsed)
totalTime = totalTime + elapsed
if (elapsed < minTime) minTime = elapsed
if (elapsed > maxTime) maxTime = elapsed
}
if (i > 0 && i % 2000 == 0) {
var progress = (i * 100 / totalMessages).floor
var currentRate = i / (System.clock - startTime)
System.print(" Progress: %(progress) percent (%(currentRate.floor) msg/sec)")
Stdout.flush()
}
i = i + 1
}
var endTime = System.clock
var totalElapsed = endTime - startTime
ws.close()
times.sort()
var p50 = times[(times.count * 0.5).floor]
var p90 = times[(times.count * 0.9).floor]
var p95 = times[(times.count * 0.95).floor]
var p99 = times[(times.count * 0.99).floor]
var avgTime = totalTime / times.count
System.print("")
System.print("=== Results ===")
System.print("Total messages: %(totalMessages)")
System.print("Errors: %(errors)")
System.print("Total time: %(totalElapsed)s")
System.print("Throughput: %((totalMessages / totalElapsed).floor) msg/sec")
System.print("")
System.print("=== Latency (per message round-trip) ===")
System.print("Min: %((minTime * 1000000).floor) us")
System.print("Max: %((maxTime * 1000000).floor) us")
System.print("Avg: %((avgTime * 1000000).floor) us")
System.print("P50: %((p50 * 1000000).floor) us")
System.print("P90: %((p90 * 1000000).floor) us")
System.print("P95: %((p95 * 1000000).floor) us")
System.print("P99: %((p99 * 1000000).floor) us")
Stdout.flush()

44
example/websocket_server_demo.wren vendored Normal file
View File

@ -0,0 +1,44 @@
// retoor <retoor@molodetz.nl>
import "websocket" for WebSocketServer
System.print("=== WebSocket Server Demo ===")
System.print("Listening on ws://0.0.0.0:8080\n")
var server = WebSocketServer.bind("0.0.0.0", 8080)
while (true) {
System.print("Waiting for connection...")
var ws = server.accept()
if (ws == null) {
System.print("Failed to accept connection")
continue
}
System.print("Client connected")
while (ws.isOpen) {
var msg = ws.receive()
if (msg == null) {
System.print("Connection lost")
break
}
if (msg.isClose) {
System.print("Client sent close frame (code: %(msg.closeCode))")
break
}
if (msg.isText) {
System.print("Received text: %(msg.text)")
ws.send("Echo: %(msg.text)")
} else if (msg.isBinary) {
System.print("Received binary: %(msg.bytes.count) bytes")
ws.sendBinary(msg.bytes)
}
}
System.print("Client disconnected\n")
}

View File

@ -19,11 +19,11 @@ endif
# #############################################
RESCOMP = windres
INCLUDES += -I../../src/cli -I../../src/module -I../../deps/wren/include -I../../deps/wren/src/vm -I../../deps/wren/src/optional -I../../deps/libuv/include -I../../deps/libuv/src
INCLUDES += -I../../src/cli -I../../src/module -I../../deps/wren/include -I../../deps/wren/src/vm -I../../deps/wren/src/optional -I../../deps/libuv/include -I../../deps/libuv/src -I../../deps/cjson -I../../deps/sqlite
FORCE_INCLUDE +=
ALL_CPPFLAGS += $(CPPFLAGS) -MMD -MP $(DEFINES) $(INCLUDES)
ALL_RESFLAGS += $(RESFLAGS) $(DEFINES) $(INCLUDES)
LIBS += -lpthread -ldl -lm
LIBS += -lpthread -ldl -lm -lssl -lcrypto
LDDEPS +=
LINKCMD = $(CC) -o "$@" $(OBJECTS) $(RESOURCES) $(ALL_LDFLAGS) $(LIBS)
define PREBUILDCMDS
@ -109,7 +109,15 @@ OBJECTS += $(OBJDIR)/getaddrinfo.o
OBJECTS += $(OBJDIR)/getnameinfo.o
OBJECTS += $(OBJDIR)/idna.o
OBJECTS += $(OBJDIR)/inet.o
OBJECTS += $(OBJDIR)/base64.o
OBJECTS += $(OBJDIR)/cJSON.o
OBJECTS += $(OBJDIR)/crypto.o
OBJECTS += $(OBJDIR)/datetime.o
OBJECTS += $(OBJDIR)/dns.o
OBJECTS += $(OBJDIR)/env.o
OBJECTS += $(OBJDIR)/io.o
OBJECTS += $(OBJDIR)/json.o
OBJECTS += $(OBJDIR)/math_module.o
OBJECTS += $(OBJDIR)/linux-core.o
OBJECTS += $(OBJDIR)/linux-inotify.o
OBJECTS += $(OBJDIR)/linux-syscalls.o
@ -129,9 +137,15 @@ OBJECTS += $(OBJDIR)/random-devurandom.o
OBJECTS += $(OBJDIR)/random-getrandom.o
OBJECTS += $(OBJDIR)/random-sysctl-linux.o
OBJECTS += $(OBJDIR)/random.o
OBJECTS += $(OBJDIR)/regex.o
OBJECTS += $(OBJDIR)/repl.o
OBJECTS += $(OBJDIR)/scheduler.o
OBJECTS += $(OBJDIR)/signal.o
OBJECTS += $(OBJDIR)/signal_module.o
OBJECTS += $(OBJDIR)/sqlite3.o
OBJECTS += $(OBJDIR)/sqlite_module.o
OBJECTS += $(OBJDIR)/subprocess.o
OBJECTS += $(OBJDIR)/tls.o
OBJECTS += $(OBJDIR)/stream.o
OBJECTS += $(OBJDIR)/strscpy.o
OBJECTS += $(OBJDIR)/sysinfo-loadavg.o
@ -363,9 +377,33 @@ $(OBJDIR)/path.o: ../../src/cli/path.c
$(OBJDIR)/vm.o: ../../src/cli/vm.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
$(OBJDIR)/base64.o: ../../src/module/base64.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
$(OBJDIR)/cJSON.o: ../../deps/cjson/cJSON.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
$(OBJDIR)/crypto.o: ../../src/module/crypto.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
$(OBJDIR)/datetime.o: ../../src/module/datetime.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
$(OBJDIR)/dns.o: ../../src/module/dns.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
$(OBJDIR)/env.o: ../../src/module/env.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
$(OBJDIR)/io.o: ../../src/module/io.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
$(OBJDIR)/json.o: ../../src/module/json.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
$(OBJDIR)/math_module.o: ../../src/module/math_module.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
$(OBJDIR)/net.o: ../../src/module/net.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
@ -381,6 +419,24 @@ $(OBJDIR)/scheduler.o: ../../src/module/scheduler.c
$(OBJDIR)/timer1.o: ../../src/module/timer.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
$(OBJDIR)/signal_module.o: ../../src/module/signal_module.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
$(OBJDIR)/subprocess.o: ../../src/module/subprocess.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
$(OBJDIR)/regex.o: ../../src/module/regex.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
$(OBJDIR)/sqlite3.o: ../../deps/sqlite/sqlite3.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
$(OBJDIR)/sqlite_module.o: ../../src/module/sqlite_module.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
$(OBJDIR)/tls.o: ../../src/module/tls.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
-include $(OBJECTS:%.o=%.d)
ifneq (,$(PCH))

View File

@ -3,16 +3,68 @@
#include "modules.h"
#include "base64.wren.inc"
#include "crypto.wren.inc"
#include "datetime.wren.inc"
#include "dns.wren.inc"
#include "env.wren.inc"
#include "http.wren.inc"
#include "io.wren.inc"
#include "jinja.wren.inc"
#include "json.wren.inc"
#include "math.wren.inc"
#include "net.wren.inc"
#include "os.wren.inc"
#include "regex.wren.inc"
#include "repl.wren.inc"
#include "scheduler.wren.inc"
#include "signal.wren.inc"
#include "sqlite.wren.inc"
#include "subprocess.wren.inc"
#include "timer.wren.inc"
#include "tls.wren.inc"
#include "websocket.wren.inc"
extern void base64Encode(WrenVM* vm);
extern void base64Decode(WrenVM* vm);
extern void cryptoRandomBytes(WrenVM* vm);
extern void cryptoMd5(WrenVM* vm);
extern void cryptoSha1(WrenVM* vm);
extern void cryptoSha256(WrenVM* vm);
extern void datetimeNow(WrenVM* vm);
extern void datetimeFromTimestamp(WrenVM* vm);
extern void datetimeFormat(WrenVM* vm);
extern void datetimeComponent(WrenVM* vm);
extern void dnsLookup(WrenVM* vm);
extern void directoryList(WrenVM* vm);
extern void directoryCreate(WrenVM* vm);
extern void directoryDelete(WrenVM* vm);
extern void envGet(WrenVM* vm);
extern void envSet(WrenVM* vm);
extern void envDelete(WrenVM* vm);
extern void envAll(WrenVM* vm);
extern void jsonParse(WrenVM* vm);
extern void mathSin(WrenVM* vm);
extern void mathCos(WrenVM* vm);
extern void mathTan(WrenVM* vm);
extern void mathAsin(WrenVM* vm);
extern void mathAcos(WrenVM* vm);
extern void mathAtan(WrenVM* vm);
extern void mathAtan2(WrenVM* vm);
extern void mathSinh(WrenVM* vm);
extern void mathCosh(WrenVM* vm);
extern void mathTanh(WrenVM* vm);
extern void mathLog(WrenVM* vm);
extern void mathLog10(WrenVM* vm);
extern void mathLog2(WrenVM* vm);
extern void mathExp(WrenVM* vm);
extern void mathPow(WrenVM* vm);
extern void mathSqrt(WrenVM* vm);
extern void mathCbrt(WrenVM* vm);
extern void mathCeil(WrenVM* vm);
extern void mathFloor(WrenVM* vm);
extern void mathRound(WrenVM* vm);
extern void mathAbs(WrenVM* vm);
extern void fileAllocate(WrenVM* vm);
extern void fileFinalize(void* data);
extern void fileDelete(WrenVM* vm);
@ -68,6 +120,35 @@ extern void serverFinalize(void* data);
extern void serverBind(WrenVM* vm);
extern void serverAccept(WrenVM* vm);
extern void serverClose(WrenVM* vm);
extern void signalTrap(WrenVM* vm);
extern void signalIgnore(WrenVM* vm);
extern void signalReset(WrenVM* vm);
extern void sqliteAllocate(WrenVM* vm);
extern void sqliteFinalize(void* data);
extern void sqliteExecute(WrenVM* vm);
extern void sqliteExecuteParams(WrenVM* vm);
extern void sqliteQuery(WrenVM* vm);
extern void sqliteQueryParams(WrenVM* vm);
extern void sqliteClose(WrenVM* vm);
extern void sqliteLastInsertId(WrenVM* vm);
extern void sqliteChanges(WrenVM* vm);
extern void subprocessRun(WrenVM* vm);
extern void regexAllocate(WrenVM* vm);
extern void regexFinalize(void* data);
extern void regexTest(WrenVM* vm);
extern void regexMatch(WrenVM* vm);
extern void regexMatchAll(WrenVM* vm);
extern void regexReplace(WrenVM* vm);
extern void regexReplaceAll(WrenVM* vm);
extern void regexSplit(WrenVM* vm);
extern void regexPattern(WrenVM* vm);
extern void regexFlags(WrenVM* vm);
extern void tlsSocketAllocate(WrenVM* vm);
extern void tlsSocketFinalize(void* data);
extern void tlsSocketConnect(WrenVM* vm);
extern void tlsSocketWrite(WrenVM* vm);
extern void tlsSocketRead(WrenVM* vm);
extern void tlsSocketClose(WrenVM* vm);
// The maximum number of foreign methods a single class defines. Ideally, we
// would use variable-length arrays for each class in the table below, but
@ -77,7 +158,7 @@ extern void serverClose(WrenVM* vm);
// If you add a new method to the longest class below, make sure to bump this.
// Note that it also includes an extra slot for the sentinel value indicating
// the end of the list.
#define MAX_METHODS_PER_CLASS 14
#define MAX_METHODS_PER_CLASS 24
// The maximum number of foreign classes a single built-in module defines.
//
@ -137,6 +218,45 @@ typedef struct
// The array of built-in modules.
static ModuleRegistry modules[] =
{
MODULE(base64)
CLASS(Base64)
STATIC_METHOD("encode(_)", base64Encode)
STATIC_METHOD("decode(_)", base64Decode)
END_CLASS
END_MODULE
MODULE(crypto)
CLASS(Crypto)
STATIC_METHOD("randomBytes_(_,_)", cryptoRandomBytes)
END_CLASS
CLASS(Hash)
STATIC_METHOD("md5_(_)", cryptoMd5)
STATIC_METHOD("sha1_(_)", cryptoSha1)
STATIC_METHOD("sha256_(_)", cryptoSha256)
END_CLASS
END_MODULE
MODULE(datetime)
CLASS(DateTime)
STATIC_METHOD("now_()", datetimeNow)
STATIC_METHOD("fromTimestamp_(_)", datetimeFromTimestamp)
STATIC_METHOD("format_(_,_)", datetimeFormat)
STATIC_METHOD("component_(_,_)", datetimeComponent)
END_CLASS
END_MODULE
MODULE(dns)
CLASS(Dns)
STATIC_METHOD("lookup_(_,_,_)", dnsLookup)
END_CLASS
END_MODULE
MODULE(env)
CLASS(Environment)
STATIC_METHOD("get(_)", envGet)
STATIC_METHOD("set(_,_)", envSet)
STATIC_METHOD("delete(_)", envDelete)
STATIC_METHOD("all", envAll)
END_CLASS
END_MODULE
MODULE(http)
END_MODULE
MODULE(io)
CLASS(Directory)
STATIC_METHOD("create_(_,_)", directoryCreate)
@ -186,6 +306,38 @@ static ModuleRegistry modules[] =
STATIC_METHOD("write(_)", stderrWrite)
END_CLASS
END_MODULE
MODULE(jinja)
END_MODULE
MODULE(json)
CLASS(Json)
STATIC_METHOD("parse(_)", jsonParse)
END_CLASS
END_MODULE
MODULE(math)
CLASS(Math)
STATIC_METHOD("sin(_)", mathSin)
STATIC_METHOD("cos(_)", mathCos)
STATIC_METHOD("tan(_)", mathTan)
STATIC_METHOD("asin(_)", mathAsin)
STATIC_METHOD("acos(_)", mathAcos)
STATIC_METHOD("atan(_)", mathAtan)
STATIC_METHOD("atan2(_,_)", mathAtan2)
STATIC_METHOD("sinh(_)", mathSinh)
STATIC_METHOD("cosh(_)", mathCosh)
STATIC_METHOD("tanh(_)", mathTanh)
STATIC_METHOD("log(_)", mathLog)
STATIC_METHOD("log10(_)", mathLog10)
STATIC_METHOD("log2(_)", mathLog2)
STATIC_METHOD("exp(_)", mathExp)
STATIC_METHOD("pow(_,_)", mathPow)
STATIC_METHOD("sqrt(_)", mathSqrt)
STATIC_METHOD("cbrt(_)", mathCbrt)
STATIC_METHOD("ceil(_)", mathCeil)
STATIC_METHOD("floor(_)", mathFloor)
STATIC_METHOD("round(_)", mathRound)
STATIC_METHOD("abs(_)", mathAbs)
END_CLASS
END_MODULE
MODULE(net)
CLASS(Socket)
ALLOCATE(socketAllocate)
@ -218,6 +370,20 @@ static ModuleRegistry modules[] =
STATIC_METHOD("version", processVersion)
END_CLASS
END_MODULE
MODULE(regex)
CLASS(Regex)
ALLOCATE(regexAllocate)
FINALIZE(regexFinalize)
METHOD("test(_)", regexTest)
METHOD("match(_)", regexMatch)
METHOD("matchAll(_)", regexMatchAll)
METHOD("replace(_,_)", regexReplace)
METHOD("replaceAll(_,_)", regexReplaceAll)
METHOD("split(_)", regexSplit)
METHOD("pattern", regexPattern)
METHOD("flags", regexFlags)
END_CLASS
END_MODULE
MODULE(repl)
END_MODULE
MODULE(scheduler)
@ -225,11 +391,48 @@ static ModuleRegistry modules[] =
STATIC_METHOD("captureMethods_()", schedulerCaptureMethods)
END_CLASS
END_MODULE
MODULE(signal)
CLASS(Signal)
STATIC_METHOD("trap_(_,_)", signalTrap)
STATIC_METHOD("ignore_(_)", signalIgnore)
STATIC_METHOD("reset_(_)", signalReset)
END_CLASS
END_MODULE
MODULE(sqlite)
CLASS(Database)
ALLOCATE(sqliteAllocate)
FINALIZE(sqliteFinalize)
METHOD("execute(_)", sqliteExecute)
METHOD("execute(_,_)", sqliteExecuteParams)
METHOD("query(_)", sqliteQuery)
METHOD("query(_,_)", sqliteQueryParams)
METHOD("close()", sqliteClose)
METHOD("lastInsertId", sqliteLastInsertId)
METHOD("changes", sqliteChanges)
END_CLASS
END_MODULE
MODULE(subprocess)
CLASS(Subprocess)
STATIC_METHOD("run_(_,_)", subprocessRun)
END_CLASS
END_MODULE
MODULE(timer)
CLASS(Timer)
STATIC_METHOD("startTimer_(_,_)", timerStartTimer)
END_CLASS
END_MODULE
MODULE(tls)
CLASS(TlsSocket)
ALLOCATE(tlsSocketAllocate)
FINALIZE(tlsSocketFinalize)
METHOD("connect_(_,_,_,_)", tlsSocketConnect)
METHOD("write_(_,_)", tlsSocketWrite)
METHOD("read_(_)", tlsSocketRead)
METHOD("close_()", tlsSocketClose)
END_CLASS
END_MODULE
MODULE(websocket)
END_MODULE
SENTINEL_MODULE
};

163
src/module/base64.c Normal file
View File

@ -0,0 +1,163 @@
// retoor <retoor@molodetz.nl>
#include <stdlib.h>
#include <string.h>
#include "base64.h"
#include "wren.h"
static const char base64_chars[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const unsigned char base64_lookup[256] = {
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64,
64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64
};
void base64Encode(WrenVM* vm) {
int length = 0;
const char* data = wrenGetSlotBytes(vm, 1, &length);
if (data == NULL || length == 0) {
wrenSetSlotString(vm, 0, "");
return;
}
size_t outputLen = 4 * ((length + 2) / 3);
char* output = (char*)malloc(outputLen + 1);
if (output == NULL) {
wrenSetSlotString(vm, 0, "Memory allocation failed.");
wrenAbortFiber(vm, 0);
return;
}
size_t i = 0;
size_t j = 0;
unsigned char a3[3];
unsigned char a4[4];
while (length--) {
a3[i++] = (unsigned char)*(data++);
if (i == 3) {
a4[0] = (a3[0] & 0xfc) >> 2;
a4[1] = ((a3[0] & 0x03) << 4) + ((a3[1] & 0xf0) >> 4);
a4[2] = ((a3[1] & 0x0f) << 2) + ((a3[2] & 0xc0) >> 6);
a4[3] = a3[2] & 0x3f;
for (i = 0; i < 4; i++) {
output[j++] = base64_chars[a4[i]];
}
i = 0;
}
}
if (i > 0) {
for (size_t k = i; k < 3; k++) {
a3[k] = '\0';
}
a4[0] = (a3[0] & 0xfc) >> 2;
a4[1] = ((a3[0] & 0x03) << 4) + ((a3[1] & 0xf0) >> 4);
a4[2] = ((a3[1] & 0x0f) << 2) + ((a3[2] & 0xc0) >> 6);
for (size_t k = 0; k < i + 1; k++) {
output[j++] = base64_chars[a4[k]];
}
while (i++ < 3) {
output[j++] = '=';
}
}
output[j] = '\0';
wrenSetSlotString(vm, 0, output);
free(output);
}
void base64Decode(WrenVM* vm) {
const char* data = wrenGetSlotString(vm, 1);
if (data == NULL) {
wrenSetSlotString(vm, 0, "");
return;
}
size_t inputLen = strlen(data);
if (inputLen == 0) {
wrenSetSlotString(vm, 0, "");
return;
}
size_t padding = 0;
if (inputLen >= 1 && data[inputLen - 1] == '=') padding++;
if (inputLen >= 2 && data[inputLen - 2] == '=') padding++;
size_t outputLen = (inputLen / 4) * 3 - padding;
char* output = (char*)malloc(outputLen + 1);
if (output == NULL) {
wrenSetSlotString(vm, 0, "Memory allocation failed.");
wrenAbortFiber(vm, 0);
return;
}
size_t i = 0;
size_t j = 0;
unsigned char a4[4];
unsigned char a3[3];
while (inputLen--) {
unsigned char c = (unsigned char)*data++;
if (c == '=') break;
if (base64_lookup[c] == 64) continue;
a4[i++] = c;
if (i == 4) {
for (i = 0; i < 4; i++) {
a4[i] = base64_lookup[a4[i]];
}
a3[0] = (a4[0] << 2) + ((a4[1] & 0x30) >> 4);
a3[1] = ((a4[1] & 0x0f) << 4) + ((a4[2] & 0x3c) >> 2);
a3[2] = ((a4[2] & 0x03) << 6) + a4[3];
for (i = 0; i < 3; i++) {
output[j++] = (char)a3[i];
}
i = 0;
}
}
if (i > 0) {
for (size_t k = i; k < 4; k++) {
a4[k] = 0;
}
for (size_t k = 0; k < 4; k++) {
a4[k] = base64_lookup[a4[k]];
}
a3[0] = (a4[0] << 2) + ((a4[1] & 0x30) >> 4);
a3[1] = ((a4[1] & 0x0f) << 4) + ((a4[2] & 0x3c) >> 2);
a3[2] = ((a4[2] & 0x03) << 6) + a4[3];
for (size_t k = 0; k < i - 1; k++) {
output[j++] = (char)a3[k];
}
}
output[j] = '\0';
wrenSetSlotBytes(vm, 0, output, j);
free(output);
}

11
src/module/base64.h Normal file
View File

@ -0,0 +1,11 @@
// retoor <retoor@molodetz.nl>
#ifndef wren_base64_h
#define wren_base64_h
#include "wren.h"
void base64Encode(WrenVM* vm);
void base64Decode(WrenVM* vm);
#endif

28
src/module/base64.wren vendored Normal file
View File

@ -0,0 +1,28 @@
// retoor <retoor@molodetz.nl>
class Base64 {
foreign static encode(data)
foreign static decode(data)
static encodeUrl(data) {
var result = encode(data)
result = result.replace("+", "-")
result = result.replace("/", "_")
while (result.endsWith("=")) {
result = result[0...-1]
}
return result
}
static decodeUrl(data) {
var result = data.replace("-", "+")
result = result.replace("_", "/")
var padding = 4 - result.count % 4
if (padding < 4) {
for (i in 0...padding) {
result = result + "="
}
}
return decode(result)
}
}

View File

@ -0,0 +1,32 @@
// Please do not edit this file. It has been generated automatically
// from `src/module/base64.wren` using `util/wren_to_c_string.py`
static const char* base64ModuleSource =
"// retoor <retoor@molodetz.nl>\n"
"\n"
"class Base64 {\n"
" foreign static encode(data)\n"
" foreign static decode(data)\n"
"\n"
" static encodeUrl(data) {\n"
" var result = encode(data)\n"
" result = result.replace(\"+\", \"-\")\n"
" result = result.replace(\"/\", \"_\")\n"
" while (result.endsWith(\"=\")) {\n"
" result = result[0...-1]\n"
" }\n"
" return result\n"
" }\n"
"\n"
" static decodeUrl(data) {\n"
" var result = data.replace(\"-\", \"+\")\n"
" result = result.replace(\"_\", \"/\")\n"
" var padding = 4 - result.count % 4\n"
" if (padding < 4) {\n"
" for (i in 0...padding) {\n"
" result = result + \"=\"\n"
" }\n"
" }\n"
" return decode(result)\n"
" }\n"
"}\n";

373
src/module/crypto.c Normal file
View File

@ -0,0 +1,373 @@
// retoor <retoor@molodetz.nl>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "crypto.h"
#include "scheduler.h"
#include "wren.h"
#include "vm.h"
#include "uv.h"
#define ROTL32(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
#define ROTR32(x, n) (((x) >> (n)) | ((x) << (32 - (n))))
static void md5Transform(uint32_t state[4], const uint8_t block[64]) {
static const uint32_t k[64] = {
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
};
static const uint8_t r[64] = {
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21
};
uint32_t m[16];
for (int i = 0; i < 16; i++) {
m[i] = block[i * 4] | (block[i * 4 + 1] << 8) |
(block[i * 4 + 2] << 16) | (block[i * 4 + 3] << 24);
}
uint32_t a = state[0], b = state[1], c = state[2], d = state[3];
for (int i = 0; i < 64; i++) {
uint32_t f, g;
if (i < 16) {
f = (b & c) | (~b & d);
g = i;
} else if (i < 32) {
f = (d & b) | (~d & c);
g = (5 * i + 1) % 16;
} else if (i < 48) {
f = b ^ c ^ d;
g = (3 * i + 5) % 16;
} else {
f = c ^ (b | ~d);
g = (7 * i) % 16;
}
uint32_t temp = d;
d = c;
c = b;
b = b + ROTL32(a + f + k[i] + m[g], r[i]);
a = temp;
}
state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
}
static void md5(const uint8_t* data, size_t len, uint8_t digest[16]) {
uint32_t state[4] = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476};
uint8_t block[64];
size_t i;
for (i = 0; i + 64 <= len; i += 64) {
md5Transform(state, data + i);
}
size_t remaining = len - i;
memcpy(block, data + i, remaining);
block[remaining++] = 0x80;
if (remaining > 56) {
memset(block + remaining, 0, 64 - remaining);
md5Transform(state, block);
remaining = 0;
}
memset(block + remaining, 0, 56 - remaining);
uint64_t bits = len * 8;
for (int j = 0; j < 8; j++) {
block[56 + j] = (bits >> (j * 8)) & 0xFF;
}
md5Transform(state, block);
for (int j = 0; j < 4; j++) {
digest[j * 4] = state[j] & 0xFF;
digest[j * 4 + 1] = (state[j] >> 8) & 0xFF;
digest[j * 4 + 2] = (state[j] >> 16) & 0xFF;
digest[j * 4 + 3] = (state[j] >> 24) & 0xFF;
}
}
static void sha1Transform(uint32_t state[5], const uint8_t block[64]) {
uint32_t w[80];
for (int i = 0; i < 16; i++) {
w[i] = (block[i * 4] << 24) | (block[i * 4 + 1] << 16) |
(block[i * 4 + 2] << 8) | block[i * 4 + 3];
}
for (int i = 16; i < 80; i++) {
w[i] = ROTL32(w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16], 1);
}
uint32_t a = state[0], b = state[1], c = state[2], d = state[3], e = state[4];
for (int i = 0; i < 80; i++) {
uint32_t f, k;
if (i < 20) {
f = (b & c) | (~b & d);
k = 0x5A827999;
} else if (i < 40) {
f = b ^ c ^ d;
k = 0x6ED9EBA1;
} else if (i < 60) {
f = (b & c) | (b & d) | (c & d);
k = 0x8F1BBCDC;
} else {
f = b ^ c ^ d;
k = 0xCA62C1D6;
}
uint32_t temp = ROTL32(a, 5) + f + e + k + w[i];
e = d;
d = c;
c = ROTL32(b, 30);
b = a;
a = temp;
}
state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
state[4] += e;
}
static void sha1(const uint8_t* data, size_t len, uint8_t digest[20]) {
uint32_t state[5] = {0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0};
uint8_t block[64];
size_t i;
for (i = 0; i + 64 <= len; i += 64) {
sha1Transform(state, data + i);
}
size_t remaining = len - i;
memcpy(block, data + i, remaining);
block[remaining++] = 0x80;
if (remaining > 56) {
memset(block + remaining, 0, 64 - remaining);
sha1Transform(state, block);
remaining = 0;
}
memset(block + remaining, 0, 56 - remaining);
uint64_t bits = len * 8;
for (int j = 0; j < 8; j++) {
block[56 + j] = (bits >> ((7 - j) * 8)) & 0xFF;
}
sha1Transform(state, block);
for (int j = 0; j < 5; j++) {
digest[j * 4] = (state[j] >> 24) & 0xFF;
digest[j * 4 + 1] = (state[j] >> 16) & 0xFF;
digest[j * 4 + 2] = (state[j] >> 8) & 0xFF;
digest[j * 4 + 3] = state[j] & 0xFF;
}
}
static void sha256Transform(uint32_t state[8], const uint8_t block[64]) {
static const uint32_t k[64] = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
};
uint32_t w[64];
for (int i = 0; i < 16; i++) {
w[i] = (block[i * 4] << 24) | (block[i * 4 + 1] << 16) |
(block[i * 4 + 2] << 8) | block[i * 4 + 3];
}
for (int i = 16; i < 64; i++) {
uint32_t s0 = ROTR32(w[i-15], 7) ^ ROTR32(w[i-15], 18) ^ (w[i-15] >> 3);
uint32_t s1 = ROTR32(w[i-2], 17) ^ ROTR32(w[i-2], 19) ^ (w[i-2] >> 10);
w[i] = w[i-16] + s0 + w[i-7] + s1;
}
uint32_t a = state[0], b = state[1], c = state[2], d = state[3];
uint32_t e = state[4], f = state[5], g = state[6], h = state[7];
for (int i = 0; i < 64; i++) {
uint32_t S1 = ROTR32(e, 6) ^ ROTR32(e, 11) ^ ROTR32(e, 25);
uint32_t ch = (e & f) ^ (~e & g);
uint32_t temp1 = h + S1 + ch + k[i] + w[i];
uint32_t S0 = ROTR32(a, 2) ^ ROTR32(a, 13) ^ ROTR32(a, 22);
uint32_t maj = (a & b) ^ (a & c) ^ (b & c);
uint32_t temp2 = S0 + maj;
h = g; g = f; f = e; e = d + temp1;
d = c; c = b; b = a; a = temp1 + temp2;
}
state[0] += a; state[1] += b; state[2] += c; state[3] += d;
state[4] += e; state[5] += f; state[6] += g; state[7] += h;
}
static void sha256(const uint8_t* data, size_t len, uint8_t digest[32]) {
uint32_t state[8] = {
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
};
uint8_t block[64];
size_t i;
for (i = 0; i + 64 <= len; i += 64) {
sha256Transform(state, data + i);
}
size_t remaining = len - i;
memcpy(block, data + i, remaining);
block[remaining++] = 0x80;
if (remaining > 56) {
memset(block + remaining, 0, 64 - remaining);
sha256Transform(state, block);
remaining = 0;
}
memset(block + remaining, 0, 56 - remaining);
uint64_t bits = len * 8;
for (int j = 0; j < 8; j++) {
block[56 + j] = (bits >> ((7 - j) * 8)) & 0xFF;
}
sha256Transform(state, block);
for (int j = 0; j < 8; j++) {
digest[j * 4] = (state[j] >> 24) & 0xFF;
digest[j * 4 + 1] = (state[j] >> 16) & 0xFF;
digest[j * 4 + 2] = (state[j] >> 8) & 0xFF;
digest[j * 4 + 3] = state[j] & 0xFF;
}
}
static void randomCallback(uv_random_t* req, int status, void* buf, size_t len) {
WrenHandle* fiber = (WrenHandle*)req->data;
WrenVM* vm = getVM();
wrenEnsureSlots(vm, 3);
wrenSetSlotNewList(vm, 2);
if (status == 0) {
uint8_t* bytes = (uint8_t*)buf;
for (size_t i = 0; i < len; i++) {
wrenSetSlotDouble(vm, 1, (double)bytes[i]);
wrenInsertInList(vm, 2, -1, 1);
}
}
free(buf);
free(req);
schedulerResume(fiber, true);
schedulerFinishResume();
}
void cryptoRandomBytes(WrenVM* vm) {
int length = (int)wrenGetSlotDouble(vm, 1);
WrenHandle* fiber = wrenGetSlotHandle(vm, 2);
if (length < 0 || length > 65536) {
wrenSetSlotString(vm, 0, "Length must be between 0 and 65536.");
wrenAbortFiber(vm, 0);
return;
}
uv_random_t* req = (uv_random_t*)malloc(sizeof(uv_random_t));
void* buf = malloc(length);
if (req == NULL || buf == NULL) {
if (req) free(req);
if (buf) free(buf);
wrenReleaseHandle(vm, fiber);
wrenSetSlotString(vm, 0, "Memory allocation failed.");
wrenAbortFiber(vm, 0);
return;
}
req->data = fiber;
int result = uv_random(getLoop(), req, buf, length, 0, randomCallback);
if (result != 0) {
free(req);
free(buf);
wrenReleaseHandle(vm, fiber);
wrenSetSlotString(vm, 0, uv_strerror(result));
wrenAbortFiber(vm, 0);
}
}
static void hashHelper(WrenVM* vm, void (*hashFn)(const uint8_t*, size_t, uint8_t*), int digestLen) {
int count = wrenGetListCount(vm, 1);
uint8_t* data = (uint8_t*)malloc(count);
if (data == NULL) {
wrenSetSlotString(vm, 0, "Memory allocation failed.");
wrenAbortFiber(vm, 0);
return;
}
wrenEnsureSlots(vm, 3);
for (int i = 0; i < count; i++) {
wrenGetListElement(vm, 1, i, 2);
data[i] = (uint8_t)wrenGetSlotDouble(vm, 2);
}
uint8_t* digest = (uint8_t*)malloc(digestLen);
if (digest == NULL) {
free(data);
wrenSetSlotString(vm, 0, "Memory allocation failed.");
wrenAbortFiber(vm, 0);
return;
}
hashFn(data, count, digest);
free(data);
wrenSetSlotNewList(vm, 0);
for (int i = 0; i < digestLen; i++) {
wrenSetSlotDouble(vm, 2, (double)digest[i]);
wrenInsertInList(vm, 0, -1, 2);
}
free(digest);
}
void cryptoMd5(WrenVM* vm) {
hashHelper(vm, md5, 16);
}
void cryptoSha1(WrenVM* vm) {
hashHelper(vm, sha1, 20);
}
void cryptoSha256(WrenVM* vm) {
hashHelper(vm, sha256, 32);
}

13
src/module/crypto.h Normal file
View File

@ -0,0 +1,13 @@
// retoor <retoor@molodetz.nl>
#ifndef wren_crypto_h
#define wren_crypto_h
#include "wren.h"
void cryptoRandomBytes(WrenVM* vm);
void cryptoMd5(WrenVM* vm);
void cryptoSha1(WrenVM* vm);
void cryptoSha256(WrenVM* vm);
#endif

63
src/module/crypto.wren vendored Normal file
View File

@ -0,0 +1,63 @@
// retoor <retoor@molodetz.nl>
import "scheduler" for Scheduler
class Crypto {
foreign static randomBytes_(length, fiber)
static randomBytes(length) {
if (!(length is Num)) Fiber.abort("Length must be a number.")
if (length < 0) Fiber.abort("Length must be non-negative.")
randomBytes_(length, Fiber.current)
return Scheduler.runNextScheduled_()
}
static randomInt(min, max) {
if (!(min is Num) || !(max is Num)) Fiber.abort("Arguments must be numbers.")
if (min >= max) Fiber.abort("Min must be less than max.")
var range = max - min
var bytes = randomBytes(4)
var value = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]
if (value < 0) value = -value
return min + (value % range)
}
}
class Hash {
foreign static md5_(data)
foreign static sha1_(data)
foreign static sha256_(data)
static md5(data) {
if (data is String) data = data.bytes.toList
if (!(data is List)) Fiber.abort("Data must be a String or List of bytes.")
return md5_(data)
}
static sha1(data) {
if (data is String) data = data.bytes.toList
if (!(data is List)) Fiber.abort("Data must be a String or List of bytes.")
return sha1_(data)
}
static sha256(data) {
if (data is String) data = data.bytes.toList
if (!(data is List)) Fiber.abort("Data must be a String or List of bytes.")
return sha256_(data)
}
static toHex(bytes) {
var hex = ""
for (b in bytes) {
var hi = (b >> 4) & 0x0F
var lo = b & 0x0F
hex = hex + hexDigit_(hi) + hexDigit_(lo)
}
return hex
}
static hexDigit_(n) {
if (n < 10) return String.fromCodePoint(48 + n)
return String.fromCodePoint(97 + n - 10)
}
}

View File

@ -0,0 +1,67 @@
// Please do not edit this file. It has been generated automatically
// from `src/module/crypto.wren` using `util/wren_to_c_string.py`
static const char* cryptoModuleSource =
"// retoor <retoor@molodetz.nl>\n"
"\n"
"import \"scheduler\" for Scheduler\n"
"\n"
"class Crypto {\n"
" foreign static randomBytes_(length, fiber)\n"
"\n"
" static randomBytes(length) {\n"
" if (!(length is Num)) Fiber.abort(\"Length must be a number.\")\n"
" if (length < 0) Fiber.abort(\"Length must be non-negative.\")\n"
" randomBytes_(length, Fiber.current)\n"
" return Scheduler.runNextScheduled_()\n"
" }\n"
"\n"
" static randomInt(min, max) {\n"
" if (!(min is Num) || !(max is Num)) Fiber.abort(\"Arguments must be numbers.\")\n"
" if (min >= max) Fiber.abort(\"Min must be less than max.\")\n"
" var range = max - min\n"
" var bytes = randomBytes(4)\n"
" var value = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]\n"
" if (value < 0) value = -value\n"
" return min + (value % range)\n"
" }\n"
"}\n"
"\n"
"class Hash {\n"
" foreign static md5_(data)\n"
" foreign static sha1_(data)\n"
" foreign static sha256_(data)\n"
"\n"
" static md5(data) {\n"
" if (data is String) data = data.bytes.toList\n"
" if (!(data is List)) Fiber.abort(\"Data must be a String or List of bytes.\")\n"
" return md5_(data)\n"
" }\n"
"\n"
" static sha1(data) {\n"
" if (data is String) data = data.bytes.toList\n"
" if (!(data is List)) Fiber.abort(\"Data must be a String or List of bytes.\")\n"
" return sha1_(data)\n"
" }\n"
"\n"
" static sha256(data) {\n"
" if (data is String) data = data.bytes.toList\n"
" if (!(data is List)) Fiber.abort(\"Data must be a String or List of bytes.\")\n"
" return sha256_(data)\n"
" }\n"
"\n"
" static toHex(bytes) {\n"
" var hex = \"\"\n"
" for (b in bytes) {\n"
" var hi = (b >> 4) & 0x0F\n"
" var lo = b & 0x0F\n"
" hex = hex + hexDigit_(hi) + hexDigit_(lo)\n"
" }\n"
" return hex\n"
" }\n"
"\n"
" static hexDigit_(n) {\n"
" if (n < 10) return String.fromCodePoint(48 + n)\n"
" return String.fromCodePoint(97 + n - 10)\n"
" }\n"
"}\n";

74
src/module/datetime.c Normal file
View File

@ -0,0 +1,74 @@
// retoor <retoor@molodetz.nl>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "datetime.h"
#include "wren.h"
void datetimeNow(WrenVM* vm) {
time_t now = time(NULL);
wrenSetSlotDouble(vm, 0, (double)now);
}
void datetimeFromTimestamp(WrenVM* vm) {
double timestamp = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, timestamp);
}
void datetimeFormat(WrenVM* vm) {
double timestamp = wrenGetSlotDouble(vm, 1);
const char* pattern = wrenGetSlotString(vm, 2);
if (pattern == NULL) {
wrenSetSlotString(vm, 0, "");
return;
}
time_t t = (time_t)timestamp;
struct tm* tm_info = localtime(&t);
if (tm_info == NULL) {
wrenSetSlotString(vm, 0, "");
return;
}
char buffer[256];
size_t result = strftime(buffer, sizeof(buffer), pattern, tm_info);
if (result == 0) {
wrenSetSlotString(vm, 0, "");
return;
}
wrenSetSlotString(vm, 0, buffer);
}
void datetimeComponent(WrenVM* vm) {
double timestamp = wrenGetSlotDouble(vm, 1);
int index = (int)wrenGetSlotDouble(vm, 2);
time_t t = (time_t)timestamp;
struct tm* tm_info = localtime(&t);
if (tm_info == NULL) {
wrenSetSlotDouble(vm, 0, 0);
return;
}
int result = 0;
switch (index) {
case 0: result = tm_info->tm_year + 1900; break;
case 1: result = tm_info->tm_mon + 1; break;
case 2: result = tm_info->tm_mday; break;
case 3: result = tm_info->tm_hour; break;
case 4: result = tm_info->tm_min; break;
case 5: result = tm_info->tm_sec; break;
case 6: result = tm_info->tm_wday; break;
case 7: result = tm_info->tm_yday + 1; break;
case 8: result = tm_info->tm_isdst; break;
default: result = 0; break;
}
wrenSetSlotDouble(vm, 0, (double)result);
}

13
src/module/datetime.h Normal file
View File

@ -0,0 +1,13 @@
// retoor <retoor@molodetz.nl>
#ifndef wren_datetime_h
#define wren_datetime_h
#include "wren.h"
void datetimeNow(WrenVM* vm);
void datetimeFromTimestamp(WrenVM* vm);
void datetimeFormat(WrenVM* vm);
void datetimeComponent(WrenVM* vm);
#endif

113
src/module/datetime.wren vendored Normal file
View File

@ -0,0 +1,113 @@
// retoor <retoor@molodetz.nl>
class DateTime {
foreign static now_()
foreign static fromTimestamp_(timestamp)
foreign static format_(timestamp, pattern)
construct now() {
_timestamp = DateTime.now_()
}
construct fromTimestamp(timestamp) {
_timestamp = timestamp
}
static parse(string, format) {
Fiber.abort("DateTime.parse not implemented yet")
}
timestamp { _timestamp }
year { DateTime.component_(_timestamp, 0) }
month { DateTime.component_(_timestamp, 1) }
day { DateTime.component_(_timestamp, 2) }
hour { DateTime.component_(_timestamp, 3) }
minute { DateTime.component_(_timestamp, 4) }
second { DateTime.component_(_timestamp, 5) }
dayOfWeek { DateTime.component_(_timestamp, 6) }
dayOfYear { DateTime.component_(_timestamp, 7) }
isDst { DateTime.component_(_timestamp, 8) == 1 }
foreign static component_(timestamp, index)
format(pattern) { DateTime.format_(_timestamp, pattern) }
toIso8601 { format("\%Y-\%m-\%dT\%H:\%M:\%S") }
toString { toIso8601 }
+(duration) {
if (!(duration is Duration)) Fiber.abort("Expected Duration.")
return DateTime.fromTimestamp(_timestamp + duration.seconds)
}
-(other) {
if (other is DateTime) {
return Duration.fromSeconds(_timestamp - other.timestamp)
}
if (other is Duration) {
return DateTime.fromTimestamp(_timestamp - other.seconds)
}
Fiber.abort("Expected DateTime or Duration.")
}
==(other) {
if (!(other is DateTime)) return false
return _timestamp == other.timestamp
}
<(other) { _timestamp < other.timestamp }
>(other) { _timestamp > other.timestamp }
<=(other) { _timestamp <= other.timestamp }
>=(other) { _timestamp >= other.timestamp }
}
class Duration {
construct fromMilliseconds(ms) {
_ms = ms
}
construct fromSeconds(s) {
_ms = s * 1000
}
construct fromMinutes(m) {
_ms = m * 60 * 1000
}
construct fromHours(h) {
_ms = h * 60 * 60 * 1000
}
construct fromDays(d) {
_ms = d * 24 * 60 * 60 * 1000
}
milliseconds { _ms }
seconds { _ms / 1000 }
minutes { _ms / 60000 }
hours { _ms / 3600000 }
days { _ms / 86400000 }
+(other) {
if (!(other is Duration)) Fiber.abort("Expected Duration.")
return Duration.fromMilliseconds(_ms + other.milliseconds)
}
-(other) {
if (!(other is Duration)) Fiber.abort("Expected Duration.")
return Duration.fromMilliseconds(_ms - other.milliseconds)
}
*(factor) {
return Duration.fromMilliseconds(_ms * factor)
}
==(other) {
if (!(other is Duration)) return false
return _ms == other.milliseconds
}
toString { "%(days.floor)d %(hours.floor % 24)h %(minutes.floor % 60)m %(seconds.floor % 60)s" }
}

View File

@ -0,0 +1,117 @@
// Please do not edit this file. It has been generated automatically
// from `src/module/datetime.wren` using `util/wren_to_c_string.py`
static const char* datetimeModuleSource =
"// retoor <retoor@molodetz.nl>\n"
"\n"
"class DateTime {\n"
" foreign static now_()\n"
" foreign static fromTimestamp_(timestamp)\n"
" foreign static format_(timestamp, pattern)\n"
"\n"
" construct now() {\n"
" _timestamp = DateTime.now_()\n"
" }\n"
"\n"
" construct fromTimestamp(timestamp) {\n"
" _timestamp = timestamp\n"
" }\n"
"\n"
" static parse(string, format) {\n"
" Fiber.abort(\"DateTime.parse not implemented yet\")\n"
" }\n"
"\n"
" timestamp { _timestamp }\n"
"\n"
" year { DateTime.component_(_timestamp, 0) }\n"
" month { DateTime.component_(_timestamp, 1) }\n"
" day { DateTime.component_(_timestamp, 2) }\n"
" hour { DateTime.component_(_timestamp, 3) }\n"
" minute { DateTime.component_(_timestamp, 4) }\n"
" second { DateTime.component_(_timestamp, 5) }\n"
" dayOfWeek { DateTime.component_(_timestamp, 6) }\n"
" dayOfYear { DateTime.component_(_timestamp, 7) }\n"
" isDst { DateTime.component_(_timestamp, 8) == 1 }\n"
"\n"
" foreign static component_(timestamp, index)\n"
"\n"
" format(pattern) { DateTime.format_(_timestamp, pattern) }\n"
"\n"
" toIso8601 { format(\"\\%Y-\\%m-\\%dT\\%H:\\%M:\\%S\") }\n"
"\n"
" toString { toIso8601 }\n"
"\n"
" +(duration) {\n"
" if (!(duration is Duration)) Fiber.abort(\"Expected Duration.\")\n"
" return DateTime.fromTimestamp(_timestamp + duration.seconds)\n"
" }\n"
"\n"
" -(other) {\n"
" if (other is DateTime) {\n"
" return Duration.fromSeconds(_timestamp - other.timestamp)\n"
" }\n"
" if (other is Duration) {\n"
" return DateTime.fromTimestamp(_timestamp - other.seconds)\n"
" }\n"
" Fiber.abort(\"Expected DateTime or Duration.\")\n"
" }\n"
"\n"
" ==(other) {\n"
" if (!(other is DateTime)) return false\n"
" return _timestamp == other.timestamp\n"
" }\n"
"\n"
" <(other) { _timestamp < other.timestamp }\n"
" >(other) { _timestamp > other.timestamp }\n"
" <=(other) { _timestamp <= other.timestamp }\n"
" >=(other) { _timestamp >= other.timestamp }\n"
"}\n"
"\n"
"class Duration {\n"
" construct fromMilliseconds(ms) {\n"
" _ms = ms\n"
" }\n"
"\n"
" construct fromSeconds(s) {\n"
" _ms = s * 1000\n"
" }\n"
"\n"
" construct fromMinutes(m) {\n"
" _ms = m * 60 * 1000\n"
" }\n"
"\n"
" construct fromHours(h) {\n"
" _ms = h * 60 * 60 * 1000\n"
" }\n"
"\n"
" construct fromDays(d) {\n"
" _ms = d * 24 * 60 * 60 * 1000\n"
" }\n"
"\n"
" milliseconds { _ms }\n"
" seconds { _ms / 1000 }\n"
" minutes { _ms / 60000 }\n"
" hours { _ms / 3600000 }\n"
" days { _ms / 86400000 }\n"
"\n"
" +(other) {\n"
" if (!(other is Duration)) Fiber.abort(\"Expected Duration.\")\n"
" return Duration.fromMilliseconds(_ms + other.milliseconds)\n"
" }\n"
"\n"
" -(other) {\n"
" if (!(other is Duration)) Fiber.abort(\"Expected Duration.\")\n"
" return Duration.fromMilliseconds(_ms - other.milliseconds)\n"
" }\n"
"\n"
" *(factor) {\n"
" return Duration.fromMilliseconds(_ms * factor)\n"
" }\n"
"\n"
" ==(other) {\n"
" if (!(other is Duration)) return false\n"
" return _ms == other.milliseconds\n"
" }\n"
"\n"
" toString { \"%(days.floor)d %(hours.floor % 24)h %(minutes.floor % 60)m %(seconds.floor % 60)s\" }\n"
"}\n";

83
src/module/dns.c Normal file
View File

@ -0,0 +1,83 @@
// retoor <retoor@molodetz.nl>
#include <stdlib.h>
#include <string.h>
#include "dns.h"
#include "scheduler.h"
#include "wren.h"
#include "vm.h"
#include "uv.h"
static void dnsCallback(uv_getaddrinfo_t* req, int status, struct addrinfo* res) {
WrenHandle* fiber = (WrenHandle*)req->data;
WrenVM* vm = getVM();
wrenEnsureSlots(vm, 3);
wrenSetSlotNewList(vm, 2);
if (status == 0 && res != NULL) {
struct addrinfo* addr = res;
while (addr != NULL) {
char ip[64];
if (addr->ai_family == AF_INET) {
struct sockaddr_in* sa = (struct sockaddr_in*)addr->ai_addr;
uv_ip4_name(sa, ip, sizeof(ip));
wrenSetSlotString(vm, 1, ip);
wrenInsertInList(vm, 2, -1, 1);
} else if (addr->ai_family == AF_INET6) {
struct sockaddr_in6* sa = (struct sockaddr_in6*)addr->ai_addr;
uv_ip6_name(sa, ip, sizeof(ip));
wrenSetSlotString(vm, 1, ip);
wrenInsertInList(vm, 2, -1, 1);
}
addr = addr->ai_next;
}
uv_freeaddrinfo(res);
}
free(req);
schedulerResume(fiber, true);
schedulerFinishResume();
}
void dnsLookup(WrenVM* vm) {
const char* hostname = wrenGetSlotString(vm, 1);
int family = (int)wrenGetSlotDouble(vm, 2);
WrenHandle* fiber = wrenGetSlotHandle(vm, 3);
if (hostname == NULL) {
wrenSetSlotString(vm, 0, "Hostname required.");
wrenAbortFiber(vm, 0);
return;
}
uv_getaddrinfo_t* req = (uv_getaddrinfo_t*)malloc(sizeof(uv_getaddrinfo_t));
if (req == NULL) {
wrenSetSlotString(vm, 0, "Memory allocation failed.");
wrenAbortFiber(vm, 0);
return;
}
req->data = fiber;
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_socktype = SOCK_STREAM;
if (family == 4) {
hints.ai_family = AF_INET;
} else if (family == 6) {
hints.ai_family = AF_INET6;
} else {
hints.ai_family = AF_UNSPEC;
}
int result = uv_getaddrinfo(getLoop(), req, dnsCallback, hostname, NULL, &hints);
if (result != 0) {
free(req);
wrenReleaseHandle(vm, fiber);
wrenSetSlotString(vm, 0, uv_strerror(result));
wrenAbortFiber(vm, 0);
return;
}
}

10
src/module/dns.h Normal file
View File

@ -0,0 +1,10 @@
// retoor <retoor@molodetz.nl>
#ifndef wren_dns_h
#define wren_dns_h
#include "wren.h"
void dnsLookup(WrenVM* vm);
#endif

17
src/module/dns.wren vendored Normal file
View File

@ -0,0 +1,17 @@
// retoor <retoor@molodetz.nl>
import "scheduler" for Scheduler
class Dns {
static lookup(hostname) { lookup(hostname, 0) }
static lookup(hostname, family) {
if (!(hostname is String)) Fiber.abort("Hostname must be a string.")
if (family != 0 && family != 4 && family != 6) {
Fiber.abort("Family must be 0 (any), 4 (IPv4), or 6 (IPv6).")
}
return Scheduler.await_ { lookup_(hostname, family, Fiber.current) }
}
foreign static lookup_(hostname, family, fiber)
}

21
src/module/dns.wren.inc Normal file
View File

@ -0,0 +1,21 @@
// Please do not edit this file. It has been generated automatically
// from `src/module/dns.wren` using `util/wren_to_c_string.py`
static const char* dnsModuleSource =
"// retoor <retoor@molodetz.nl>\n"
"\n"
"import \"scheduler\" for Scheduler\n"
"\n"
"class Dns {\n"
" static lookup(hostname) { lookup(hostname, 0) }\n"
"\n"
" static lookup(hostname, family) {\n"
" if (!(hostname is String)) Fiber.abort(\"Hostname must be a string.\")\n"
" if (family != 0 && family != 4 && family != 6) {\n"
" Fiber.abort(\"Family must be 0 (any), 4 (IPv4), or 6 (IPv6).\")\n"
" }\n"
" return Scheduler.await_ { lookup_(hostname, family, Fiber.current) }\n"
" }\n"
"\n"
" foreign static lookup_(hostname, family, fiber)\n"
"}\n";

108
src/module/env.c Normal file
View File

@ -0,0 +1,108 @@
// retoor <retoor@molodetz.nl>
#include <stdlib.h>
#include <string.h>
#include "env.h"
#include "wren.h"
#include "uv.h"
void envGet(WrenVM* vm) {
const char* name = wrenGetSlotString(vm, 1);
if (name == NULL) {
wrenSetSlotNull(vm, 0);
return;
}
char buffer[1024];
size_t size = sizeof(buffer);
int result = uv_os_getenv(name, buffer, &size);
if (result == UV_ENOENT) {
wrenSetSlotNull(vm, 0);
return;
}
if (result == UV_ENOBUFS) {
char* largeBuffer = (char*)malloc(size);
if (largeBuffer == NULL) {
wrenSetSlotNull(vm, 0);
return;
}
result = uv_os_getenv(name, largeBuffer, &size);
if (result == 0) {
wrenSetSlotString(vm, 0, largeBuffer);
} else {
wrenSetSlotNull(vm, 0);
}
free(largeBuffer);
return;
}
if (result != 0) {
wrenSetSlotNull(vm, 0);
return;
}
wrenSetSlotString(vm, 0, buffer);
}
void envSet(WrenVM* vm) {
const char* name = wrenGetSlotString(vm, 1);
const char* value = wrenGetSlotString(vm, 2);
if (name == NULL || value == NULL) {
wrenSetSlotString(vm, 0, "Name and value must be strings.");
wrenAbortFiber(vm, 0);
return;
}
int result = uv_os_setenv(name, value);
if (result != 0) {
wrenSetSlotString(vm, 0, "Failed to set environment variable.");
wrenAbortFiber(vm, 0);
return;
}
wrenSetSlotNull(vm, 0);
}
void envDelete(WrenVM* vm) {
const char* name = wrenGetSlotString(vm, 1);
if (name == NULL) {
wrenSetSlotString(vm, 0, "Name must be a string.");
wrenAbortFiber(vm, 0);
return;
}
int result = uv_os_unsetenv(name);
if (result != 0) {
wrenSetSlotString(vm, 0, "Failed to delete environment variable.");
wrenAbortFiber(vm, 0);
return;
}
wrenSetSlotNull(vm, 0);
}
void envAll(WrenVM* vm) {
uv_env_item_t* envItems = NULL;
int count = 0;
int result = uv_os_environ(&envItems, &count);
if (result != 0) {
wrenSetSlotNewMap(vm, 0);
return;
}
wrenEnsureSlots(vm, 3);
wrenSetSlotNewMap(vm, 0);
for (int i = 0; i < count; i++) {
wrenSetSlotString(vm, 1, envItems[i].name);
wrenSetSlotString(vm, 2, envItems[i].value);
wrenSetMapValue(vm, 0, 1, 2);
}
uv_os_free_environ(envItems, count);
}

13
src/module/env.h Normal file
View File

@ -0,0 +1,13 @@
// retoor <retoor@molodetz.nl>
#ifndef wren_env_h
#define wren_env_h
#include "wren.h"
void envGet(WrenVM* vm);
void envSet(WrenVM* vm);
void envDelete(WrenVM* vm);
void envAll(WrenVM* vm);
#endif

8
src/module/env.wren vendored Normal file
View File

@ -0,0 +1,8 @@
// retoor <retoor@molodetz.nl>
class Environment {
foreign static get(name)
foreign static set(name, value)
foreign static delete(name)
foreign static all
}

12
src/module/env.wren.inc Normal file
View File

@ -0,0 +1,12 @@
// Please do not edit this file. It has been generated automatically
// from `src/module/env.wren` using `util/wren_to_c_string.py`
static const char* envModuleSource =
"// retoor <retoor@molodetz.nl>\n"
"\n"
"class Environment {\n"
" foreign static get(name)\n"
" foreign static set(name, value)\n"
" foreign static delete(name)\n"
" foreign static all\n"
"}\n";

304
src/module/http.wren vendored Normal file
View File

@ -0,0 +1,304 @@
// retoor <retoor@molodetz.nl>
import "net" for Socket
import "tls" for TlsSocket
import "dns" for Dns
import "json" for Json
import "base64" for Base64
class Url {
construct parse(url) {
_scheme = "http"
_host = ""
_port = 80
_path = "/"
_query = ""
var rest = url
var schemeEnd = rest.indexOf("://")
if (schemeEnd >= 0) {
_scheme = Url.toLower_(rest[0...schemeEnd])
rest = rest[(schemeEnd + 3)..-1]
if (_scheme == "https") _port = 443
}
var pathStart = rest.indexOf("/")
var hostPart = pathStart >= 0 ? rest[0...pathStart] : rest
var pathPart = pathStart >= 0 ? rest[pathStart..-1] : "/"
var queryStart = pathPart.indexOf("?")
if (queryStart >= 0) {
_query = pathPart[(queryStart + 1)..-1]
_path = pathPart[0...queryStart]
} else {
_path = pathPart
}
var portStart = hostPart.indexOf(":")
if (portStart >= 0) {
_host = hostPart[0...portStart]
_port = Num.fromString(hostPart[(portStart + 1)..-1])
} else {
_host = hostPart
}
}
static toLower_(str) {
var result = ""
for (c in str) {
var cp = c.codePoints[0]
if (cp >= 65 && cp <= 90) {
result = result + String.fromCodePoint(cp + 32)
} else {
result = result + c
}
}
return result
}
scheme { _scheme }
host { _host }
port { _port }
path { _path }
query { _query }
fullPath {
if (_query.count > 0) return _path + "?" + _query
return _path
}
}
class HttpResponse {
construct new_(statusCode, statusText, headers, body) {
_statusCode = statusCode
_statusText = statusText
_headers = headers
_body = body
}
statusCode { _statusCode }
statusText { _statusText }
headers { _headers }
body { _body }
ok { _statusCode >= 200 && _statusCode < 300 }
header(name) {
var lower = Url.toLower_(name)
for (entry in _headers) {
if (Url.toLower_(entry.key) == lower) return entry.value
}
return null
}
json {
if (_body == null || _body.count == 0) return null
return Json.parse(_body)
}
toString { "HttpResponse(%(statusCode) %(statusText))" }
}
class Http {
static get(url) { request(url, "GET", null, {}) }
static get(url, headers) { request(url, "GET", null, headers) }
static post(url, body) { request(url, "POST", body, {}) }
static post(url, body, headers) { request(url, "POST", body, headers) }
static put(url, body) { request(url, "PUT", body, {}) }
static put(url, body, headers) { request(url, "PUT", body, headers) }
static delete(url) { request(url, "DELETE", null, {}) }
static delete(url, headers) { request(url, "DELETE", null, headers) }
static patch(url, body) { request(url, "PATCH", body, {}) }
static patch(url, body, headers) { request(url, "PATCH", body, headers) }
static request(url, method, body, headers) {
var parsed = Url.parse(url)
if (parsed.scheme != "http" && parsed.scheme != "https") {
Fiber.abort("Unsupported scheme: %(parsed.scheme)")
}
var isHttps = parsed.scheme == "https"
var bodyStr = ""
if (body != null) {
if (body is String) {
bodyStr = body
} else if (body is Map || body is List) {
bodyStr = Json.stringify(body)
if (!hasHeader_(headers, "Content-Type")) {
headers["Content-Type"] = "application/json"
}
} else {
bodyStr = body.toString
}
}
var requestHeaders = {}
requestHeaders["Host"] = parsed.host
if ((isHttps && parsed.port != 443) || (!isHttps && parsed.port != 80)) {
requestHeaders["Host"] = "%(parsed.host):%(parsed.port)"
}
requestHeaders["Connection"] = "close"
requestHeaders["User-Agent"] = "Wren-CLI/1.0"
if (bodyStr.count > 0) {
requestHeaders["Content-Length"] = bodyStr.count.toString
}
for (entry in headers) {
requestHeaders[entry.key] = entry.value
}
var request = "%(method) %(parsed.fullPath) HTTP/1.1\r\n"
for (entry in requestHeaders) {
request = request + "%(entry.key): %(entry.value)\r\n"
}
request = request + "\r\n" + bodyStr
var addresses = Dns.lookup(parsed.host, 4)
if (addresses.count == 0) {
Fiber.abort("Could not resolve host: %(parsed.host)")
}
var socket
if (isHttps) {
socket = TlsSocket.connect(addresses[0], parsed.port, parsed.host)
} else {
socket = Socket.connect(addresses[0], parsed.port)
}
socket.write(request)
var response = ""
while (true) {
var chunk = socket.read()
if (chunk == null || chunk.count == 0) break
response = response + chunk
}
socket.close()
return parseResponse_(response)
}
static hasHeader_(headers, name) {
var lower = Url.toLower_(name)
for (entry in headers) {
if (Url.toLower_(entry.key) == lower) return true
}
return false
}
static parseResponse_(response) {
var headerEnd = response.indexOf("\r\n\r\n")
if (headerEnd < 0) {
Fiber.abort("Invalid HTTP response: no header/body separator")
}
var headerPart = response[0...headerEnd]
var body = response[(headerEnd + 4)..-1]
var lines = splitLines_(headerPart)
if (lines.count == 0) {
Fiber.abort("Invalid HTTP response: empty headers")
}
var statusLine = lines[0]
var statusParts = statusLine.split(" ")
if (statusParts.count < 2) {
Fiber.abort("Invalid HTTP status line")
}
var statusCode = Num.fromString(statusParts[1])
var statusText = statusParts.count > 2 ? statusParts[2..-1].join(" ") : ""
var headers = {}
for (i in 1...lines.count) {
var line = lines[i]
var colonPos = line.indexOf(":")
if (colonPos > 0) {
var name = line[0...colonPos].trim()
var value = line[(colonPos + 1)..-1].trim()
headers[name] = value
}
}
var transferEncoding = null
for (entry in headers) {
if (Url.toLower_(entry.key) == "transfer-encoding") {
transferEncoding = Url.toLower_(entry.value)
break
}
}
if (transferEncoding == "chunked") {
body = decodeChunked_(body)
}
return HttpResponse.new_(statusCode, statusText, headers, body)
}
static splitLines_(text) {
var lines = []
var start = 0
var i = 0
while (i < text.count) {
if (text[i] == "\r" && i + 1 < text.count && text[i + 1] == "\n") {
lines.add(text[start...i])
start = i + 2
i = i + 2
} else {
i = i + 1
}
}
if (start < text.count) {
lines.add(text[start..-1])
}
return lines
}
static decodeChunked_(body) {
var result = ""
var pos = 0
while (pos < body.count) {
var lineEnd = body.indexOf("\r\n", pos)
if (lineEnd < 0) break
var sizeStr = body[pos...lineEnd].trim()
var size = parseHex_(sizeStr)
if (size == 0) break
pos = lineEnd + 2
if (pos + size > body.count) break
result = result + body[pos...(pos + size)]
pos = pos + size + 2
}
return result
}
static parseHex_(str) {
var result = 0
for (c in str) {
var cp = c.codePoints[0]
var digit = 0
if (cp >= 48 && cp <= 57) {
digit = cp - 48
} else if (cp >= 65 && cp <= 70) {
digit = cp - 55
} else if (cp >= 97 && cp <= 102) {
digit = cp - 87
} else {
break
}
result = result * 16 + digit
}
return result
}
}

308
src/module/http.wren.inc Normal file
View File

@ -0,0 +1,308 @@
// Please do not edit this file. It has been generated automatically
// from `/home/retoor/projects/wren-cli/src/module/http.wren` using `util/wren_to_c_string.py`
static const char* httpModuleSource =
"// retoor <retoor@molodetz.nl>\n"
"\n"
"import \"net\" for Socket\n"
"import \"tls\" for TlsSocket\n"
"import \"dns\" for Dns\n"
"import \"json\" for Json\n"
"import \"base64\" for Base64\n"
"\n"
"class Url {\n"
" construct parse(url) {\n"
" _scheme = \"http\"\n"
" _host = \"\"\n"
" _port = 80\n"
" _path = \"/\"\n"
" _query = \"\"\n"
"\n"
" var rest = url\n"
"\n"
" var schemeEnd = rest.indexOf(\"://\")\n"
" if (schemeEnd >= 0) {\n"
" _scheme = Url.toLower_(rest[0...schemeEnd])\n"
" rest = rest[(schemeEnd + 3)..-1]\n"
" if (_scheme == \"https\") _port = 443\n"
" }\n"
"\n"
" var pathStart = rest.indexOf(\"/\")\n"
" var hostPart = pathStart >= 0 ? rest[0...pathStart] : rest\n"
" var pathPart = pathStart >= 0 ? rest[pathStart..-1] : \"/\"\n"
"\n"
" var queryStart = pathPart.indexOf(\"?\")\n"
" if (queryStart >= 0) {\n"
" _query = pathPart[(queryStart + 1)..-1]\n"
" _path = pathPart[0...queryStart]\n"
" } else {\n"
" _path = pathPart\n"
" }\n"
"\n"
" var portStart = hostPart.indexOf(\":\")\n"
" if (portStart >= 0) {\n"
" _host = hostPart[0...portStart]\n"
" _port = Num.fromString(hostPart[(portStart + 1)..-1])\n"
" } else {\n"
" _host = hostPart\n"
" }\n"
" }\n"
"\n"
" static toLower_(str) {\n"
" var result = \"\"\n"
" for (c in str) {\n"
" var cp = c.codePoints[0]\n"
" if (cp >= 65 && cp <= 90) {\n"
" result = result + String.fromCodePoint(cp + 32)\n"
" } else {\n"
" result = result + c\n"
" }\n"
" }\n"
" return result\n"
" }\n"
"\n"
" scheme { _scheme }\n"
" host { _host }\n"
" port { _port }\n"
" path { _path }\n"
" query { _query }\n"
"\n"
" fullPath {\n"
" if (_query.count > 0) return _path + \"?\" + _query\n"
" return _path\n"
" }\n"
"}\n"
"\n"
"class HttpResponse {\n"
" construct new_(statusCode, statusText, headers, body) {\n"
" _statusCode = statusCode\n"
" _statusText = statusText\n"
" _headers = headers\n"
" _body = body\n"
" }\n"
"\n"
" statusCode { _statusCode }\n"
" statusText { _statusText }\n"
" headers { _headers }\n"
" body { _body }\n"
" ok { _statusCode >= 200 && _statusCode < 300 }\n"
"\n"
" header(name) {\n"
" var lower = Url.toLower_(name)\n"
" for (entry in _headers) {\n"
" if (Url.toLower_(entry.key) == lower) return entry.value\n"
" }\n"
" return null\n"
" }\n"
"\n"
" json {\n"
" if (_body == null || _body.count == 0) return null\n"
" return Json.parse(_body)\n"
" }\n"
"\n"
" toString { \"HttpResponse(%(statusCode) %(statusText))\" }\n"
"}\n"
"\n"
"class Http {\n"
" static get(url) { request(url, \"GET\", null, {}) }\n"
" static get(url, headers) { request(url, \"GET\", null, headers) }\n"
"\n"
" static post(url, body) { request(url, \"POST\", body, {}) }\n"
" static post(url, body, headers) { request(url, \"POST\", body, headers) }\n"
"\n"
" static put(url, body) { request(url, \"PUT\", body, {}) }\n"
" static put(url, body, headers) { request(url, \"PUT\", body, headers) }\n"
"\n"
" static delete(url) { request(url, \"DELETE\", null, {}) }\n"
" static delete(url, headers) { request(url, \"DELETE\", null, headers) }\n"
"\n"
" static patch(url, body) { request(url, \"PATCH\", body, {}) }\n"
" static patch(url, body, headers) { request(url, \"PATCH\", body, headers) }\n"
"\n"
" static request(url, method, body, headers) {\n"
" var parsed = Url.parse(url)\n"
"\n"
" if (parsed.scheme != \"http\" && parsed.scheme != \"https\") {\n"
" Fiber.abort(\"Unsupported scheme: %(parsed.scheme)\")\n"
" }\n"
"\n"
" var isHttps = parsed.scheme == \"https\"\n"
"\n"
" var bodyStr = \"\"\n"
" if (body != null) {\n"
" if (body is String) {\n"
" bodyStr = body\n"
" } else if (body is Map || body is List) {\n"
" bodyStr = Json.stringify(body)\n"
" if (!hasHeader_(headers, \"Content-Type\")) {\n"
" headers[\"Content-Type\"] = \"application/json\"\n"
" }\n"
" } else {\n"
" bodyStr = body.toString\n"
" }\n"
" }\n"
"\n"
" var requestHeaders = {}\n"
" requestHeaders[\"Host\"] = parsed.host\n"
" if ((isHttps && parsed.port != 443) || (!isHttps && parsed.port != 80)) {\n"
" requestHeaders[\"Host\"] = \"%(parsed.host):%(parsed.port)\"\n"
" }\n"
" requestHeaders[\"Connection\"] = \"close\"\n"
" requestHeaders[\"User-Agent\"] = \"Wren-CLI/1.0\"\n"
"\n"
" if (bodyStr.count > 0) {\n"
" requestHeaders[\"Content-Length\"] = bodyStr.count.toString\n"
" }\n"
"\n"
" for (entry in headers) {\n"
" requestHeaders[entry.key] = entry.value\n"
" }\n"
"\n"
" var request = \"%(method) %(parsed.fullPath) HTTP/1.1\\r\\n\"\n"
" for (entry in requestHeaders) {\n"
" request = request + \"%(entry.key): %(entry.value)\\r\\n\"\n"
" }\n"
" request = request + \"\\r\\n\" + bodyStr\n"
"\n"
" var addresses = Dns.lookup(parsed.host, 4)\n"
" if (addresses.count == 0) {\n"
" Fiber.abort(\"Could not resolve host: %(parsed.host)\")\n"
" }\n"
"\n"
" var socket\n"
" if (isHttps) {\n"
" socket = TlsSocket.connect(addresses[0], parsed.port, parsed.host)\n"
" } else {\n"
" socket = Socket.connect(addresses[0], parsed.port)\n"
" }\n"
" socket.write(request)\n"
"\n"
" var response = \"\"\n"
" while (true) {\n"
" var chunk = socket.read()\n"
" if (chunk == null || chunk.count == 0) break\n"
" response = response + chunk\n"
" }\n"
" socket.close()\n"
"\n"
" return parseResponse_(response)\n"
" }\n"
"\n"
" static hasHeader_(headers, name) {\n"
" var lower = Url.toLower_(name)\n"
" for (entry in headers) {\n"
" if (Url.toLower_(entry.key) == lower) return true\n"
" }\n"
" return false\n"
" }\n"
"\n"
" static parseResponse_(response) {\n"
" var headerEnd = response.indexOf(\"\\r\\n\\r\\n\")\n"
" if (headerEnd < 0) {\n"
" Fiber.abort(\"Invalid HTTP response: no header/body separator\")\n"
" }\n"
"\n"
" var headerPart = response[0...headerEnd]\n"
" var body = response[(headerEnd + 4)..-1]\n"
"\n"
" var lines = splitLines_(headerPart)\n"
" if (lines.count == 0) {\n"
" Fiber.abort(\"Invalid HTTP response: empty headers\")\n"
" }\n"
"\n"
" var statusLine = lines[0]\n"
" var statusParts = statusLine.split(\" \")\n"
" if (statusParts.count < 2) {\n"
" Fiber.abort(\"Invalid HTTP status line\")\n"
" }\n"
"\n"
" var statusCode = Num.fromString(statusParts[1])\n"
" var statusText = statusParts.count > 2 ? statusParts[2..-1].join(\" \") : \"\"\n"
"\n"
" var headers = {}\n"
" for (i in 1...lines.count) {\n"
" var line = lines[i]\n"
" var colonPos = line.indexOf(\":\")\n"
" if (colonPos > 0) {\n"
" var name = line[0...colonPos].trim()\n"
" var value = line[(colonPos + 1)..-1].trim()\n"
" headers[name] = value\n"
" }\n"
" }\n"
"\n"
" var transferEncoding = null\n"
" for (entry in headers) {\n"
" if (Url.toLower_(entry.key) == \"transfer-encoding\") {\n"
" transferEncoding = Url.toLower_(entry.value)\n"
" break\n"
" }\n"
" }\n"
"\n"
" if (transferEncoding == \"chunked\") {\n"
" body = decodeChunked_(body)\n"
" }\n"
"\n"
" return HttpResponse.new_(statusCode, statusText, headers, body)\n"
" }\n"
"\n"
" static splitLines_(text) {\n"
" var lines = []\n"
" var start = 0\n"
" var i = 0\n"
" while (i < text.count) {\n"
" if (text[i] == \"\\r\" && i + 1 < text.count && text[i + 1] == \"\\n\") {\n"
" lines.add(text[start...i])\n"
" start = i + 2\n"
" i = i + 2\n"
" } else {\n"
" i = i + 1\n"
" }\n"
" }\n"
" if (start < text.count) {\n"
" lines.add(text[start..-1])\n"
" }\n"
" return lines\n"
" }\n"
"\n"
" static decodeChunked_(body) {\n"
" var result = \"\"\n"
" var pos = 0\n"
"\n"
" while (pos < body.count) {\n"
" var lineEnd = body.indexOf(\"\\r\\n\", pos)\n"
" if (lineEnd < 0) break\n"
"\n"
" var sizeStr = body[pos...lineEnd].trim()\n"
" var size = parseHex_(sizeStr)\n"
"\n"
" if (size == 0) break\n"
"\n"
" pos = lineEnd + 2\n"
" if (pos + size > body.count) break\n"
"\n"
" result = result + body[pos...(pos + size)]\n"
" pos = pos + size + 2\n"
" }\n"
"\n"
" return result\n"
" }\n"
"\n"
" static parseHex_(str) {\n"
" var result = 0\n"
" for (c in str) {\n"
" var cp = c.codePoints[0]\n"
" var digit = 0\n"
" if (cp >= 48 && cp <= 57) {\n"
" digit = cp - 48\n"
" } else if (cp >= 65 && cp <= 70) {\n"
" digit = cp - 55\n"
" } else if (cp >= 97 && cp <= 102) {\n"
" digit = cp - 87\n"
" } else {\n"
" break\n"
" }\n"
" result = result * 16 + digit\n"
" }\n"
" return result\n"
" }\n"
"}\n";

2649
src/module/jinja.wren vendored Normal file

File diff suppressed because it is too large Load Diff

2653
src/module/jinja.wren.inc Normal file

File diff suppressed because it is too large Load Diff

98
src/module/json.c Normal file
View File

@ -0,0 +1,98 @@
// retoor <retoor@molodetz.nl>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "json.h"
#include "wren.h"
#include "cJSON.h"
static void cjsonToWren(WrenVM* vm, int slot, cJSON* item) {
if (item == NULL) {
wrenSetSlotNull(vm, slot);
return;
}
switch (item->type & 0xFF) {
case cJSON_NULL:
wrenSetSlotNull(vm, slot);
break;
case cJSON_False:
wrenSetSlotBool(vm, slot, false);
break;
case cJSON_True:
wrenSetSlotBool(vm, slot, true);
break;
case cJSON_Number:
wrenSetSlotDouble(vm, slot, item->valuedouble);
break;
case cJSON_String:
wrenSetSlotString(vm, slot, item->valuestring ? item->valuestring : "");
break;
case cJSON_Array: {
wrenSetSlotNewList(vm, slot);
int elemSlot = slot + 1;
wrenEnsureSlots(vm, elemSlot + 1);
cJSON* child = item->child;
while (child != NULL) {
cjsonToWren(vm, elemSlot, child);
wrenInsertInList(vm, slot, -1, elemSlot);
child = child->next;
}
break;
}
case cJSON_Object: {
wrenSetSlotNewMap(vm, slot);
int keySlot = slot + 1;
int valueSlot = slot + 2;
wrenEnsureSlots(vm, valueSlot + 1);
cJSON* child = item->child;
while (child != NULL) {
wrenSetSlotString(vm, keySlot, child->string ? child->string : "");
cjsonToWren(vm, valueSlot, child);
wrenSetMapValue(vm, slot, keySlot, valueSlot);
child = child->next;
}
break;
}
default:
wrenSetSlotNull(vm, slot);
break;
}
}
void jsonParse(WrenVM* vm) {
const char* jsonString = wrenGetSlotString(vm, 1);
if (jsonString == NULL) {
wrenSetSlotString(vm, 0, "Expected string argument.");
wrenAbortFiber(vm, 0);
return;
}
cJSON* json = cJSON_Parse(jsonString);
if (json == NULL) {
const char* error = cJSON_GetErrorPtr();
if (error != NULL) {
char msg[256];
snprintf(msg, sizeof(msg), "JSON parse error near: %.50s", error);
wrenSetSlotString(vm, 0, msg);
} else {
wrenSetSlotString(vm, 0, "JSON parse error.");
}
wrenAbortFiber(vm, 0);
return;
}
wrenEnsureSlots(vm, 4);
cjsonToWren(vm, 0, json);
cJSON_Delete(json);
}

10
src/module/json.h Normal file
View File

@ -0,0 +1,10 @@
// retoor <retoor@molodetz.nl>
#ifndef wren_json_h
#define wren_json_h
#include "wren.h"
void jsonParse(WrenVM* vm);
#endif

99
src/module/json.wren vendored Normal file
View File

@ -0,0 +1,99 @@
// retoor <retoor@molodetz.nl>
class Json {
foreign static parse(string)
static stringify(value) { stringify_(value, "") }
static stringify(value, indent) {
var indentStr = ""
if (indent is Num && indent > 0) {
for (i in 0...indent) {
indentStr = indentStr + " "
}
} else if (indent is String) {
indentStr = indent
}
return stringify_(value, indentStr)
}
static stringify_(value, indent) {
return stringifyValue_(value, indent, "")
}
static stringifyValue_(value, indent, currentIndent) {
if (value == null) return "null"
if (value is Bool) return value ? "true" : "false"
if (value is Num) {
if (value.isInfinity || value.isNan) return "null"
if (value == value.truncate) return value.truncate.toString
return value.toString
}
if (value is String) return escapeString_(value)
if (value is List) return stringifyList_(value, indent, currentIndent)
if (value is Map) return stringifyMap_(value, indent, currentIndent)
return "null"
}
static escapeString_(s) {
var result = "\""
for (c in s) {
if (c == "\"") {
result = result + "\\\""
} else if (c == "\\") {
result = result + "\\\\"
} else if (c == "\b") {
result = result + "\\b"
} else if (c == "\f") {
result = result + "\\f"
} else if (c == "\n") {
result = result + "\\n"
} else if (c == "\r") {
result = result + "\\r"
} else if (c == "\t") {
result = result + "\\t"
} else {
result = result + c
}
}
return result + "\""
}
static stringifyList_(list, indent, currentIndent) {
if (list.count == 0) return "[]"
var nextIndent = currentIndent + indent
var parts = []
for (item in list) {
parts.add(stringifyValue_(item, indent, nextIndent))
}
if (indent == "") {
return "[" + parts.join(",") + "]"
}
return "[\n" + nextIndent + parts.join(",\n" + nextIndent) + "\n" + currentIndent + "]"
}
static stringifyMap_(map, indent, currentIndent) {
if (map.count == 0) return "{}"
var nextIndent = currentIndent + indent
var parts = []
for (key in map.keys) {
var keyStr = escapeString_(key.toString)
var valueStr = stringifyValue_(map[key], indent, nextIndent)
if (indent == "") {
parts.add(keyStr + ":" + valueStr)
} else {
parts.add(keyStr + ": " + valueStr)
}
}
if (indent == "") {
return "{" + parts.join(",") + "}"
}
return "{\n" + nextIndent + parts.join(",\n" + nextIndent) + "\n" + currentIndent + "}"
}
}

103
src/module/json.wren.inc Normal file
View File

@ -0,0 +1,103 @@
// Please do not edit this file. It has been generated automatically
// from `src/module/json.wren` using `util/wren_to_c_string.py`
static const char* jsonModuleSource =
"// retoor <retoor@molodetz.nl>\n"
"\n"
"class Json {\n"
" foreign static parse(string)\n"
"\n"
" static stringify(value) { stringify_(value, \"\") }\n"
"\n"
" static stringify(value, indent) {\n"
" var indentStr = \"\"\n"
" if (indent is Num && indent > 0) {\n"
" for (i in 0...indent) {\n"
" indentStr = indentStr + \" \"\n"
" }\n"
" } else if (indent is String) {\n"
" indentStr = indent\n"
" }\n"
" return stringify_(value, indentStr)\n"
" }\n"
"\n"
" static stringify_(value, indent) {\n"
" return stringifyValue_(value, indent, \"\")\n"
" }\n"
"\n"
" static stringifyValue_(value, indent, currentIndent) {\n"
" if (value == null) return \"null\"\n"
" if (value is Bool) return value ? \"true\" : \"false\"\n"
" if (value is Num) {\n"
" if (value.isInfinity || value.isNan) return \"null\"\n"
" if (value == value.truncate) return value.truncate.toString\n"
" return value.toString\n"
" }\n"
" if (value is String) return escapeString_(value)\n"
" if (value is List) return stringifyList_(value, indent, currentIndent)\n"
" if (value is Map) return stringifyMap_(value, indent, currentIndent)\n"
" return \"null\"\n"
" }\n"
"\n"
" static escapeString_(s) {\n"
" var result = \"\\\"\"\n"
" for (c in s) {\n"
" if (c == \"\\\"\") {\n"
" result = result + \"\\\\\\\"\"\n"
" } else if (c == \"\\\\\") {\n"
" result = result + \"\\\\\\\\\"\n"
" } else if (c == \"\\b\") {\n"
" result = result + \"\\\\b\"\n"
" } else if (c == \"\\f\") {\n"
" result = result + \"\\\\f\"\n"
" } else if (c == \"\\n\") {\n"
" result = result + \"\\\\n\"\n"
" } else if (c == \"\\r\") {\n"
" result = result + \"\\\\r\"\n"
" } else if (c == \"\\t\") {\n"
" result = result + \"\\\\t\"\n"
" } else {\n"
" result = result + c\n"
" }\n"
" }\n"
" return result + \"\\\"\"\n"
" }\n"
"\n"
" static stringifyList_(list, indent, currentIndent) {\n"
" if (list.count == 0) return \"[]\"\n"
"\n"
" var nextIndent = currentIndent + indent\n"
" var parts = []\n"
" for (item in list) {\n"
" parts.add(stringifyValue_(item, indent, nextIndent))\n"
" }\n"
"\n"
" if (indent == \"\") {\n"
" return \"[\" + parts.join(\",\") + \"]\"\n"
" }\n"
"\n"
" return \"[\\n\" + nextIndent + parts.join(\",\\n\" + nextIndent) + \"\\n\" + currentIndent + \"]\"\n"
" }\n"
"\n"
" static stringifyMap_(map, indent, currentIndent) {\n"
" if (map.count == 0) return \"{}\"\n"
"\n"
" var nextIndent = currentIndent + indent\n"
" var parts = []\n"
" for (key in map.keys) {\n"
" var keyStr = escapeString_(key.toString)\n"
" var valueStr = stringifyValue_(map[key], indent, nextIndent)\n"
" if (indent == \"\") {\n"
" parts.add(keyStr + \":\" + valueStr)\n"
" } else {\n"
" parts.add(keyStr + \": \" + valueStr)\n"
" }\n"
" }\n"
"\n"
" if (indent == \"\") {\n"
" return \"{\" + parts.join(\",\") + \"}\"\n"
" }\n"
"\n"
" return \"{\\n\" + nextIndent + parts.join(\",\\n\" + nextIndent) + \"\\n\" + currentIndent + \"}\"\n"
" }\n"
"}\n";

46
src/module/math.wren vendored Normal file
View File

@ -0,0 +1,46 @@
// retoor <retoor@molodetz.nl>
class Math {
foreign static sin(x)
foreign static cos(x)
foreign static tan(x)
foreign static asin(x)
foreign static acos(x)
foreign static atan(x)
foreign static atan2(y, x)
foreign static sinh(x)
foreign static cosh(x)
foreign static tanh(x)
foreign static log(x)
foreign static log10(x)
foreign static log2(x)
foreign static exp(x)
foreign static pow(base, exp)
foreign static sqrt(x)
foreign static cbrt(x)
foreign static ceil(x)
foreign static floor(x)
foreign static round(x)
foreign static abs(x)
static min(a, b) { a < b ? a : b }
static max(a, b) { a > b ? a : b }
static clamp(x, min, max) { x < min ? min : (x > max ? max : x) }
static pi { 3.14159265358979323846 }
static e { 2.71828182845904523536 }
static tau { 6.28318530717958647692 }
static infinity { 1 / 0 }
static nan { 0 / 0 }
static degrees(radians) { radians * 180 / pi }
static radians(degrees) { degrees * pi / 180 }
static sign(x) {
if (x > 0) return 1
if (x < 0) return -1
return 0
}
static lerp(a, b, t) { a + (b - a) * t }
}

50
src/module/math.wren.inc Normal file
View File

@ -0,0 +1,50 @@
// Please do not edit this file. It has been generated automatically
// from `src/module/math.wren` using `util/wren_to_c_string.py`
static const char* mathModuleSource =
"// retoor <retoor@molodetz.nl>\n"
"\n"
"class Math {\n"
" foreign static sin(x)\n"
" foreign static cos(x)\n"
" foreign static tan(x)\n"
" foreign static asin(x)\n"
" foreign static acos(x)\n"
" foreign static atan(x)\n"
" foreign static atan2(y, x)\n"
" foreign static sinh(x)\n"
" foreign static cosh(x)\n"
" foreign static tanh(x)\n"
" foreign static log(x)\n"
" foreign static log10(x)\n"
" foreign static log2(x)\n"
" foreign static exp(x)\n"
" foreign static pow(base, exp)\n"
" foreign static sqrt(x)\n"
" foreign static cbrt(x)\n"
" foreign static ceil(x)\n"
" foreign static floor(x)\n"
" foreign static round(x)\n"
" foreign static abs(x)\n"
"\n"
" static min(a, b) { a < b ? a : b }\n"
" static max(a, b) { a > b ? a : b }\n"
" static clamp(x, min, max) { x < min ? min : (x > max ? max : x) }\n"
"\n"
" static pi { 3.14159265358979323846 }\n"
" static e { 2.71828182845904523536 }\n"
" static tau { 6.28318530717958647692 }\n"
" static infinity { 1 / 0 }\n"
" static nan { 0 / 0 }\n"
"\n"
" static degrees(radians) { radians * 180 / pi }\n"
" static radians(degrees) { degrees * pi / 180 }\n"
"\n"
" static sign(x) {\n"
" if (x > 0) return 1\n"
" if (x < 0) return -1\n"
" return 0\n"
" }\n"
"\n"
" static lerp(a, b, t) { a + (b - a) * t }\n"
"}\n";

112
src/module/math_module.c Normal file
View File

@ -0,0 +1,112 @@
// retoor <retoor@molodetz.nl>
#include <math.h>
#include "math_module.h"
#include "wren.h"
void mathSin(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, sin(x));
}
void mathCos(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, cos(x));
}
void mathTan(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, tan(x));
}
void mathAsin(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, asin(x));
}
void mathAcos(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, acos(x));
}
void mathAtan(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, atan(x));
}
void mathAtan2(WrenVM* vm) {
double y = wrenGetSlotDouble(vm, 1);
double x = wrenGetSlotDouble(vm, 2);
wrenSetSlotDouble(vm, 0, atan2(y, x));
}
void mathSinh(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, sinh(x));
}
void mathCosh(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, cosh(x));
}
void mathTanh(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, tanh(x));
}
void mathLog(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, log(x));
}
void mathLog10(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, log10(x));
}
void mathLog2(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, log2(x));
}
void mathExp(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, exp(x));
}
void mathPow(WrenVM* vm) {
double base = wrenGetSlotDouble(vm, 1);
double exponent = wrenGetSlotDouble(vm, 2);
wrenSetSlotDouble(vm, 0, pow(base, exponent));
}
void mathSqrt(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, sqrt(x));
}
void mathCbrt(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, cbrt(x));
}
void mathCeil(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, ceil(x));
}
void mathFloor(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, floor(x));
}
void mathRound(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, round(x));
}
void mathAbs(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, fabs(x));
}

30
src/module/math_module.h Normal file
View File

@ -0,0 +1,30 @@
// retoor <retoor@molodetz.nl>
#ifndef wren_math_module_h
#define wren_math_module_h
#include "wren.h"
void mathSin(WrenVM* vm);
void mathCos(WrenVM* vm);
void mathTan(WrenVM* vm);
void mathAsin(WrenVM* vm);
void mathAcos(WrenVM* vm);
void mathAtan(WrenVM* vm);
void mathAtan2(WrenVM* vm);
void mathSinh(WrenVM* vm);
void mathCosh(WrenVM* vm);
void mathTanh(WrenVM* vm);
void mathLog(WrenVM* vm);
void mathLog10(WrenVM* vm);
void mathLog2(WrenVM* vm);
void mathExp(WrenVM* vm);
void mathPow(WrenVM* vm);
void mathSqrt(WrenVM* vm);
void mathCbrt(WrenVM* vm);
void mathCeil(WrenVM* vm);
void mathFloor(WrenVM* vm);
void mathRound(WrenVM* vm);
void mathAbs(WrenVM* vm);
#endif

View File

@ -159,9 +159,8 @@ void socketWrite(WrenVM* vm)
SocketData* data = (SocketData*)wrenGetSlotForeign(vm, 0);
const char* text = wrenGetSlotString(vm, 1);
size_t length = strlen(text);
int length;
const char* text = wrenGetSlotBytes(vm, 1, &length);
WrenHandle* fiber = wrenGetSlotHandle(vm, 2);

447
src/module/regex.c Normal file
View File

@ -0,0 +1,447 @@
// retoor <retoor@molodetz.nl>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "regex.h"
#include "wren.h"
typedef struct {
char* pattern;
char* flags;
bool ignoreCase;
bool global;
bool multiline;
} RegexData;
typedef struct {
int type;
char literal;
const char* classStart;
const char* classEnd;
} PatternElement;
#define ELEM_LITERAL 0
#define ELEM_DOT 1
#define ELEM_CLASS 2
#define ELEM_ESCAPE 3
static bool matchElement(PatternElement* elem, char c, bool ignoreCase) {
if (c == '\0') return false;
if (elem->type == ELEM_DOT) {
return c != '\n';
}
if (elem->type == ELEM_LITERAL) {
char p = elem->literal;
if (ignoreCase) {
if (p >= 'A' && p <= 'Z') p += 32;
if (c >= 'A' && c <= 'Z') c += 32;
}
return p == c;
}
if (elem->type == ELEM_ESCAPE) {
switch (elem->literal) {
case 'd': return (c >= '0' && c <= '9');
case 'D': return !(c >= '0' && c <= '9');
case 'w': return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') || c == '_';
case 'W': return !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') || c == '_');
case 's': return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f';
case 'S': return !(c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f');
case 'n': return c == '\n';
case 't': return c == '\t';
case 'r': return c == '\r';
default: {
char p = elem->literal;
if (ignoreCase) {
if (p >= 'A' && p <= 'Z') p += 32;
if (c >= 'A' && c <= 'Z') c += 32;
}
return p == c;
}
}
}
if (elem->type == ELEM_CLASS) {
const char* p = elem->classStart;
bool negate = false;
bool matched = false;
if (*p == '^') {
negate = true;
p++;
}
while (p < elem->classEnd) {
if (p[1] == '-' && p + 2 < elem->classEnd) {
char lo = *p, hi = p[2];
char tc = c;
if (ignoreCase) {
if (lo >= 'A' && lo <= 'Z') lo += 32;
if (hi >= 'A' && hi <= 'Z') hi += 32;
if (tc >= 'A' && tc <= 'Z') tc += 32;
}
if (tc >= lo && tc <= hi) matched = true;
p += 3;
} else {
char pc = *p;
char tc = c;
if (ignoreCase) {
if (pc >= 'A' && pc <= 'Z') pc += 32;
if (tc >= 'A' && tc <= 'Z') tc += 32;
}
if (pc == tc) matched = true;
p++;
}
}
return negate ? !matched : matched;
}
return false;
}
static const char* parseElement(const char* pattern, PatternElement* elem) {
if (*pattern == '.') {
elem->type = ELEM_DOT;
return pattern + 1;
}
if (*pattern == '\\' && *(pattern + 1)) {
elem->type = ELEM_ESCAPE;
elem->literal = *(pattern + 1);
return pattern + 2;
}
if (*pattern == '[') {
elem->type = ELEM_CLASS;
elem->classStart = pattern + 1;
const char* p = pattern + 1;
if (*p == '^') p++;
if (*p == ']') p++;
while (*p && *p != ']') p++;
elem->classEnd = p;
return *p ? p + 1 : p;
}
elem->type = ELEM_LITERAL;
elem->literal = *pattern;
return pattern + 1;
}
static bool matchHere(const char* pattern, const char* text, bool ignoreCase, const char** matchEnd);
static bool matchStar(PatternElement* elem, const char* pattern, const char* text,
bool ignoreCase, const char** matchEnd) {
const char* t = text;
while (*t && matchElement(elem, *t, ignoreCase)) t++;
do {
if (matchHere(pattern, t, ignoreCase, matchEnd)) return true;
} while (t-- > text);
return false;
}
static bool matchPlus(PatternElement* elem, const char* pattern, const char* text,
bool ignoreCase, const char** matchEnd) {
if (!*text || !matchElement(elem, *text, ignoreCase)) return false;
return matchStar(elem, pattern, text + 1, ignoreCase, matchEnd);
}
static bool matchQuestion(PatternElement* elem, const char* pattern, const char* text,
bool ignoreCase, const char** matchEnd) {
if (matchHere(pattern, text, ignoreCase, matchEnd)) return true;
if (*text && matchElement(elem, *text, ignoreCase)) {
return matchHere(pattern, text + 1, ignoreCase, matchEnd);
}
return false;
}
static bool matchHere(const char* pattern, const char* text, bool ignoreCase, const char** matchEnd) {
if (*pattern == '\0') {
*matchEnd = text;
return true;
}
if (*pattern == '$' && *(pattern + 1) == '\0') {
*matchEnd = text;
return *text == '\0' || *text == '\n';
}
PatternElement elem;
const char* nextPattern = parseElement(pattern, &elem);
if (*nextPattern == '*') {
return matchStar(&elem, nextPattern + 1, text, ignoreCase, matchEnd);
}
if (*nextPattern == '+') {
return matchPlus(&elem, nextPattern + 1, text, ignoreCase, matchEnd);
}
if (*nextPattern == '?') {
return matchQuestion(&elem, nextPattern + 1, text, ignoreCase, matchEnd);
}
if (matchElement(&elem, *text, ignoreCase)) {
return matchHere(nextPattern, text + 1, ignoreCase, matchEnd);
}
return false;
}
static bool matchPattern(const char* pattern, const char* text, bool ignoreCase,
const char** matchStart, const char** matchEnd) {
if (*pattern == '^') {
*matchStart = text;
return matchHere(pattern + 1, text, ignoreCase, matchEnd);
}
do {
*matchStart = text;
if (matchHere(pattern, text, ignoreCase, matchEnd)) {
return true;
}
} while (*text++);
return false;
}
void regexAllocate(WrenVM* vm) {
RegexData* data = (RegexData*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(RegexData));
memset(data, 0, sizeof(RegexData));
const char* pattern = wrenGetSlotString(vm, 1);
data->pattern = strdup(pattern);
const char* flags = "";
if (wrenGetSlotCount(vm) > 2 && wrenGetSlotType(vm, 2) == WREN_TYPE_STRING) {
flags = wrenGetSlotString(vm, 2);
}
data->flags = strdup(flags);
for (const char* f = flags; *f; f++) {
switch (*f) {
case 'i': data->ignoreCase = true; break;
case 'g': data->global = true; break;
case 'm': data->multiline = true; break;
}
}
}
void regexFinalize(void* ptr) {
RegexData* data = (RegexData*)ptr;
if (data->pattern) free(data->pattern);
if (data->flags) free(data->flags);
}
void regexTest(WrenVM* vm) {
RegexData* data = (RegexData*)wrenGetSlotForeign(vm, 0);
const char* text = wrenGetSlotString(vm, 1);
const char* matchStart;
const char* matchEnd;
bool result = matchPattern(data->pattern, text, data->ignoreCase, &matchStart, &matchEnd);
wrenSetSlotBool(vm, 0, result);
}
void regexMatch(WrenVM* vm) {
RegexData* data = (RegexData*)wrenGetSlotForeign(vm, 0);
const char* text = wrenGetSlotString(vm, 1);
const char* matchStart;
const char* matchEnd;
if (!matchPattern(data->pattern, text, data->ignoreCase, &matchStart, &matchEnd)) {
wrenSetSlotNull(vm, 0);
return;
}
wrenEnsureSlots(vm, 7);
wrenGetVariable(vm, "regex", "Match", 1);
size_t matchLen = matchEnd - matchStart;
char* matchText = (char*)malloc(matchLen + 1);
memcpy(matchText, matchStart, matchLen);
matchText[matchLen] = '\0';
wrenSetSlotString(vm, 2, matchText);
wrenSetSlotDouble(vm, 3, (double)(matchStart - text));
wrenSetSlotDouble(vm, 4, (double)(matchEnd - text));
wrenSetSlotNewList(vm, 5);
wrenSetSlotString(vm, 6, matchText);
wrenInsertInList(vm, 5, -1, 6);
free(matchText);
WrenHandle* newMethod = wrenMakeCallHandle(vm, "new_(_,_,_,_)");
wrenCall(vm, newMethod);
wrenReleaseHandle(vm, newMethod);
}
void regexMatchAll(WrenVM* vm) {
RegexData* data = (RegexData*)wrenGetSlotForeign(vm, 0);
const char* text = wrenGetSlotString(vm, 1);
const char* cursor = text;
const char* matchStart;
const char* matchEnd;
wrenEnsureSlots(vm, 8);
wrenSetSlotNewList(vm, 0);
while (matchPattern(data->pattern, cursor, data->ignoreCase, &matchStart, &matchEnd)) {
wrenGetVariable(vm, "regex", "Match", 1);
size_t matchLen = matchEnd - matchStart;
char* matchText = (char*)malloc(matchLen + 1);
memcpy(matchText, matchStart, matchLen);
matchText[matchLen] = '\0';
wrenSetSlotString(vm, 2, matchText);
wrenSetSlotDouble(vm, 3, (double)(matchStart - text));
wrenSetSlotDouble(vm, 4, (double)(matchEnd - text));
wrenSetSlotNewList(vm, 5);
wrenSetSlotString(vm, 6, matchText);
wrenInsertInList(vm, 5, -1, 6);
free(matchText);
WrenHandle* newMethod = wrenMakeCallHandle(vm, "new_(_,_,_,_)");
wrenCall(vm, newMethod);
wrenReleaseHandle(vm, newMethod);
wrenInsertInList(vm, 0, -1, 1);
if (matchEnd == cursor) cursor++;
else cursor = matchEnd;
if (*cursor == '\0') break;
if (!data->global) break;
}
}
void regexReplace(WrenVM* vm) {
RegexData* data = (RegexData*)wrenGetSlotForeign(vm, 0);
const char* text = wrenGetSlotString(vm, 1);
const char* replacement = wrenGetSlotString(vm, 2);
const char* matchStart;
const char* matchEnd;
if (!matchPattern(data->pattern, text, data->ignoreCase, &matchStart, &matchEnd)) {
wrenSetSlotString(vm, 0, text);
return;
}
size_t textLen = strlen(text);
size_t replLen = strlen(replacement);
size_t matchLen = matchEnd - matchStart;
size_t resultLen = textLen - matchLen + replLen;
char* result = (char*)malloc(resultLen + 1);
size_t prefixLen = matchStart - text;
memcpy(result, text, prefixLen);
memcpy(result + prefixLen, replacement, replLen);
memcpy(result + prefixLen + replLen, matchEnd, textLen - prefixLen - matchLen);
result[resultLen] = '\0';
wrenSetSlotString(vm, 0, result);
free(result);
}
void regexReplaceAll(WrenVM* vm) {
RegexData* data = (RegexData*)wrenGetSlotForeign(vm, 0);
const char* text = wrenGetSlotString(vm, 1);
const char* replacement = wrenGetSlotString(vm, 2);
size_t textLen = strlen(text);
size_t replLen = strlen(replacement);
size_t capacity = textLen * 2 + 1;
char* result = (char*)malloc(capacity);
size_t resultLen = 0;
const char* cursor = text;
const char* matchStart;
const char* matchEnd;
while (matchPattern(data->pattern, cursor, data->ignoreCase, &matchStart, &matchEnd)) {
size_t prefixLen = matchStart - cursor;
while (resultLen + prefixLen + replLen >= capacity) {
capacity *= 2;
result = (char*)realloc(result, capacity);
}
memcpy(result + resultLen, cursor, prefixLen);
resultLen += prefixLen;
memcpy(result + resultLen, replacement, replLen);
resultLen += replLen;
if (matchEnd == cursor) {
if (*cursor) {
result[resultLen++] = *cursor;
}
cursor++;
} else {
cursor = matchEnd;
}
if (*cursor == '\0') break;
}
size_t remaining = strlen(cursor);
while (resultLen + remaining >= capacity) {
capacity *= 2;
result = (char*)realloc(result, capacity);
}
memcpy(result + resultLen, cursor, remaining);
resultLen += remaining;
result[resultLen] = '\0';
wrenSetSlotString(vm, 0, result);
free(result);
}
void regexSplit(WrenVM* vm) {
RegexData* data = (RegexData*)wrenGetSlotForeign(vm, 0);
const char* text = wrenGetSlotString(vm, 1);
wrenEnsureSlots(vm, 3);
wrenSetSlotNewList(vm, 0);
const char* cursor = text;
const char* matchStart;
const char* matchEnd;
while (matchPattern(data->pattern, cursor, data->ignoreCase, &matchStart, &matchEnd)) {
size_t partLen = matchStart - cursor;
char* part = (char*)malloc(partLen + 1);
memcpy(part, cursor, partLen);
part[partLen] = '\0';
wrenSetSlotString(vm, 2, part);
wrenInsertInList(vm, 0, -1, 2);
free(part);
if (matchEnd == cursor) cursor++;
else cursor = matchEnd;
if (*cursor == '\0') break;
}
wrenSetSlotString(vm, 2, cursor);
wrenInsertInList(vm, 0, -1, 2);
}
void regexPattern(WrenVM* vm) {
RegexData* data = (RegexData*)wrenGetSlotForeign(vm, 0);
wrenSetSlotString(vm, 0, data->pattern);
}
void regexFlags(WrenVM* vm) {
RegexData* data = (RegexData*)wrenGetSlotForeign(vm, 0);
wrenSetSlotString(vm, 0, data->flags);
}

19
src/module/regex.h Normal file
View File

@ -0,0 +1,19 @@
// retoor <retoor@molodetz.nl>
#ifndef wren_regex_h
#define wren_regex_h
#include "wren.h"
void regexAllocate(WrenVM* vm);
void regexFinalize(void* data);
void regexTest(WrenVM* vm);
void regexMatch(WrenVM* vm);
void regexMatchAll(WrenVM* vm);
void regexReplace(WrenVM* vm);
void regexReplaceAll(WrenVM* vm);
void regexSplit(WrenVM* vm);
void regexPattern(WrenVM* vm);
void regexFlags(WrenVM* vm);
#endif

36
src/module/regex.wren vendored Normal file
View File

@ -0,0 +1,36 @@
// retoor <retoor@molodetz.nl>
foreign class Regex {
construct new(pattern) {}
construct new(pattern, flags) {}
foreign test(string)
foreign match(string)
foreign matchAll(string)
foreign replace(string, replacement)
foreign replaceAll(string, replacement)
foreign split(string)
foreign pattern
foreign flags
}
class Match {
construct new_(text, start, end, groups) {
_text = text
_start = start
_end = end
_groups = groups
}
text { _text }
start { _start }
end { _end }
groups { _groups }
group(index) {
if (index < 0 || index >= _groups.count) return null
return _groups[index]
}
toString { _text }
}

40
src/module/regex.wren.inc Normal file
View File

@ -0,0 +1,40 @@
// Please do not edit this file. It has been generated automatically
// from `src/module/regex.wren` using `util/wren_to_c_string.py`
static const char* regexModuleSource =
"// retoor <retoor@molodetz.nl>\n"
"\n"
"foreign class Regex {\n"
" construct new(pattern) {}\n"
" construct new(pattern, flags) {}\n"
"\n"
" foreign test(string)\n"
" foreign match(string)\n"
" foreign matchAll(string)\n"
" foreign replace(string, replacement)\n"
" foreign replaceAll(string, replacement)\n"
" foreign split(string)\n"
" foreign pattern\n"
" foreign flags\n"
"}\n"
"\n"
"class Match {\n"
" construct new_(text, start, end, groups) {\n"
" _text = text\n"
" _start = start\n"
" _end = end\n"
" _groups = groups\n"
" }\n"
"\n"
" text { _text }\n"
" start { _start }\n"
" end { _end }\n"
" groups { _groups }\n"
"\n"
" group(index) {\n"
" if (index < 0 || index >= _groups.count) return null\n"
" return _groups[index]\n"
" }\n"
"\n"
" toString { _text }\n"
"}\n";

35
src/module/signal.wren vendored Normal file
View File

@ -0,0 +1,35 @@
// retoor <retoor@molodetz.nl>
class Signal {
static SIGHUP { 1 }
static SIGINT { 2 }
static SIGQUIT { 3 }
static SIGTERM { 15 }
static SIGUSR1 { 10 }
static SIGUSR2 { 12 }
foreign static trap_(signum, fiber)
foreign static ignore_(signum)
foreign static reset_(signum)
static trap(signum, fn) {
if (!(signum is Num)) Fiber.abort("Signal number must be a number.")
var fiber = Fiber.new {
while (true) {
fn.call()
Fiber.yield()
}
}
trap_(signum, fiber)
}
static ignore(signum) {
if (!(signum is Num)) Fiber.abort("Signal number must be a number.")
ignore_(signum)
}
static reset(signum) {
if (!(signum is Num)) Fiber.abort("Signal number must be a number.")
reset_(signum)
}
}

View File

@ -0,0 +1,39 @@
// Please do not edit this file. It has been generated automatically
// from `src/module/signal.wren` using `util/wren_to_c_string.py`
static const char* signalModuleSource =
"// retoor <retoor@molodetz.nl>\n"
"\n"
"class Signal {\n"
" static SIGHUP { 1 }\n"
" static SIGINT { 2 }\n"
" static SIGQUIT { 3 }\n"
" static SIGTERM { 15 }\n"
" static SIGUSR1 { 10 }\n"
" static SIGUSR2 { 12 }\n"
"\n"
" foreign static trap_(signum, fiber)\n"
" foreign static ignore_(signum)\n"
" foreign static reset_(signum)\n"
"\n"
" static trap(signum, fn) {\n"
" if (!(signum is Num)) Fiber.abort(\"Signal number must be a number.\")\n"
" var fiber = Fiber.new {\n"
" while (true) {\n"
" fn.call()\n"
" Fiber.yield()\n"
" }\n"
" }\n"
" trap_(signum, fiber)\n"
" }\n"
"\n"
" static ignore(signum) {\n"
" if (!(signum is Num)) Fiber.abort(\"Signal number must be a number.\")\n"
" ignore_(signum)\n"
" }\n"
"\n"
" static reset(signum) {\n"
" if (!(signum is Num)) Fiber.abort(\"Signal number must be a number.\")\n"
" reset_(signum)\n"
" }\n"
"}\n";

128
src/module/signal_module.c Normal file
View File

@ -0,0 +1,128 @@
// retoor <retoor@molodetz.nl>
#include <stdlib.h>
#include <string.h>
#include "signal_module.h"
#include "scheduler.h"
#include "wren.h"
#include "vm.h"
#include "uv.h"
#define MAX_SIGNALS 32
static uv_signal_t* signalHandles[MAX_SIGNALS] = {NULL};
static WrenHandle* signalFibers[MAX_SIGNALS] = {NULL};
static void signalCallback(uv_signal_t* handle, int signum) {
if (signum < 0 || signum >= MAX_SIGNALS) return;
WrenHandle* fiber = signalFibers[signum];
if (fiber == NULL) return;
schedulerResume(fiber, false);
}
void signalTrap(WrenVM* vm) {
int signum = (int)wrenGetSlotDouble(vm, 1);
WrenHandle* fiber = wrenGetSlotHandle(vm, 2);
if (signum < 0 || signum >= MAX_SIGNALS) {
wrenSetSlotString(vm, 0, "Invalid signal number.");
wrenAbortFiber(vm, 0);
return;
}
if (signalHandles[signum] != NULL) {
uv_signal_stop(signalHandles[signum]);
uv_close((uv_handle_t*)signalHandles[signum], NULL);
free(signalHandles[signum]);
}
if (signalFibers[signum] != NULL) {
wrenReleaseHandle(vm, signalFibers[signum]);
}
uv_signal_t* handle = (uv_signal_t*)malloc(sizeof(uv_signal_t));
if (handle == NULL) {
wrenSetSlotString(vm, 0, "Memory allocation failed.");
wrenAbortFiber(vm, 0);
return;
}
uv_signal_init(getLoop(), handle);
signalHandles[signum] = handle;
signalFibers[signum] = fiber;
int result = uv_signal_start(handle, signalCallback, signum);
if (result != 0) {
free(handle);
signalHandles[signum] = NULL;
signalFibers[signum] = NULL;
wrenReleaseHandle(vm, fiber);
wrenSetSlotString(vm, 0, uv_strerror(result));
wrenAbortFiber(vm, 0);
return;
}
}
void signalIgnore(WrenVM* vm) {
int signum = (int)wrenGetSlotDouble(vm, 1);
if (signum < 0 || signum >= MAX_SIGNALS) {
wrenSetSlotString(vm, 0, "Invalid signal number.");
wrenAbortFiber(vm, 0);
return;
}
if (signalHandles[signum] != NULL) {
uv_signal_stop(signalHandles[signum]);
uv_close((uv_handle_t*)signalHandles[signum], NULL);
free(signalHandles[signum]);
signalHandles[signum] = NULL;
}
if (signalFibers[signum] != NULL) {
wrenReleaseHandle(vm, signalFibers[signum]);
signalFibers[signum] = NULL;
}
uv_signal_t* handle = (uv_signal_t*)malloc(sizeof(uv_signal_t));
if (handle == NULL) {
wrenSetSlotString(vm, 0, "Memory allocation failed.");
wrenAbortFiber(vm, 0);
return;
}
uv_signal_init(getLoop(), handle);
signalHandles[signum] = handle;
int result = uv_signal_start(handle, NULL, signum);
if (result != 0) {
free(handle);
signalHandles[signum] = NULL;
wrenSetSlotString(vm, 0, uv_strerror(result));
wrenAbortFiber(vm, 0);
}
}
void signalReset(WrenVM* vm) {
int signum = (int)wrenGetSlotDouble(vm, 1);
if (signum < 0 || signum >= MAX_SIGNALS) {
wrenSetSlotString(vm, 0, "Invalid signal number.");
wrenAbortFiber(vm, 0);
return;
}
if (signalHandles[signum] != NULL) {
uv_signal_stop(signalHandles[signum]);
uv_close((uv_handle_t*)signalHandles[signum], NULL);
free(signalHandles[signum]);
signalHandles[signum] = NULL;
}
if (signalFibers[signum] != NULL) {
wrenReleaseHandle(vm, signalFibers[signum]);
signalFibers[signum] = NULL;
}
}

View File

@ -0,0 +1,12 @@
// retoor <retoor@molodetz.nl>
#ifndef wren_signal_module_h
#define wren_signal_module_h
#include "wren.h"
void signalTrap(WrenVM* vm);
void signalIgnore(WrenVM* vm);
void signalReset(WrenVM* vm);
#endif

14
src/module/sqlite.wren vendored Normal file
View File

@ -0,0 +1,14 @@
// retoor <retoor@molodetz.nl>
foreign class Database {
construct open(path) {}
construct memory() {}
foreign execute(sql)
foreign execute(sql, params)
foreign query(sql)
foreign query(sql, params)
foreign close()
foreign lastInsertId
foreign changes
}

View File

@ -0,0 +1,18 @@
// Please do not edit this file. It has been generated automatically
// from `src/module/sqlite.wren` using `util/wren_to_c_string.py`
static const char* sqliteModuleSource =
"// retoor <retoor@molodetz.nl>\n"
"\n"
"foreign class Database {\n"
" construct open(path) {}\n"
" construct memory() {}\n"
"\n"
" foreign execute(sql)\n"
" foreign execute(sql, params)\n"
" foreign query(sql)\n"
" foreign query(sql, params)\n"
" foreign close()\n"
" foreign lastInsertId\n"
" foreign changes\n"
"}\n";

219
src/module/sqlite_module.c Normal file
View File

@ -0,0 +1,219 @@
// retoor <retoor@molodetz.nl>
#include <stdlib.h>
#include <string.h>
#include "sqlite_module.h"
#include "wren.h"
#include "sqlite3.h"
typedef struct {
sqlite3* db;
} SqliteData;
void sqliteAllocate(WrenVM* vm) {
SqliteData* data = (SqliteData*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(SqliteData));
data->db = NULL;
const char* path = ":memory:";
if (wrenGetSlotCount(vm) > 1 && wrenGetSlotType(vm, 1) == WREN_TYPE_STRING) {
path = wrenGetSlotString(vm, 1);
}
int result = sqlite3_open(path, &data->db);
if (result != SQLITE_OK) {
const char* err = sqlite3_errmsg(data->db);
sqlite3_close(data->db);
data->db = NULL;
wrenSetSlotString(vm, 0, err);
wrenAbortFiber(vm, 0);
}
}
void sqliteFinalize(void* ptr) {
SqliteData* data = (SqliteData*)ptr;
if (data->db != NULL) {
sqlite3_close(data->db);
data->db = NULL;
}
}
static void bindParams(WrenVM* vm, sqlite3_stmt* stmt, int paramsSlot) {
int count = wrenGetListCount(vm, paramsSlot);
wrenEnsureSlots(vm, paramsSlot + 2);
for (int i = 0; i < count; i++) {
wrenGetListElement(vm, paramsSlot, i, paramsSlot + 1);
WrenType type = wrenGetSlotType(vm, paramsSlot + 1);
switch (type) {
case WREN_TYPE_NULL:
sqlite3_bind_null(stmt, i + 1);
break;
case WREN_TYPE_BOOL:
sqlite3_bind_int(stmt, i + 1, wrenGetSlotBool(vm, paramsSlot + 1) ? 1 : 0);
break;
case WREN_TYPE_NUM:
sqlite3_bind_double(stmt, i + 1, wrenGetSlotDouble(vm, paramsSlot + 1));
break;
case WREN_TYPE_STRING:
sqlite3_bind_text(stmt, i + 1, wrenGetSlotString(vm, paramsSlot + 1), -1, SQLITE_TRANSIENT);
break;
default:
sqlite3_bind_null(stmt, i + 1);
break;
}
}
}
static void executeInternal(WrenVM* vm, bool hasParams) {
SqliteData* data = (SqliteData*)wrenGetSlotForeign(vm, 0);
if (data->db == NULL) {
wrenSetSlotString(vm, 0, "Database is closed.");
wrenAbortFiber(vm, 0);
return;
}
const char* sql = wrenGetSlotString(vm, 1);
sqlite3_stmt* stmt;
int result = sqlite3_prepare_v2(data->db, sql, -1, &stmt, NULL);
if (result != SQLITE_OK) {
wrenSetSlotString(vm, 0, sqlite3_errmsg(data->db));
wrenAbortFiber(vm, 0);
return;
}
if (hasParams) {
bindParams(vm, stmt, 2);
}
result = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (result != SQLITE_DONE && result != SQLITE_ROW) {
wrenSetSlotString(vm, 0, sqlite3_errmsg(data->db));
wrenAbortFiber(vm, 0);
return;
}
wrenSetSlotNull(vm, 0);
}
void sqliteExecute(WrenVM* vm) {
executeInternal(vm, false);
}
void sqliteExecuteParams(WrenVM* vm) {
executeInternal(vm, true);
}
static void queryInternal(WrenVM* vm, bool hasParams) {
SqliteData* data = (SqliteData*)wrenGetSlotForeign(vm, 0);
if (data->db == NULL) {
wrenSetSlotString(vm, 0, "Database is closed.");
wrenAbortFiber(vm, 0);
return;
}
const char* sql = wrenGetSlotString(vm, 1);
sqlite3_stmt* stmt;
int result = sqlite3_prepare_v2(data->db, sql, -1, &stmt, NULL);
if (result != SQLITE_OK) {
wrenSetSlotString(vm, 0, sqlite3_errmsg(data->db));
wrenAbortFiber(vm, 0);
return;
}
if (hasParams) {
bindParams(vm, stmt, 2);
}
wrenEnsureSlots(vm, 5);
wrenSetSlotNewList(vm, 0);
int colCount = sqlite3_column_count(stmt);
while ((result = sqlite3_step(stmt)) == SQLITE_ROW) {
wrenSetSlotNewMap(vm, 3);
for (int i = 0; i < colCount; i++) {
const char* colName = sqlite3_column_name(stmt, i);
wrenSetSlotString(vm, 1, colName);
int colType = sqlite3_column_type(stmt, i);
switch (colType) {
case SQLITE_NULL:
wrenSetSlotNull(vm, 4);
break;
case SQLITE_INTEGER:
wrenSetSlotDouble(vm, 4, (double)sqlite3_column_int64(stmt, i));
break;
case SQLITE_FLOAT:
wrenSetSlotDouble(vm, 4, sqlite3_column_double(stmt, i));
break;
case SQLITE_TEXT:
wrenSetSlotString(vm, 4, (const char*)sqlite3_column_text(stmt, i));
break;
case SQLITE_BLOB: {
int size = sqlite3_column_bytes(stmt, i);
const unsigned char* blob = sqlite3_column_blob(stmt, i);
wrenSetSlotNewList(vm, 4);
for (int j = 0; j < size; j++) {
wrenSetSlotDouble(vm, 2, (double)blob[j]);
wrenInsertInList(vm, 4, -1, 2);
}
break;
}
default:
wrenSetSlotNull(vm, 4);
break;
}
wrenSetMapValue(vm, 3, 1, 4);
}
wrenInsertInList(vm, 0, -1, 3);
}
sqlite3_finalize(stmt);
if (result != SQLITE_DONE) {
wrenSetSlotString(vm, 0, sqlite3_errmsg(data->db));
wrenAbortFiber(vm, 0);
}
}
void sqliteQuery(WrenVM* vm) {
queryInternal(vm, false);
}
void sqliteQueryParams(WrenVM* vm) {
queryInternal(vm, true);
}
void sqliteClose(WrenVM* vm) {
SqliteData* data = (SqliteData*)wrenGetSlotForeign(vm, 0);
if (data->db != NULL) {
sqlite3_close(data->db);
data->db = NULL;
}
}
void sqliteLastInsertId(WrenVM* vm) {
SqliteData* data = (SqliteData*)wrenGetSlotForeign(vm, 0);
if (data->db == NULL) {
wrenSetSlotDouble(vm, 0, 0);
return;
}
wrenSetSlotDouble(vm, 0, (double)sqlite3_last_insert_rowid(data->db));
}
void sqliteChanges(WrenVM* vm) {
SqliteData* data = (SqliteData*)wrenGetSlotForeign(vm, 0);
if (data->db == NULL) {
wrenSetSlotDouble(vm, 0, 0);
return;
}
wrenSetSlotDouble(vm, 0, (double)sqlite3_changes(data->db));
}

View File

@ -0,0 +1,18 @@
// retoor <retoor@molodetz.nl>
#ifndef wren_sqlite_h
#define wren_sqlite_h
#include "wren.h"
void sqliteAllocate(WrenVM* vm);
void sqliteFinalize(void* data);
void sqliteExecute(WrenVM* vm);
void sqliteExecuteParams(WrenVM* vm);
void sqliteQuery(WrenVM* vm);
void sqliteQueryParams(WrenVM* vm);
void sqliteClose(WrenVM* vm);
void sqliteLastInsertId(WrenVM* vm);
void sqliteChanges(WrenVM* vm);
#endif

184
src/module/subprocess.c Normal file
View File

@ -0,0 +1,184 @@
// retoor <retoor@molodetz.nl>
#include <stdlib.h>
#include <string.h>
#include "subprocess.h"
#include "scheduler.h"
#include "wren.h"
#include "vm.h"
#include "uv.h"
typedef struct {
WrenHandle* fiber;
uv_process_t process;
uv_pipe_t stdoutPipe;
uv_pipe_t stderrPipe;
char* stdoutBuf;
size_t stdoutLen;
size_t stdoutCap;
char* stderrBuf;
size_t stderrLen;
size_t stderrCap;
int64_t exitCode;
int handlesOpen;
} ProcessData;
static void appendBuffer(char** buf, size_t* len, size_t* cap, const char* data, size_t dataLen) {
if (*len + dataLen >= *cap) {
size_t newCap = (*cap == 0) ? 1024 : *cap * 2;
while (newCap < *len + dataLen + 1) newCap *= 2;
*buf = (char*)realloc(*buf, newCap);
*cap = newCap;
}
memcpy(*buf + *len, data, dataLen);
*len += dataLen;
(*buf)[*len] = '\0';
}
static void onHandleClosed(uv_handle_t* handle) {
ProcessData* data = (ProcessData*)handle->data;
data->handlesOpen--;
if (data->handlesOpen > 0) return;
WrenVM* vm = getVM();
WrenHandle* fiber = data->fiber;
wrenEnsureSlots(vm, 4);
wrenSetSlotNewList(vm, 2);
wrenSetSlotDouble(vm, 3, (double)data->exitCode);
wrenInsertInList(vm, 2, -1, 3);
wrenSetSlotString(vm, 3, data->stdoutBuf != NULL ? data->stdoutBuf : "");
wrenInsertInList(vm, 2, -1, 3);
wrenSetSlotString(vm, 3, data->stderrBuf != NULL ? data->stderrBuf : "");
wrenInsertInList(vm, 2, -1, 3);
if (data->stdoutBuf != NULL) free(data->stdoutBuf);
if (data->stderrBuf != NULL) free(data->stderrBuf);
free(data);
schedulerResume(fiber, true);
schedulerFinishResume();
}
static void allocBuffer(uv_handle_t* handle, size_t size, uv_buf_t* buf) {
buf->base = (char*)malloc(size);
buf->len = size;
}
static void onStdoutRead(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
ProcessData* data = (ProcessData*)stream->data;
if (nread > 0) {
appendBuffer(&data->stdoutBuf, &data->stdoutLen, &data->stdoutCap, buf->base, nread);
}
if (buf->base != NULL) free(buf->base);
if (nread < 0) {
uv_close((uv_handle_t*)stream, onHandleClosed);
}
}
static void onStderrRead(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
ProcessData* data = (ProcessData*)stream->data;
if (nread > 0) {
appendBuffer(&data->stderrBuf, &data->stderrLen, &data->stderrCap, buf->base, nread);
}
if (buf->base != NULL) free(buf->base);
if (nread < 0) {
uv_close((uv_handle_t*)stream, onHandleClosed);
}
}
static void onProcessExit(uv_process_t* process, int64_t exitStatus, int termSignal) {
ProcessData* data = (ProcessData*)process->data;
data->exitCode = exitStatus;
uv_close((uv_handle_t*)process, onHandleClosed);
}
void subprocessRun(WrenVM* vm) {
int argCount = wrenGetListCount(vm, 1);
if (argCount == 0) {
wrenSetSlotString(vm, 0, "Command list cannot be empty.");
wrenAbortFiber(vm, 0);
return;
}
char** args = (char**)malloc((argCount + 1) * sizeof(char*));
if (args == NULL) {
wrenSetSlotString(vm, 0, "Memory allocation failed.");
wrenAbortFiber(vm, 0);
return;
}
wrenEnsureSlots(vm, 4);
for (int i = 0; i < argCount; i++) {
wrenGetListElement(vm, 1, i, 3);
const char* arg = wrenGetSlotString(vm, 3);
args[i] = strdup(arg);
}
args[argCount] = NULL;
WrenHandle* fiber = wrenGetSlotHandle(vm, 2);
ProcessData* data = (ProcessData*)calloc(1, sizeof(ProcessData));
if (data == NULL) {
for (int i = 0; i < argCount; i++) free(args[i]);
free(args);
wrenReleaseHandle(vm, fiber);
wrenSetSlotString(vm, 0, "Memory allocation failed.");
wrenAbortFiber(vm, 0);
return;
}
data->fiber = fiber;
data->handlesOpen = 3;
uv_loop_t* loop = getLoop();
uv_pipe_init(loop, &data->stdoutPipe, 0);
uv_pipe_init(loop, &data->stderrPipe, 0);
data->stdoutPipe.data = data;
data->stderrPipe.data = data;
data->process.data = data;
uv_stdio_container_t stdio[3];
stdio[0].flags = UV_IGNORE;
stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
stdio[1].data.stream = (uv_stream_t*)&data->stdoutPipe;
stdio[2].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
stdio[2].data.stream = (uv_stream_t*)&data->stderrPipe;
uv_process_options_t options;
memset(&options, 0, sizeof(options));
options.file = args[0];
options.args = args;
options.stdio = stdio;
options.stdio_count = 3;
options.exit_cb = onProcessExit;
int result = uv_spawn(loop, &data->process, &options);
for (int i = 0; i < argCount; i++) free(args[i]);
free(args);
if (result != 0) {
data->handlesOpen = 2;
uv_close((uv_handle_t*)&data->stdoutPipe, onHandleClosed);
uv_close((uv_handle_t*)&data->stderrPipe, onHandleClosed);
wrenReleaseHandle(vm, fiber);
wrenSetSlotString(vm, 0, uv_strerror(result));
wrenAbortFiber(vm, 0);
return;
}
uv_read_start((uv_stream_t*)&data->stdoutPipe, allocBuffer, onStdoutRead);
uv_read_start((uv_stream_t*)&data->stderrPipe, allocBuffer, onStderrRead);
}

10
src/module/subprocess.h Normal file
View File

@ -0,0 +1,10 @@
// retoor <retoor@molodetz.nl>
#ifndef wren_subprocess_h
#define wren_subprocess_h
#include "wren.h"
void subprocessRun(WrenVM* vm);
#endif

47
src/module/subprocess.wren vendored Normal file
View File

@ -0,0 +1,47 @@
// retoor <retoor@molodetz.nl>
import "scheduler" for Scheduler
class ProcessResult {
construct new_(exitCode, stdout, stderr) {
_exitCode = exitCode
_stdout = stdout
_stderr = stderr
}
exitCode { _exitCode }
stdout { _stdout }
stderr { _stderr }
success { _exitCode == 0 }
toString {
if (success) return _stdout
return "Process exited with code %(_exitCode): %(_stderr)"
}
}
class Subprocess {
foreign static run_(args, fiber)
static run(command) {
if (command is String) {
return run(["/bin/sh", "-c", command])
}
if (!(command is List)) Fiber.abort("Command must be a String or List.")
if (command.count == 0) Fiber.abort("Command list cannot be empty.")
run_(command, Fiber.current)
var result = Scheduler.runNextScheduled_()
return ProcessResult.new_(result[0], result[1], result[2])
}
static run(command, cwd) {
if (command is String) {
return run(["/bin/sh", "-c", command], cwd)
}
if (!(command is List)) Fiber.abort("Command must be a String or List.")
if (command.count == 0) Fiber.abort("Command list cannot be empty.")
run_(command, Fiber.current)
var result = Scheduler.runNextScheduled_()
return ProcessResult.new_(result[0], result[1], result[2])
}
}

View File

@ -0,0 +1,51 @@
// Please do not edit this file. It has been generated automatically
// from `src/module/subprocess.wren` using `util/wren_to_c_string.py`
static const char* subprocessModuleSource =
"// retoor <retoor@molodetz.nl>\n"
"\n"
"import \"scheduler\" for Scheduler\n"
"\n"
"class ProcessResult {\n"
" construct new_(exitCode, stdout, stderr) {\n"
" _exitCode = exitCode\n"
" _stdout = stdout\n"
" _stderr = stderr\n"
" }\n"
"\n"
" exitCode { _exitCode }\n"
" stdout { _stdout }\n"
" stderr { _stderr }\n"
" success { _exitCode == 0 }\n"
"\n"
" toString {\n"
" if (success) return _stdout\n"
" return \"Process exited with code %(_exitCode): %(_stderr)\"\n"
" }\n"
"}\n"
"\n"
"class Subprocess {\n"
" foreign static run_(args, fiber)\n"
"\n"
" static run(command) {\n"
" if (command is String) {\n"
" return run([\"/bin/sh\", \"-c\", command])\n"
" }\n"
" if (!(command is List)) Fiber.abort(\"Command must be a String or List.\")\n"
" if (command.count == 0) Fiber.abort(\"Command list cannot be empty.\")\n"
" run_(command, Fiber.current)\n"
" var result = Scheduler.runNextScheduled_()\n"
" return ProcessResult.new_(result[0], result[1], result[2])\n"
" }\n"
"\n"
" static run(command, cwd) {\n"
" if (command is String) {\n"
" return run([\"/bin/sh\", \"-c\", command], cwd)\n"
" }\n"
" if (!(command is List)) Fiber.abort(\"Command must be a String or List.\")\n"
" if (command.count == 0) Fiber.abort(\"Command list cannot be empty.\")\n"
" run_(command, Fiber.current)\n"
" var result = Scheduler.runNextScheduled_()\n"
" return ProcessResult.new_(result[0], result[1], result[2])\n"
" }\n"
"}\n";

403
src/module/tls.c Normal file
View File

@ -0,0 +1,403 @@
// retoor <retoor@molodetz.nl>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/x509v3.h>
#include "uv.h"
#include "vm.h"
#include "scheduler.h"
#include "tls.h"
static SSL_CTX* globalCtx = NULL;
static bool sslInitialized = false;
typedef enum {
TLS_STATE_DISCONNECTED,
TLS_STATE_CONNECTING,
TLS_STATE_HANDSHAKING,
TLS_STATE_CONNECTED,
TLS_STATE_ERROR
} TlsState;
typedef struct {
uv_tcp_t* handle;
SSL* ssl;
BIO* readBio;
BIO* writeBio;
WrenHandle* fiber;
TlsState state;
char* hostname;
char* readBuffer;
size_t readBufferSize;
bool wantRead;
bool writeInProgress;
} TlsSocketData;
static void initOpenSSL() {
if (sslInitialized) return;
SSL_library_init();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
globalCtx = SSL_CTX_new(TLS_client_method());
if (globalCtx) {
SSL_CTX_set_default_verify_paths(globalCtx);
SSL_CTX_set_verify(globalCtx, SSL_VERIFY_PEER, NULL);
SSL_CTX_set_mode(globalCtx, SSL_MODE_AUTO_RETRY);
}
sslInitialized = true;
}
static void tlsCloseCallback(uv_handle_t* handle) {
free(handle);
}
static void flushWriteBio(TlsSocketData* data);
static void doHandshake(TlsSocketData* data);
void tlsSocketAllocate(WrenVM* vm) {
initOpenSSL();
TlsSocketData* data = (TlsSocketData*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(TlsSocketData));
memset(data, 0, sizeof(TlsSocketData));
data->handle = (uv_tcp_t*)malloc(sizeof(uv_tcp_t));
uv_tcp_init(getLoop(), data->handle);
data->handle->data = data;
data->ssl = SSL_new(globalCtx);
data->readBio = BIO_new(BIO_s_mem());
data->writeBio = BIO_new(BIO_s_mem());
SSL_set_bio(data->ssl, data->readBio, data->writeBio);
SSL_set_connect_state(data->ssl);
data->state = TLS_STATE_DISCONNECTED;
data->readBufferSize = 16384;
data->readBuffer = (char*)malloc(data->readBufferSize);
}
void tlsSocketFinalize(void* ptr) {
TlsSocketData* data = (TlsSocketData*)ptr;
if (data->ssl) {
SSL_free(data->ssl);
data->ssl = NULL;
}
if (data->handle) {
uv_close((uv_handle_t*)data->handle, tlsCloseCallback);
data->handle = NULL;
}
if (data->hostname) {
free(data->hostname);
data->hostname = NULL;
}
if (data->readBuffer) {
free(data->readBuffer);
data->readBuffer = NULL;
}
}
static void tlsAllocCallback(uv_handle_t* handle, size_t suggestedSize, uv_buf_t* buf) {
buf->base = (char*)malloc(suggestedSize);
buf->len = suggestedSize;
}
static void tlsReadCallback(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
TlsSocketData* data = (TlsSocketData*)stream->data;
if (nread < 0) {
uv_read_stop(stream);
free(buf->base);
if (data->fiber) {
WrenHandle* fiber = data->fiber;
data->fiber = NULL;
if (nread == UV_EOF) {
schedulerResume(fiber, true);
wrenSetSlotNull(getVM(), 2);
schedulerFinishResume();
} else {
schedulerResumeError(fiber, uv_strerror(nread));
}
}
return;
}
if (nread > 0) {
BIO_write(data->readBio, buf->base, nread);
}
free(buf->base);
if (data->state == TLS_STATE_HANDSHAKING) {
doHandshake(data);
return;
}
if (data->state == TLS_STATE_CONNECTED && data->wantRead && data->fiber) {
int decrypted = SSL_read(data->ssl, data->readBuffer, data->readBufferSize - 1);
if (decrypted > 0) {
uv_read_stop(stream);
data->wantRead = false;
flushWriteBio(data);
WrenHandle* fiber = data->fiber;
data->fiber = NULL;
schedulerResume(fiber, true);
wrenSetSlotBytes(getVM(), 2, data->readBuffer, decrypted);
schedulerFinishResume();
} else {
int err = SSL_get_error(data->ssl, decrypted);
if (err == SSL_ERROR_ZERO_RETURN) {
uv_read_stop(stream);
data->wantRead = false;
flushWriteBio(data);
WrenHandle* fiber = data->fiber;
data->fiber = NULL;
schedulerResume(fiber, true);
wrenSetSlotNull(getVM(), 2);
schedulerFinishResume();
} else if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) {
uv_read_stop(stream);
data->wantRead = false;
flushWriteBio(data);
WrenHandle* fiber = data->fiber;
data->fiber = NULL;
schedulerResumeError(fiber, "SSL read error");
}
}
}
}
typedef struct {
uv_write_t req;
uv_buf_t buf;
TlsSocketData* data;
bool isLast;
} TlsWriteRequest;
static void tlsWriteCallback(uv_write_t* req, int status) {
TlsWriteRequest* wr = (TlsWriteRequest*)req;
TlsSocketData* data = wr->data;
bool isLast = wr->isLast;
free(wr->buf.base);
free(wr);
if (isLast && data->writeInProgress && data->fiber) {
data->writeInProgress = false;
WrenHandle* fiber = data->fiber;
data->fiber = NULL;
if (status < 0) {
schedulerResumeError(fiber, uv_strerror(status));
} else {
schedulerResume(fiber, false);
}
}
}
static void flushWriteBio(TlsSocketData* data) {
if (data->handle == NULL) return;
char buffer[16384];
int pending;
while ((pending = BIO_read(data->writeBio, buffer, sizeof(buffer))) > 0) {
TlsWriteRequest* wr = (TlsWriteRequest*)malloc(sizeof(TlsWriteRequest));
wr->buf.base = (char*)malloc(pending);
wr->buf.len = pending;
memcpy(wr->buf.base, buffer, pending);
wr->data = data;
wr->isLast = false;
wr->req.data = wr;
uv_write(&wr->req, (uv_stream_t*)data->handle, &wr->buf, 1, tlsWriteCallback);
}
}
static void doHandshake(TlsSocketData* data) {
int result = SSL_do_handshake(data->ssl);
flushWriteBio(data);
if (result == 1) {
data->state = TLS_STATE_CONNECTED;
uv_read_stop((uv_stream_t*)data->handle);
if (data->fiber) {
WrenHandle* fiber = data->fiber;
data->fiber = NULL;
schedulerResume(fiber, false);
}
} else {
int err = SSL_get_error(data->ssl, result);
if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
return;
}
data->state = TLS_STATE_ERROR;
uv_read_stop((uv_stream_t*)data->handle);
if (data->fiber) {
WrenHandle* fiber = data->fiber;
data->fiber = NULL;
unsigned long errCode = ERR_get_error();
char errMsg[256];
ERR_error_string_n(errCode, errMsg, sizeof(errMsg));
schedulerResumeError(fiber, errMsg);
}
}
}
static void tlsConnectCallback(uv_connect_t* req, int status) {
TlsSocketData* data = (TlsSocketData*)req->data;
free(req);
if (status < 0) {
data->state = TLS_STATE_ERROR;
if (data->fiber) {
WrenHandle* fiber = data->fiber;
data->fiber = NULL;
schedulerResumeError(fiber, uv_strerror(status));
}
return;
}
data->state = TLS_STATE_HANDSHAKING;
if (data->hostname) {
SSL_set_tlsext_host_name(data->ssl, data->hostname);
SSL_set1_host(data->ssl, data->hostname);
}
uv_read_start((uv_stream_t*)data->handle, tlsAllocCallback, tlsReadCallback);
doHandshake(data);
}
void tlsSocketConnect(WrenVM* vm) {
TlsSocketData* data = (TlsSocketData*)wrenGetSlotForeign(vm, 0);
const char* host = wrenGetSlotString(vm, 1);
int port = (int)wrenGetSlotDouble(vm, 2);
const char* hostname = wrenGetSlotString(vm, 3);
WrenHandle* fiber = wrenGetSlotHandle(vm, 4);
data->fiber = fiber;
data->hostname = strdup(hostname);
data->state = TLS_STATE_CONNECTING;
struct sockaddr_in addr;
int r = uv_ip4_addr(host, port, &addr);
if (r != 0) {
data->state = TLS_STATE_ERROR;
schedulerResumeError(fiber, "Invalid IP address");
return;
}
uv_connect_t* connectReq = (uv_connect_t*)malloc(sizeof(uv_connect_t));
connectReq->data = data;
r = uv_tcp_connect(connectReq, data->handle, (const struct sockaddr*)&addr, tlsConnectCallback);
if (r != 0) {
free(connectReq);
data->state = TLS_STATE_ERROR;
schedulerResumeError(fiber, uv_strerror(r));
}
}
void tlsSocketWrite(WrenVM* vm) {
TlsSocketData* data = (TlsSocketData*)wrenGetSlotForeign(vm, 0);
int length;
const char* text = wrenGetSlotBytes(vm, 1, &length);
WrenHandle* fiber = wrenGetSlotHandle(vm, 2);
if (data->state != TLS_STATE_CONNECTED) {
schedulerResumeError(fiber, "Socket not connected");
return;
}
data->fiber = fiber;
data->writeInProgress = true;
int written = SSL_write(data->ssl, text, length);
if (written <= 0) {
int err = SSL_get_error(data->ssl, written);
if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) {
data->writeInProgress = false;
data->fiber = NULL;
schedulerResumeError(fiber, "SSL write error");
return;
}
}
char buffer[16384];
int pending;
TlsWriteRequest* lastWr = NULL;
while ((pending = BIO_read(data->writeBio, buffer, sizeof(buffer))) > 0) {
TlsWriteRequest* wr = (TlsWriteRequest*)malloc(sizeof(TlsWriteRequest));
wr->buf.base = (char*)malloc(pending);
wr->buf.len = pending;
memcpy(wr->buf.base, buffer, pending);
wr->data = data;
wr->isLast = false;
wr->req.data = wr;
lastWr = wr;
uv_write(&wr->req, (uv_stream_t*)data->handle, &wr->buf, 1, tlsWriteCallback);
}
if (lastWr) {
lastWr->isLast = true;
} else {
data->writeInProgress = false;
data->fiber = NULL;
schedulerResume(fiber, false);
}
}
void tlsSocketRead(WrenVM* vm) {
TlsSocketData* data = (TlsSocketData*)wrenGetSlotForeign(vm, 0);
WrenHandle* fiber = wrenGetSlotHandle(vm, 1);
if (data->state != TLS_STATE_CONNECTED) {
schedulerResumeError(fiber, "Socket not connected");
return;
}
data->fiber = fiber;
data->wantRead = true;
uv_read_start((uv_stream_t*)data->handle, tlsAllocCallback, tlsReadCallback);
}
void tlsSocketClose(WrenVM* vm) {
TlsSocketData* data = (TlsSocketData*)wrenGetSlotForeign(vm, 0);
if (data->ssl) {
SSL_shutdown(data->ssl);
}
if (data->handle && !uv_is_closing((uv_handle_t*)data->handle)) {
uv_close((uv_handle_t*)data->handle, tlsCloseCallback);
data->handle = NULL;
}
data->state = TLS_STATE_DISCONNECTED;
}

15
src/module/tls.h Normal file
View File

@ -0,0 +1,15 @@
// retoor <retoor@molodetz.nl>
#ifndef tls_h
#define tls_h
#include "wren.h"
void tlsSocketAllocate(WrenVM* vm);
void tlsSocketFinalize(void* data);
void tlsSocketConnect(WrenVM* vm);
void tlsSocketWrite(WrenVM* vm);
void tlsSocketRead(WrenVM* vm);
void tlsSocketClose(WrenVM* vm);
#endif

30
src/module/tls.wren vendored Normal file
View File

@ -0,0 +1,30 @@
// retoor <retoor@molodetz.nl>
import "scheduler" for Scheduler
foreign class TlsSocket {
construct new() {}
foreign connect_(host, port, hostname, fiber)
foreign write_(text, fiber)
foreign read_(fiber)
foreign close_()
static connect(host, port, hostname) {
var socket = TlsSocket.new()
Scheduler.await_ { socket.connect_(host, port, hostname, Fiber.current) }
return socket
}
write(text) {
Scheduler.await_ { write_(text, Fiber.current) }
}
read() {
return Scheduler.await_ { read_(Fiber.current) }
}
close() {
close_()
}
}

34
src/module/tls.wren.inc Normal file
View File

@ -0,0 +1,34 @@
// Please do not edit this file. It has been generated automatically
// from `/home/retoor/projects/wren-cli/src/module/tls.wren` using `util/wren_to_c_string.py`
static const char* tlsModuleSource =
"// retoor <retoor@molodetz.nl>\n"
"\n"
"import \"scheduler\" for Scheduler\n"
"\n"
"foreign class TlsSocket {\n"
" construct new() {}\n"
"\n"
" foreign connect_(host, port, hostname, fiber)\n"
" foreign write_(text, fiber)\n"
" foreign read_(fiber)\n"
" foreign close_()\n"
"\n"
" static connect(host, port, hostname) {\n"
" var socket = TlsSocket.new()\n"
" Scheduler.await_ { socket.connect_(host, port, hostname, Fiber.current) }\n"
" return socket\n"
" }\n"
"\n"
" write(text) {\n"
" Scheduler.await_ { write_(text, Fiber.current) }\n"
" }\n"
"\n"
" read() {\n"
" return Scheduler.await_ { read_(Fiber.current) }\n"
" }\n"
"\n"
" close() {\n"
" close_()\n"
" }\n"
"}\n";

553
src/module/websocket.wren vendored Normal file
View File

@ -0,0 +1,553 @@
// retoor <retoor@molodetz.nl>
import "net" for Socket, Server
import "tls" for TlsSocket
import "dns" for Dns
import "crypto" for Crypto, Hash
import "base64" for Base64
class WebSocketMessage {
construct new_(opcode, payload, fin) {
_opcode = opcode
_payload = payload
_fin = fin
}
opcode { _opcode }
payload { _payload }
fin { _fin }
isText { _opcode == 1 }
isBinary { _opcode == 2 }
isClose { _opcode == 8 }
isPing { _opcode == 9 }
isPong { _opcode == 10 }
text {
if (!isText) Fiber.abort("Message is not a text frame.")
return bytesToString_(_payload)
}
bytes { _payload }
closeCode {
if (!isClose) Fiber.abort("Message is not a close frame.")
if (_payload.count < 2) return 1005
return (_payload[0] << 8) | _payload[1]
}
closeReason {
if (!isClose) Fiber.abort("Message is not a close frame.")
if (_payload.count <= 2) return ""
return bytesToString_(_payload[2..-1])
}
static bytesToString_(bytes) {
var str = ""
for (b in bytes) {
str = str + String.fromCodePoint(b)
}
return str
}
bytesToString_(bytes) { WebSocketMessage.bytesToString_(bytes) }
}
class WebSocket {
construct new_(socket, url, isClient) {
_socket = socket
_url = url
_isClient = isClient
_isOpen = true
_fragmentBuffer = []
_fragmentOpcode = null
_readBuffer = []
}
static connect(url) { connect(url, {}) }
static connect(url, headers) {
var parsed = parseUrl_(url)
if (parsed["scheme"] != "ws" && parsed["scheme"] != "wss") {
Fiber.abort("Unsupported scheme: %(parsed["scheme"]). Use ws:// or wss://")
}
var isSecure = parsed["scheme"] == "wss"
var host = parsed["host"]
var port = parsed["port"]
var path = parsed["path"]
var addresses = Dns.lookup(host, 4)
if (addresses.count == 0) {
Fiber.abort("Could not resolve host: %(host)")
}
var socket
if (isSecure) {
socket = TlsSocket.connect(addresses[0], port, host)
} else {
socket = Socket.connect(addresses[0], port)
}
var keyBytes = Crypto.randomBytes(16)
var key = Base64.encode(bytesToString_(keyBytes))
var request = "GET %(path) HTTP/1.1\r\n"
request = request + "Host: %(host)\r\n"
request = request + "Upgrade: websocket\r\n"
request = request + "Connection: Upgrade\r\n"
request = request + "Sec-WebSocket-Key: %(key)\r\n"
request = request + "Sec-WebSocket-Version: 13\r\n"
for (entry in headers) {
request = request + "%(entry.key): %(entry.value)\r\n"
}
request = request + "\r\n"
socket.write(request)
var response = ""
while (true) {
var chunk = socket.read()
if (chunk == null || chunk.count == 0) {
Fiber.abort("Connection closed during handshake")
}
response = response + chunk
if (response.indexOf("\r\n\r\n") >= 0) break
}
var headerEnd = response.indexOf("\r\n\r\n")
var headerPart = response[0...headerEnd]
var lines = splitLines_(headerPart)
if (lines.count == 0) {
Fiber.abort("Invalid WebSocket handshake response")
}
var statusLine = lines[0]
if (!statusLine.contains("101")) {
Fiber.abort("WebSocket handshake failed: %(statusLine)")
}
var expectedAccept = computeAcceptKey_(key)
var receivedAccept = null
for (i in 1...lines.count) {
var line = lines[i]
var colonPos = line.indexOf(":")
if (colonPos > 0) {
var name = toLower_(line[0...colonPos].trim())
var value = line[(colonPos + 1)..-1].trim()
if (name == "sec-websocket-accept") {
receivedAccept = value
break
}
}
}
if (receivedAccept != expectedAccept) {
Fiber.abort("Invalid Sec-WebSocket-Accept header")
}
return WebSocket.new_(socket, url, true)
}
static parseUrl_(url) {
var result = {
"scheme": "ws",
"host": "",
"port": 80,
"path": "/"
}
var rest = url
var schemeEnd = rest.indexOf("://")
if (schemeEnd >= 0) {
result["scheme"] = toLower_(rest[0...schemeEnd])
rest = rest[(schemeEnd + 3)..-1]
if (result["scheme"] == "wss") result["port"] = 443
}
var pathStart = rest.indexOf("/")
var hostPart = pathStart >= 0 ? rest[0...pathStart] : rest
result["path"] = pathStart >= 0 ? rest[pathStart..-1] : "/"
var portStart = hostPart.indexOf(":")
if (portStart >= 0) {
result["host"] = hostPart[0...portStart]
result["port"] = Num.fromString(hostPart[(portStart + 1)..-1])
} else {
result["host"] = hostPart
}
return result
}
static splitLines_(text) {
var lines = []
var start = 0
var i = 0
while (i < text.count) {
if (text[i] == "\r" && i + 1 < text.count && text[i + 1] == "\n") {
lines.add(text[start...i])
start = i + 2
i = i + 2
} else {
i = i + 1
}
}
if (start < text.count) {
lines.add(text[start..-1])
}
return lines
}
static toLower_(str) {
var result = ""
for (c in str) {
var cp = c.codePoints[0]
if (cp >= 65 && cp <= 90) {
result = result + String.fromCodePoint(cp + 32)
} else {
result = result + c
}
}
return result
}
static computeAcceptKey_(key) {
var magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
var combined = key + magic
var sha1 = Hash.sha1(combined)
var sha1Str = bytesToString_(sha1)
return Base64.encode(sha1Str)
}
static bytesToString_(bytes) {
var str = ""
for (b in bytes) {
str = str + String.fromByte(b)
}
return str
}
url { _url }
isOpen { _isOpen }
send(text) {
if (!_isOpen) Fiber.abort("WebSocket is not open.")
if (!(text is String)) Fiber.abort("Data must be a string.")
var payload = stringToBytes_(text)
sendFrame_(1, payload)
}
sendBinary(bytes) {
if (!_isOpen) Fiber.abort("WebSocket is not open.")
if (!(bytes is List)) Fiber.abort("Data must be a list of bytes.")
sendFrame_(2, bytes)
}
ping() { ping([]) }
ping(data) {
if (!_isOpen) Fiber.abort("WebSocket is not open.")
var payload = data
if (data is String) payload = stringToBytes_(data)
if (payload.count > 125) Fiber.abort("Ping payload too large (max 125 bytes).")
sendFrame_(9, payload)
}
pong(data) {
if (!_isOpen) Fiber.abort("WebSocket is not open.")
var payload = data
if (data is String) payload = stringToBytes_(data)
if (payload.count > 125) Fiber.abort("Pong payload too large (max 125 bytes).")
sendFrame_(10, payload)
}
close() { close(1000, "") }
close(code, reason) {
if (!_isOpen) return
var payload = []
payload.add((code >> 8) & 0xFF)
payload.add(code & 0xFF)
for (b in stringToBytes_(reason)) {
payload.add(b)
}
sendFrame_(8, payload)
_isOpen = false
_socket.close()
}
receive() {
if (!_isOpen) return null
while (true) {
var frame = readFrame_()
if (frame == null) {
_isOpen = false
return null
}
var opcode = frame.opcode
var payload = frame.payload
var fin = frame.fin
if (opcode == 8) {
_isOpen = false
if (_isClient) {
sendFrame_(8, payload)
_socket.close()
}
return frame
}
if (opcode == 9) {
sendFrame_(10, payload)
continue
}
if (opcode == 10) {
continue
}
if (opcode == 0) {
for (b in payload) {
_fragmentBuffer.add(b)
}
if (fin) {
var completePayload = _fragmentBuffer
var completeOpcode = _fragmentOpcode
_fragmentBuffer = []
_fragmentOpcode = null
return WebSocketMessage.new_(completeOpcode, completePayload, true)
}
continue
}
if (opcode == 1 || opcode == 2) {
if (fin) {
return frame
} else {
_fragmentOpcode = opcode
_fragmentBuffer = []
for (b in payload) {
_fragmentBuffer.add(b)
}
continue
}
}
Fiber.abort("Unknown opcode: %(opcode)")
}
}
sendFrame_(opcode, payload) {
var frame = encodeFrame_(opcode, payload, _isClient)
var data = ""
for (b in frame) {
data = data + String.fromByte(b)
}
_socket.write(data)
}
encodeFrame_(opcode, payload, masked) {
var frame = []
frame.add(0x80 | opcode)
var len = payload.count
var maskBit = masked ? 0x80 : 0x00
if (len < 126) {
frame.add(maskBit | len)
} else if (len < 65536) {
frame.add(maskBit | 126)
frame.add((len >> 8) & 0xFF)
frame.add(len & 0xFF)
} else {
frame.add(maskBit | 127)
for (i in 0...4) frame.add(0)
frame.add((len >> 24) & 0xFF)
frame.add((len >> 16) & 0xFF)
frame.add((len >> 8) & 0xFF)
frame.add(len & 0xFF)
}
if (masked) {
var mask = Crypto.randomBytes(4)
for (b in mask) frame.add(b)
for (i in 0...len) {
frame.add(payload[i] ^ mask[i % 4])
}
} else {
for (b in payload) frame.add(b)
}
return frame
}
readFrame_() {
var header = readBytes_(2)
if (header == null || header.count < 2) return null
var fin = (header[0] & 0x80) != 0
var opcode = header[0] & 0x0F
var masked = (header[1] & 0x80) != 0
var len = header[1] & 0x7F
if (len == 126) {
var ext = readBytes_(2)
if (ext == null || ext.count < 2) return null
len = (ext[0] << 8) | ext[1]
} else if (len == 127) {
var ext = readBytes_(8)
if (ext == null || ext.count < 8) return null
len = 0
for (i in 4...8) {
len = (len << 8) | ext[i]
}
}
var mask = null
if (masked) {
mask = readBytes_(4)
if (mask == null || mask.count < 4) return null
}
var payload = []
if (len > 0) {
payload = readBytes_(len)
if (payload == null) return null
}
if (masked && mask != null) {
for (i in 0...payload.count) {
payload[i] = payload[i] ^ mask[i % 4]
}
}
return WebSocketMessage.new_(opcode, payload, fin)
}
readBytes_(count) {
while (_readBuffer.count < count) {
var chunk = _socket.read()
if (chunk == null || chunk.count == 0) {
if (_readBuffer.count == 0) return null
var result = []
for (b in _readBuffer) result.add(b)
_readBuffer = []
return result
}
for (b in chunk.bytes) {
_readBuffer.add(b)
}
}
var result = []
for (i in 0...count) {
result.add(_readBuffer[i])
}
var remaining = []
for (i in count..._readBuffer.count) {
remaining.add(_readBuffer[i])
}
_readBuffer = remaining
return result
}
stringToBytes_(str) {
var bytes = []
for (b in str.bytes) {
bytes.add(b)
}
return bytes
}
}
class WebSocketServer {
construct new_(server) {
_server = server
}
static bind(host, port) {
var server = Server.bind(host, port)
return WebSocketServer.new_(server)
}
accept() {
var socket = _server.accept()
return upgradeConnection_(socket)
}
upgradeConnection_(socket) {
var request = ""
while (true) {
var chunk = socket.read()
if (chunk == null || chunk.count == 0) {
socket.close()
return null
}
request = request + chunk
if (request.indexOf("\r\n\r\n") >= 0) break
}
var headerEnd = request.indexOf("\r\n\r\n")
var headerPart = request[0...headerEnd]
var lines = WebSocket.splitLines_(headerPart)
if (lines.count == 0) {
socket.close()
return null
}
var key = null
var isUpgrade = false
var isWebSocket = false
for (i in 1...lines.count) {
var line = lines[i]
var colonPos = line.indexOf(":")
if (colonPos > 0) {
var name = WebSocket.toLower_(line[0...colonPos].trim())
var value = line[(colonPos + 1)..-1].trim()
if (name == "sec-websocket-key") {
key = value
} else if (name == "upgrade" && WebSocket.toLower_(value) == "websocket") {
isWebSocket = true
} else if (name == "connection" && WebSocket.toLower_(value).contains("upgrade")) {
isUpgrade = true
}
}
}
if (!isUpgrade || !isWebSocket || key == null) {
var response = "HTTP/1.1 400 Bad Request\r\n\r\n"
socket.write(response)
socket.close()
return null
}
var acceptKey = WebSocket.computeAcceptKey_(key)
var response = "HTTP/1.1 101 Switching Protocols\r\n"
response = response + "Upgrade: websocket\r\n"
response = response + "Connection: Upgrade\r\n"
response = response + "Sec-WebSocket-Accept: %(acceptKey)\r\n"
response = response + "\r\n"
socket.write(response)
return WebSocket.new_(socket, null, false)
}
close() {
_server.close()
}
}

View File

@ -0,0 +1,557 @@
// Please do not edit this file. It has been generated automatically
// from `src/module/websocket.wren` using `util/wren_to_c_string.py`
static const char* websocketModuleSource =
"// retoor <retoor@molodetz.nl>\n"
"\n"
"import \"net\" for Socket, Server\n"
"import \"tls\" for TlsSocket\n"
"import \"dns\" for Dns\n"
"import \"crypto\" for Crypto, Hash\n"
"import \"base64\" for Base64\n"
"\n"
"class WebSocketMessage {\n"
" construct new_(opcode, payload, fin) {\n"
" _opcode = opcode\n"
" _payload = payload\n"
" _fin = fin\n"
" }\n"
"\n"
" opcode { _opcode }\n"
" payload { _payload }\n"
" fin { _fin }\n"
"\n"
" isText { _opcode == 1 }\n"
" isBinary { _opcode == 2 }\n"
" isClose { _opcode == 8 }\n"
" isPing { _opcode == 9 }\n"
" isPong { _opcode == 10 }\n"
"\n"
" text {\n"
" if (!isText) Fiber.abort(\"Message is not a text frame.\")\n"
" return bytesToString_(_payload)\n"
" }\n"
"\n"
" bytes { _payload }\n"
"\n"
" closeCode {\n"
" if (!isClose) Fiber.abort(\"Message is not a close frame.\")\n"
" if (_payload.count < 2) return 1005\n"
" return (_payload[0] << 8) | _payload[1]\n"
" }\n"
"\n"
" closeReason {\n"
" if (!isClose) Fiber.abort(\"Message is not a close frame.\")\n"
" if (_payload.count <= 2) return \"\"\n"
" return bytesToString_(_payload[2..-1])\n"
" }\n"
"\n"
" static bytesToString_(bytes) {\n"
" var str = \"\"\n"
" for (b in bytes) {\n"
" str = str + String.fromCodePoint(b)\n"
" }\n"
" return str\n"
" }\n"
"\n"
" bytesToString_(bytes) { WebSocketMessage.bytesToString_(bytes) }\n"
"}\n"
"\n"
"class WebSocket {\n"
" construct new_(socket, url, isClient) {\n"
" _socket = socket\n"
" _url = url\n"
" _isClient = isClient\n"
" _isOpen = true\n"
" _fragmentBuffer = []\n"
" _fragmentOpcode = null\n"
" _readBuffer = []\n"
" }\n"
"\n"
" static connect(url) { connect(url, {}) }\n"
"\n"
" static connect(url, headers) {\n"
" var parsed = parseUrl_(url)\n"
"\n"
" if (parsed[\"scheme\"] != \"ws\" && parsed[\"scheme\"] != \"wss\") {\n"
" Fiber.abort(\"Unsupported scheme: %(parsed[\"scheme\"]). Use ws:// or wss://\")\n"
" }\n"
"\n"
" var isSecure = parsed[\"scheme\"] == \"wss\"\n"
" var host = parsed[\"host\"]\n"
" var port = parsed[\"port\"]\n"
" var path = parsed[\"path\"]\n"
"\n"
" var addresses = Dns.lookup(host, 4)\n"
" if (addresses.count == 0) {\n"
" Fiber.abort(\"Could not resolve host: %(host)\")\n"
" }\n"
"\n"
" var socket\n"
" if (isSecure) {\n"
" socket = TlsSocket.connect(addresses[0], port, host)\n"
" } else {\n"
" socket = Socket.connect(addresses[0], port)\n"
" }\n"
"\n"
" var keyBytes = Crypto.randomBytes(16)\n"
" var key = Base64.encode(bytesToString_(keyBytes))\n"
"\n"
" var request = \"GET %(path) HTTP/1.1\\r\\n\"\n"
" request = request + \"Host: %(host)\\r\\n\"\n"
" request = request + \"Upgrade: websocket\\r\\n\"\n"
" request = request + \"Connection: Upgrade\\r\\n\"\n"
" request = request + \"Sec-WebSocket-Key: %(key)\\r\\n\"\n"
" request = request + \"Sec-WebSocket-Version: 13\\r\\n\"\n"
"\n"
" for (entry in headers) {\n"
" request = request + \"%(entry.key): %(entry.value)\\r\\n\"\n"
" }\n"
" request = request + \"\\r\\n\"\n"
"\n"
" socket.write(request)\n"
"\n"
" var response = \"\"\n"
" while (true) {\n"
" var chunk = socket.read()\n"
" if (chunk == null || chunk.count == 0) {\n"
" Fiber.abort(\"Connection closed during handshake\")\n"
" }\n"
" response = response + chunk\n"
" if (response.indexOf(\"\\r\\n\\r\\n\") >= 0) break\n"
" }\n"
"\n"
" var headerEnd = response.indexOf(\"\\r\\n\\r\\n\")\n"
" var headerPart = response[0...headerEnd]\n"
" var lines = splitLines_(headerPart)\n"
"\n"
" if (lines.count == 0) {\n"
" Fiber.abort(\"Invalid WebSocket handshake response\")\n"
" }\n"
"\n"
" var statusLine = lines[0]\n"
" if (!statusLine.contains(\"101\")) {\n"
" Fiber.abort(\"WebSocket handshake failed: %(statusLine)\")\n"
" }\n"
"\n"
" var expectedAccept = computeAcceptKey_(key)\n"
" var receivedAccept = null\n"
"\n"
" for (i in 1...lines.count) {\n"
" var line = lines[i]\n"
" var colonPos = line.indexOf(\":\")\n"
" if (colonPos > 0) {\n"
" var name = toLower_(line[0...colonPos].trim())\n"
" var value = line[(colonPos + 1)..-1].trim()\n"
" if (name == \"sec-websocket-accept\") {\n"
" receivedAccept = value\n"
" break\n"
" }\n"
" }\n"
" }\n"
"\n"
" if (receivedAccept != expectedAccept) {\n"
" Fiber.abort(\"Invalid Sec-WebSocket-Accept header\")\n"
" }\n"
"\n"
" return WebSocket.new_(socket, url, true)\n"
" }\n"
"\n"
" static parseUrl_(url) {\n"
" var result = {\n"
" \"scheme\": \"ws\",\n"
" \"host\": \"\",\n"
" \"port\": 80,\n"
" \"path\": \"/\"\n"
" }\n"
"\n"
" var rest = url\n"
"\n"
" var schemeEnd = rest.indexOf(\"://\")\n"
" if (schemeEnd >= 0) {\n"
" result[\"scheme\"] = toLower_(rest[0...schemeEnd])\n"
" rest = rest[(schemeEnd + 3)..-1]\n"
" if (result[\"scheme\"] == \"wss\") result[\"port\"] = 443\n"
" }\n"
"\n"
" var pathStart = rest.indexOf(\"/\")\n"
" var hostPart = pathStart >= 0 ? rest[0...pathStart] : rest\n"
" result[\"path\"] = pathStart >= 0 ? rest[pathStart..-1] : \"/\"\n"
"\n"
" var portStart = hostPart.indexOf(\":\")\n"
" if (portStart >= 0) {\n"
" result[\"host\"] = hostPart[0...portStart]\n"
" result[\"port\"] = Num.fromString(hostPart[(portStart + 1)..-1])\n"
" } else {\n"
" result[\"host\"] = hostPart\n"
" }\n"
"\n"
" return result\n"
" }\n"
"\n"
" static splitLines_(text) {\n"
" var lines = []\n"
" var start = 0\n"
" var i = 0\n"
" while (i < text.count) {\n"
" if (text[i] == \"\\r\" && i + 1 < text.count && text[i + 1] == \"\\n\") {\n"
" lines.add(text[start...i])\n"
" start = i + 2\n"
" i = i + 2\n"
" } else {\n"
" i = i + 1\n"
" }\n"
" }\n"
" if (start < text.count) {\n"
" lines.add(text[start..-1])\n"
" }\n"
" return lines\n"
" }\n"
"\n"
" static toLower_(str) {\n"
" var result = \"\"\n"
" for (c in str) {\n"
" var cp = c.codePoints[0]\n"
" if (cp >= 65 && cp <= 90) {\n"
" result = result + String.fromCodePoint(cp + 32)\n"
" } else {\n"
" result = result + c\n"
" }\n"
" }\n"
" return result\n"
" }\n"
"\n"
" static computeAcceptKey_(key) {\n"
" var magic = \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\"\n"
" var combined = key + magic\n"
" var sha1 = Hash.sha1(combined)\n"
" var sha1Str = bytesToString_(sha1)\n"
" return Base64.encode(sha1Str)\n"
" }\n"
"\n"
" static bytesToString_(bytes) {\n"
" var str = \"\"\n"
" for (b in bytes) {\n"
" str = str + String.fromByte(b)\n"
" }\n"
" return str\n"
" }\n"
"\n"
" url { _url }\n"
" isOpen { _isOpen }\n"
"\n"
" send(text) {\n"
" if (!_isOpen) Fiber.abort(\"WebSocket is not open.\")\n"
" if (!(text is String)) Fiber.abort(\"Data must be a string.\")\n"
" var payload = stringToBytes_(text)\n"
" sendFrame_(1, payload)\n"
" }\n"
"\n"
" sendBinary(bytes) {\n"
" if (!_isOpen) Fiber.abort(\"WebSocket is not open.\")\n"
" if (!(bytes is List)) Fiber.abort(\"Data must be a list of bytes.\")\n"
" sendFrame_(2, bytes)\n"
" }\n"
"\n"
" ping() { ping([]) }\n"
"\n"
" ping(data) {\n"
" if (!_isOpen) Fiber.abort(\"WebSocket is not open.\")\n"
" var payload = data\n"
" if (data is String) payload = stringToBytes_(data)\n"
" if (payload.count > 125) Fiber.abort(\"Ping payload too large (max 125 bytes).\")\n"
" sendFrame_(9, payload)\n"
" }\n"
"\n"
" pong(data) {\n"
" if (!_isOpen) Fiber.abort(\"WebSocket is not open.\")\n"
" var payload = data\n"
" if (data is String) payload = stringToBytes_(data)\n"
" if (payload.count > 125) Fiber.abort(\"Pong payload too large (max 125 bytes).\")\n"
" sendFrame_(10, payload)\n"
" }\n"
"\n"
" close() { close(1000, \"\") }\n"
"\n"
" close(code, reason) {\n"
" if (!_isOpen) return\n"
"\n"
" var payload = []\n"
" payload.add((code >> 8) & 0xFF)\n"
" payload.add(code & 0xFF)\n"
" for (b in stringToBytes_(reason)) {\n"
" payload.add(b)\n"
" }\n"
"\n"
" sendFrame_(8, payload)\n"
" _isOpen = false\n"
" _socket.close()\n"
" }\n"
"\n"
" receive() {\n"
" if (!_isOpen) return null\n"
"\n"
" while (true) {\n"
" var frame = readFrame_()\n"
" if (frame == null) {\n"
" _isOpen = false\n"
" return null\n"
" }\n"
"\n"
" var opcode = frame.opcode\n"
" var payload = frame.payload\n"
" var fin = frame.fin\n"
"\n"
" if (opcode == 8) {\n"
" _isOpen = false\n"
" if (_isClient) {\n"
" sendFrame_(8, payload)\n"
" _socket.close()\n"
" }\n"
" return frame\n"
" }\n"
"\n"
" if (opcode == 9) {\n"
" sendFrame_(10, payload)\n"
" continue\n"
" }\n"
"\n"
" if (opcode == 10) {\n"
" continue\n"
" }\n"
"\n"
" if (opcode == 0) {\n"
" for (b in payload) {\n"
" _fragmentBuffer.add(b)\n"
" }\n"
" if (fin) {\n"
" var completePayload = _fragmentBuffer\n"
" var completeOpcode = _fragmentOpcode\n"
" _fragmentBuffer = []\n"
" _fragmentOpcode = null\n"
" return WebSocketMessage.new_(completeOpcode, completePayload, true)\n"
" }\n"
" continue\n"
" }\n"
"\n"
" if (opcode == 1 || opcode == 2) {\n"
" if (fin) {\n"
" return frame\n"
" } else {\n"
" _fragmentOpcode = opcode\n"
" _fragmentBuffer = []\n"
" for (b in payload) {\n"
" _fragmentBuffer.add(b)\n"
" }\n"
" continue\n"
" }\n"
" }\n"
"\n"
" Fiber.abort(\"Unknown opcode: %(opcode)\")\n"
" }\n"
" }\n"
"\n"
" sendFrame_(opcode, payload) {\n"
" var frame = encodeFrame_(opcode, payload, _isClient)\n"
" var data = \"\"\n"
" for (b in frame) {\n"
" data = data + String.fromByte(b)\n"
" }\n"
" _socket.write(data)\n"
" }\n"
"\n"
" encodeFrame_(opcode, payload, masked) {\n"
" var frame = []\n"
"\n"
" frame.add(0x80 | opcode)\n"
"\n"
" var len = payload.count\n"
" var maskBit = masked ? 0x80 : 0x00\n"
"\n"
" if (len < 126) {\n"
" frame.add(maskBit | len)\n"
" } else if (len < 65536) {\n"
" frame.add(maskBit | 126)\n"
" frame.add((len >> 8) & 0xFF)\n"
" frame.add(len & 0xFF)\n"
" } else {\n"
" frame.add(maskBit | 127)\n"
" for (i in 0...4) frame.add(0)\n"
" frame.add((len >> 24) & 0xFF)\n"
" frame.add((len >> 16) & 0xFF)\n"
" frame.add((len >> 8) & 0xFF)\n"
" frame.add(len & 0xFF)\n"
" }\n"
"\n"
" if (masked) {\n"
" var mask = Crypto.randomBytes(4)\n"
" for (b in mask) frame.add(b)\n"
" for (i in 0...len) {\n"
" frame.add(payload[i] ^ mask[i % 4])\n"
" }\n"
" } else {\n"
" for (b in payload) frame.add(b)\n"
" }\n"
"\n"
" return frame\n"
" }\n"
"\n"
" readFrame_() {\n"
" var header = readBytes_(2)\n"
" if (header == null || header.count < 2) return null\n"
"\n"
" var fin = (header[0] & 0x80) != 0\n"
" var opcode = header[0] & 0x0F\n"
" var masked = (header[1] & 0x80) != 0\n"
" var len = header[1] & 0x7F\n"
"\n"
" if (len == 126) {\n"
" var ext = readBytes_(2)\n"
" if (ext == null || ext.count < 2) return null\n"
" len = (ext[0] << 8) | ext[1]\n"
" } else if (len == 127) {\n"
" var ext = readBytes_(8)\n"
" if (ext == null || ext.count < 8) return null\n"
" len = 0\n"
" for (i in 4...8) {\n"
" len = (len << 8) | ext[i]\n"
" }\n"
" }\n"
"\n"
" var mask = null\n"
" if (masked) {\n"
" mask = readBytes_(4)\n"
" if (mask == null || mask.count < 4) return null\n"
" }\n"
"\n"
" var payload = []\n"
" if (len > 0) {\n"
" payload = readBytes_(len)\n"
" if (payload == null) return null\n"
" }\n"
"\n"
" if (masked && mask != null) {\n"
" for (i in 0...payload.count) {\n"
" payload[i] = payload[i] ^ mask[i % 4]\n"
" }\n"
" }\n"
"\n"
" return WebSocketMessage.new_(opcode, payload, fin)\n"
" }\n"
"\n"
" readBytes_(count) {\n"
" while (_readBuffer.count < count) {\n"
" var chunk = _socket.read()\n"
" if (chunk == null || chunk.count == 0) {\n"
" if (_readBuffer.count == 0) return null\n"
" var result = []\n"
" for (b in _readBuffer) result.add(b)\n"
" _readBuffer = []\n"
" return result\n"
" }\n"
" for (b in chunk.bytes) {\n"
" _readBuffer.add(b)\n"
" }\n"
" }\n"
" var result = []\n"
" for (i in 0...count) {\n"
" result.add(_readBuffer[i])\n"
" }\n"
" var remaining = []\n"
" for (i in count..._readBuffer.count) {\n"
" remaining.add(_readBuffer[i])\n"
" }\n"
" _readBuffer = remaining\n"
" return result\n"
" }\n"
"\n"
" stringToBytes_(str) {\n"
" var bytes = []\n"
" for (b in str.bytes) {\n"
" bytes.add(b)\n"
" }\n"
" return bytes\n"
" }\n"
"}\n"
"\n"
"class WebSocketServer {\n"
" construct new_(server) {\n"
" _server = server\n"
" }\n"
"\n"
" static bind(host, port) {\n"
" var server = Server.bind(host, port)\n"
" return WebSocketServer.new_(server)\n"
" }\n"
"\n"
" accept() {\n"
" var socket = _server.accept()\n"
" return upgradeConnection_(socket)\n"
" }\n"
"\n"
" upgradeConnection_(socket) {\n"
" var request = \"\"\n"
" while (true) {\n"
" var chunk = socket.read()\n"
" if (chunk == null || chunk.count == 0) {\n"
" socket.close()\n"
" return null\n"
" }\n"
" request = request + chunk\n"
" if (request.indexOf(\"\\r\\n\\r\\n\") >= 0) break\n"
" }\n"
"\n"
" var headerEnd = request.indexOf(\"\\r\\n\\r\\n\")\n"
" var headerPart = request[0...headerEnd]\n"
" var lines = WebSocket.splitLines_(headerPart)\n"
"\n"
" if (lines.count == 0) {\n"
" socket.close()\n"
" return null\n"
" }\n"
"\n"
" var key = null\n"
" var isUpgrade = false\n"
" var isWebSocket = false\n"
"\n"
" for (i in 1...lines.count) {\n"
" var line = lines[i]\n"
" var colonPos = line.indexOf(\":\")\n"
" if (colonPos > 0) {\n"
" var name = WebSocket.toLower_(line[0...colonPos].trim())\n"
" var value = line[(colonPos + 1)..-1].trim()\n"
"\n"
" if (name == \"sec-websocket-key\") {\n"
" key = value\n"
" } else if (name == \"upgrade\" && WebSocket.toLower_(value) == \"websocket\") {\n"
" isWebSocket = true\n"
" } else if (name == \"connection\" && WebSocket.toLower_(value).contains(\"upgrade\")) {\n"
" isUpgrade = true\n"
" }\n"
" }\n"
" }\n"
"\n"
" if (!isUpgrade || !isWebSocket || key == null) {\n"
" var response = \"HTTP/1.1 400 Bad Request\\r\\n\\r\\n\"\n"
" socket.write(response)\n"
" socket.close()\n"
" return null\n"
" }\n"
"\n"
" var acceptKey = WebSocket.computeAcceptKey_(key)\n"
"\n"
" var response = \"HTTP/1.1 101 Switching Protocols\\r\\n\"\n"
" response = response + \"Upgrade: websocket\\r\\n\"\n"
" response = response + \"Connection: Upgrade\\r\\n\"\n"
" response = response + \"Sec-WebSocket-Accept: %(acceptKey)\\r\\n\"\n"
" response = response + \"\\r\\n\"\n"
"\n"
" socket.write(response)\n"
"\n"
" return WebSocket.new_(socket, null, false)\n"
" }\n"
"\n"
" close() {\n"
" _server.close()\n"
" }\n"
"}\n";

18
test/base64/base64.wren vendored Normal file
View File

@ -0,0 +1,18 @@
import "base64" for Base64
System.print(Base64.encode("Hello")) // expect: SGVsbG8=
System.print(Base64.encode("Hello, World!")) // expect: SGVsbG8sIFdvcmxkIQ==
System.print(Base64.encode("")) // expect:
System.print(Base64.encode("a")) // expect: YQ==
System.print(Base64.encode("ab")) // expect: YWI=
System.print(Base64.encode("abc")) // expect: YWJj
System.print(Base64.decode("SGVsbG8=")) // expect: Hello
System.print(Base64.decode("SGVsbG8sIFdvcmxkIQ==")) // expect: Hello, World!
System.print(Base64.decode("")) // expect:
System.print(Base64.decode("YQ==")) // expect: a
System.print(Base64.decode("YWI=")) // expect: ab
System.print(Base64.decode("YWJj")) // expect: abc
System.print(Base64.encodeUrl("Hello+World/Test")) // expect: SGVsbG8rV29ybGQvVGVzdA
System.print(Base64.decode(Base64.encode("Test roundtrip"))) // expect: Test roundtrip

19
test/crypto/crypto.wren vendored Normal file
View File

@ -0,0 +1,19 @@
// retoor <retoor@molodetz.nl>
import "crypto" for Crypto, Hash
var bytes = Crypto.randomBytes(16)
System.print(bytes is List) // expect: true
System.print(bytes.count) // expect: 16
var md5 = Hash.md5("hello")
System.print(Hash.toHex(md5)) // expect: 5d41402abc4b2a76b9719d911017c592
var sha1 = Hash.sha1("hello")
System.print(Hash.toHex(sha1)) // expect: aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d
var sha256 = Hash.sha256("hello")
System.print(Hash.toHex(sha256)) // expect: 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
var emptyMd5 = Hash.md5("")
System.print(Hash.toHex(emptyMd5)) // expect: d41d8cd98f00b204e9800998ecf8427e

28
test/datetime/datetime.wren vendored Normal file
View File

@ -0,0 +1,28 @@
import "datetime" for DateTime, Duration
var now = DateTime.now()
System.print(now.year >= 2024) // expect: true
System.print(now.month >= 1 && now.month <= 12) // expect: true
System.print(now.day >= 1 && now.day <= 31) // expect: true
System.print(now.hour >= 0 && now.hour <= 23) // expect: true
System.print(now.minute >= 0 && now.minute <= 59) // expect: true
System.print(now.second >= 0 && now.second <= 59) // expect: true
var ts = DateTime.fromTimestamp(0)
System.print(ts.year) // expect: 1970
var d1 = Duration.fromSeconds(60)
System.print(d1.seconds) // expect: 60
System.print(d1.minutes) // expect: 1
var d2 = Duration.fromMinutes(2)
System.print(d2.seconds) // expect: 120
var d3 = Duration.fromHours(1)
System.print(d3.minutes) // expect: 60
var d4 = Duration.fromDays(1)
System.print(d4.hours) // expect: 24
var sum = d1 + d2
System.print(sum.seconds) // expect: 180

8
test/dns/dns.wren vendored Normal file
View File

@ -0,0 +1,8 @@
import "dns" for Dns
var addresses = Dns.lookup("localhost")
System.print(addresses is List) // expect: true
System.print(addresses.count > 0) // expect: true
var first = addresses[0]
System.print(first == "127.0.0.1" || first == "::1") // expect: true

19
test/env/environment.wren vendored Normal file
View File

@ -0,0 +1,19 @@
import "env" for Environment
Environment.set("WREN_TEST_VAR", "hello")
System.print(Environment.get("WREN_TEST_VAR")) // expect: hello
Environment.set("WREN_TEST_VAR", "world")
System.print(Environment.get("WREN_TEST_VAR")) // expect: world
Environment.delete("WREN_TEST_VAR")
System.print(Environment.get("WREN_TEST_VAR") == null) // expect: true
System.print(Environment.get("THIS_VAR_DOES_NOT_EXIST") == null) // expect: true
var home = Environment.get("HOME")
System.print(home != null) // expect: true
var all = Environment.all
System.print(all is Map) // expect: true
System.print(all.count > 0) // expect: true

19
test/http/http.wren vendored Normal file
View File

@ -0,0 +1,19 @@
// retoor <retoor@molodetz.nl>
import "http" for Url, Http, HttpResponse
var url1 = Url.parse("http://example.com/path")
System.print(url1.scheme) // expect: http
System.print(url1.host) // expect: example.com
System.print(url1.port) // expect: 80
System.print(url1.path) // expect: /path
var url2 = Url.parse("http://example.com:8080/test?foo=bar")
System.print(url2.port) // expect: 8080
System.print(url2.path) // expect: /test
System.print(url2.query) // expect: foo=bar
System.print(url2.fullPath) // expect: /test?foo=bar
var url3 = Url.parse("http://localhost/")
System.print(url3.host) // expect: localhost
System.print(url3.path) // expect: /

13
test/jinja/comments.wren vendored Normal file
View File

@ -0,0 +1,13 @@
// retoor <retoor@molodetz.nl>
import "jinja" for Environment, DictLoader
var env = Environment.new(DictLoader.new({
"simple": "before{# comment #}after",
"multiword": "a{# this is a comment #}b",
"with_tags": "x{# {{ ignored }} #}y"
}))
System.print(env.getTemplate("simple").render({})) // expect: beforeafter
System.print(env.getTemplate("multiword").render({})) // expect: ab
System.print(env.getTemplate("with_tags").render({})) // expect: xy

32
test/jinja/conditionals.wren vendored Normal file
View File

@ -0,0 +1,32 @@
// retoor <retoor@molodetz.nl>
import "jinja" for Environment, DictLoader
var env = Environment.new(DictLoader.new({
"if_true": "{\% if show \%}visible{\% endif \%}",
"if_false": "{\% if hide \%}visible{\% endif \%}",
"if_else": "{\% if show \%}yes{\% else \%}no{\% endif \%}",
"if_elif": "{\% if x == 1 \%}one{\% elif x == 2 \%}two{\% else \%}other{\% endif \%}",
"compare": "{\% if a > b \%}greater{\% elif a < b \%}less{\% else \%}equal{\% endif \%}",
"and_or": "{\% if a and b \%}both{\% elif a or b \%}one{\% else \%}none{\% endif \%}",
"not": "{\% if not hidden \%}shown{\% endif \%}",
"in_list": "{\% if item in items \%}found{\% else \%}missing{\% endif \%}",
"is_even": "{\% if num is even \%}even{\% else \%}odd{\% endif \%}",
"is_defined": "{\% if var is defined \%}defined{\% else \%}undefined{\% endif \%}",
"is_not": "{\% if num is not odd \%}not odd{\% endif \%}",
"ternary": "{{ \"yes\" if show else \"no\" }}"
}))
System.print(env.getTemplate("if_true").render({"show": true})) // expect: visible
System.print(env.getTemplate("if_false").render({"hide": false})) // expect:
System.print(env.getTemplate("if_else").render({"show": false})) // expect: no
System.print(env.getTemplate("if_elif").render({"x": 2})) // expect: two
System.print(env.getTemplate("compare").render({"a": 5, "b": 3})) // expect: greater
System.print(env.getTemplate("and_or").render({"a": true, "b": false})) // expect: one
System.print(env.getTemplate("not").render({"hidden": false})) // expect: shown
System.print(env.getTemplate("in_list").render({"item": "b", "items": ["a", "b", "c"]})) // expect: found
System.print(env.getTemplate("is_even").render({"num": 4})) // expect: even
System.print(env.getTemplate("is_defined").render({"var": "something"})) // expect: defined
System.print(env.getTemplate("is_defined").render({})) // expect: undefined
System.print(env.getTemplate("is_not").render({"num": 4})) // expect: not odd
System.print(env.getTemplate("ternary").render({"show": true})) // expect: yes

35
test/jinja/expressions.wren vendored Normal file
View File

@ -0,0 +1,35 @@
// retoor <retoor@molodetz.nl>
import "jinja" for Environment, DictLoader
var env = Environment.new(DictLoader.new({
"math": "{{ 2 + 3 * 4 }}",
"paren": "{{ (2 + 3) * 4 }}",
"mod": "{{ 10 \% 3 }}",
"div": "{{ 10 / 4 }}",
"neg": "{{ -5 }}",
"compare_chain": "{\% if 1 < 2 and 2 < 3 \%}yes{\% endif \%}",
"bool_and": "{{ true and false }}",
"bool_or": "{{ true or false }}",
"bool_not": "{{ not false }}",
"string_in": "{\% if \"b\" in \"abc\" \%}found{\% endif \%}",
"list_in": "{\% if 2 in [1, 2, 3] \%}found{\% endif \%}",
"nested_access": "{{ data.items[0].name }}",
"filter_chain": "{{ text|trim|upper }}",
"method_call": "{{ items|join(\", \")|upper }}"
}))
System.print(env.getTemplate("math").render({})) // expect: 14
System.print(env.getTemplate("paren").render({})) // expect: 20
System.print(env.getTemplate("mod").render({})) // expect: 1
System.print(env.getTemplate("div").render({})) // expect: 2.5
System.print(env.getTemplate("neg").render({})) // expect: -5
System.print(env.getTemplate("compare_chain").render({})) // expect: yes
System.print(env.getTemplate("bool_and").render({})) // expect: false
System.print(env.getTemplate("bool_or").render({})) // expect: true
System.print(env.getTemplate("bool_not").render({})) // expect: true
System.print(env.getTemplate("string_in").render({})) // expect: found
System.print(env.getTemplate("list_in").render({})) // expect: found
System.print(env.getTemplate("nested_access").render({"data": {"items": [{"name": "test"}]}})) // expect: test
System.print(env.getTemplate("filter_chain").render({"text": " hello "})) // expect: HELLO
System.print(env.getTemplate("method_call").render({"items": ["a", "b", "c"]})) // expect: A, B, C

63
test/jinja/filters.wren vendored Normal file
View File

@ -0,0 +1,63 @@
// retoor <retoor@molodetz.nl>
import "jinja" for Environment, DictLoader
var env = Environment.new(DictLoader.new({
"upper": "{{ text|upper }}",
"lower": "{{ text|lower }}",
"capitalize": "{{ text|capitalize }}",
"title": "{{ text|title }}",
"trim": "[{{ text|trim }}]",
"length": "{{ items|length }}",
"first": "{{ items|first }}",
"last": "{{ items|last }}",
"reverse": "{{ items|reverse|join }}",
"sort": "{{ items|sort|join(\", \") }}",
"join": "{{ items|join(\"-\") }}",
"default": "{{ missing|default(\"N/A\") }}",
"int": "{{ num|int }}",
"abs": "{{ num|abs }}",
"round": "{{ num|round(2) }}",
"replace": "{{ text|replace(\"o\", \"0\") }}",
"truncate": "{{ text|truncate(10) }}",
"batch": "{\% for row in items|batch(2) \%}[{\% for i in row \%}{{ i }}{\% endfor \%}]{\% endfor \%}",
"unique": "{{ items|unique|join }}",
"sum": "{{ nums|sum }}",
"min": "{{ nums|min }}",
"max": "{{ nums|max }}",
"striptags": "{{ html|striptags }}",
"urlencode": "{{ text|urlencode }}",
"wordcount": "{{ text|wordcount }}",
"center": "[{{ text|center(10) }}]",
"indent": "{{ text|indent(4) }}",
"tojson": "{{ data|tojson }}"
}))
System.print(env.getTemplate("upper").render({"text": "hello"})) // expect: HELLO
System.print(env.getTemplate("lower").render({"text": "HELLO"})) // expect: hello
System.print(env.getTemplate("capitalize").render({"text": "hello world"})) // expect: Hello world
System.print(env.getTemplate("title").render({"text": "hello world"})) // expect: Hello World
System.print(env.getTemplate("trim").render({"text": " hi "})) // expect: [hi]
System.print(env.getTemplate("length").render({"items": [1, 2, 3]})) // expect: 3
System.print(env.getTemplate("first").render({"items": ["a", "b", "c"]})) // expect: a
System.print(env.getTemplate("last").render({"items": ["a", "b", "c"]})) // expect: c
System.print(env.getTemplate("reverse").render({"items": ["a", "b", "c"]})) // expect: cba
System.print(env.getTemplate("sort").render({"items": ["c", "a", "b"]})) // expect: a, b, c
System.print(env.getTemplate("join").render({"items": ["a", "b", "c"]})) // expect: a-b-c
System.print(env.getTemplate("default").render({})) // expect: N/A
System.print(env.getTemplate("int").render({"num": 3.7})) // expect: 3
System.print(env.getTemplate("abs").render({"num": -5})) // expect: 5
System.print(env.getTemplate("round").render({"num": 3.14159})) // expect: 3.14
System.print(env.getTemplate("replace").render({"text": "hello"})) // expect: hell0
System.print(env.getTemplate("truncate").render({"text": "hello world this is long"})) // expect: hello...
System.print(env.getTemplate("batch").render({"items": [1, 2, 3, 4]})) // expect: [12][34]
System.print(env.getTemplate("unique").render({"items": ["a", "b", "a", "c"]})) // expect: abc
System.print(env.getTemplate("sum").render({"nums": [1, 2, 3, 4]})) // expect: 10
System.print(env.getTemplate("min").render({"nums": [3, 1, 4, 1, 5]})) // expect: 1
System.print(env.getTemplate("max").render({"nums": [3, 1, 4, 1, 5]})) // expect: 5
System.print(env.getTemplate("striptags").render({"html": "<p>text</p>"})) // expect: text
System.print(env.getTemplate("urlencode").render({"text": "hello world"})) // expect: hello+world
System.print(env.getTemplate("wordcount").render({"text": "one two three"})) // expect: 3
System.print(env.getTemplate("center").render({"text": "hi"})) // expect: [ hi ]
System.print(env.getTemplate("indent").render({"text": "line"})) // expect: line
System.print(env.getTemplate("tojson").render({"data": {"a": 1}})) // expect: {"a": 1}

Some files were not shown because too many files have changed in this diff Show More