From 5babd9f64f8afe60add249a1dda19460e8e20385 Mon Sep 17 00:00:00 2001 From: Qing Date: Tue, 11 Jul 2023 11:08:11 +0800 Subject: [PATCH] =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sql/hellogithub.sql | 60 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 10 deletions(-) diff --git a/sql/hellogithub.sql b/sql/hellogithub.sql index c345759..20f82d9 100644 --- a/sql/hellogithub.sql +++ b/sql/hellogithub.sql @@ -11,7 +11,7 @@ Target Server Version : 80031 (8.0.31) File Encoding : 65001 - Date: 10/07/2023 23:35:48 + Date: 11/07/2023 11:07:38 */ SET NAMES utf8mb4; @@ -33,7 +33,7 @@ CREATE TABLE `admin` ( -- ---------------------------- -- Records of admin -- ---------------------------- -INSERT INTO `admin` VALUES (1, 'admin', 'admin', 1, '2023-07-10 23:31:54'); +INSERT INTO `admin` VALUES (1, 'admin', 'admin', 1, '2023-07-11 10:54:46'); INSERT INTO `admin` VALUES (9, '666', '666', 0, '2023-07-10'); -- ---------------------------- @@ -56,8 +56,8 @@ CREATE TABLE `article` ( -- ---------------------------- -- Records of article -- ---------------------------- -INSERT INTO `article` VALUES (30, 13, '

优秀的导航项目,优质的导航站搜集整理:https://github.com/eryajf/awesome-navigation

nav: ? 发现导航

https://github.com/xjh22222228/nav

WebStackPage

https://github.com/WebStackPage/WebStackPage.github.io

hexo-theme-webstack 一个基于webstack的hexo主题。

https://github.com/HCLonely/hexo-theme-webstack

WebStack-Hugo 一键配置的纯静态网址导航网站

https://github.com/shenweiyan/WebStack-Hugo

typecho 导航主题Webstack

https://www.zmki.cn/5366.html

onenav: 书签管理系统

https://github.com/helloxz/onenav

geek-navigation: ❤️ 极客猿导航

https://github.com/geekape/geek-navigation

aNavigation:

https://github.com/Jackie1123/aNavigation

team-nav:

https://github.com/tuituidan/team-nav

daohang: 运维导航系统

https://github.com/zhuima/daohang

【来源:https://landaiqing.space/451.html,转载请注明】

', '2023-07-10', 1, 6, '优秀的导航项目,优质的导航站搜集整', 'https://pic.imgdb.cn/item/64107dd8ebf10e5d538b2f3b.jpg'); -INSERT INTO `article` VALUES (31, 12, '

推荐一款支持谷歌插件,微软插件的手机浏览器~

站长我使用的也是这款手机浏览器,还是比较好用的,界面简单优雅!

而且关键还支持Tampermonkey哦!!!

下载地址:

https://lemurbrowser.com/app/zh/

【来源:https://landaiqing.space/436.html,转载请注明】

', '2023-07-10', 1, 7, '推荐一个支持谷歌插件,微软插件的手机浏览器~', 'https://pic.imgdb.cn/item/640f214ef144a01007e4e317.jpg'); +INSERT INTO `article` VALUES (30, 13, '

优秀的导航项目,优质的导航站搜集整理:https://github.com/eryajf/awesome-navigation

nav: ? 发现导航

https://github.com/xjh22222228/nav

WebStackPage

https://github.com/WebStackPage/WebStackPage.github.io

hexo-theme-webstack 一个基于webstack的hexo主题。

https://github.com/HCLonely/hexo-theme-webstack

WebStack-Hugo 一键配置的纯静态网址导航网站

https://github.com/shenweiyan/WebStack-Hugo

typecho 导航主题Webstack

https://www.zmki.cn/5366.html

onenav: 书签管理系统

https://github.com/helloxz/onenav

geek-navigation: ❤️ 极客猿导航

https://github.com/geekape/geek-navigation

aNavigation:

https://github.com/Jackie1123/aNavigation

team-nav:

https://github.com/tuituidan/team-nav

daohang: 运维导航系统

https://github.com/zhuima/daohang

【来源:https://landaiqing.space/451.html,转载请注明】

', '2023-07-10', 1, 10, '优秀的导航项目,优质的导航站搜集整', 'https://pic.imgdb.cn/item/64107dd8ebf10e5d538b2f3b.jpg'); +INSERT INTO `article` VALUES (31, 12, '

推荐一款支持谷歌插件,微软插件的手机浏览器~

站长我使用的也是这款手机浏览器,还是比较好用的,界面简单优雅!

而且关键还支持Tampermonkey哦!!!

下载地址:

https://lemurbrowser.com/app/zh/

【来源:https://landaiqing.space/436.html,转载请注明】

', '2023-07-10', 1, 8, '推荐一个支持谷歌插件,微软插件的手机浏览器~', 'https://pic.imgdb.cn/item/640f214ef144a01007e4e317.jpg'); -- ---------------------------- -- Table structure for category @@ -116,7 +116,7 @@ CREATE TABLE `comment` ( -- ---------------------------- -- Records of comment -- ---------------------------- -INSERT INTO `comment` VALUES (49, 13, 26, '可以,666', 0, '2023-07-10 20:31:59', 1, 5, '2023-07-10 20:31:59', 0); +INSERT INTO `comment` VALUES (49, 13, 26, '可以', 0, '2023-07-10 20:31:59', 1, 5, '2023-07-10 20:31:59', 0); -- ---------------------------- -- Table structure for githubinfo @@ -141,7 +141,7 @@ CREATE TABLE `githubinfo` ( `isVaild` int NULL DEFAULT NULL COMMENT '是否有效', PRIMARY KEY (`id`) USING BTREE, INDEX `projectId_info`(`projectId` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 27 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 28 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of githubinfo @@ -150,6 +150,46 @@ INSERT INTO `githubinfo` VALUES (23, 27, 'https://avatars.githubusercontent.com/ INSERT INTO `githubinfo` VALUES (24, 26, 'https://avatars.githubusercontent.com/u/3266363?v=4', 'awesome-macos-screensavers', 'https://starchart.cc/agarrharr/awesome-macos-screensavers.svg', 3180, '无', 'true', 80, 4, 'User', 'master', 129, '无', '0', 0); INSERT INTO `githubinfo` VALUES (25, 42, 'https://avatars.githubusercontent.com/u/65466868?v=4', 'devpod', 'https://starchart.cc/loft-sh/devpod.svg', 3233, 'Go', 'true', 21, 69, 'Organization', 'main', 137, 'MPL-2.0', '0', 0); INSERT INTO `githubinfo` VALUES (26, 43, 'https://avatars.githubusercontent.com/u/69631?v=4', 'redex', 'https://starchart.cc/facebook/redex.svg', 5854, 'C++', 'true', 229, 70, 'Organization', 'main', 671, 'MIT', '0', 0); +INSERT INTO `githubinfo` VALUES (27, 32, 'https://avatars.githubusercontent.com/u/65466868?v=4', 'devpod', 'https://starchart.cc/loft-sh/devpod.svg', 3239, 'Go', 'true', 21, 69, 'Organization', 'main', 137, 'MPL-2.0', '0', 0); + +-- ---------------------------- +-- Table structure for onefile +-- ---------------------------- +DROP TABLE IF EXISTS `onefile`; +CREATE TABLE `onefile` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT 'ID', + `oneFile_Name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '名称', + `language` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '语言', + `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '描述', + `userId` int NULL DEFAULT NULL COMMENT '作者ID', + `url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '访问地址', + `lookNum` int NULL DEFAULT NULL COMMENT '观看数', + `Content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '内容', + `isValid` int NULL DEFAULT NULL COMMENT '是否有效', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 19 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of onefile +-- ---------------------------- +INSERT INTO `onefile` VALUES (1, 'tinyhttpd.c', 'C', '不到 500 行的超轻量型 HTTP Server,可以用来理解服务器程序的原理和本质', 12, 'https://github.com/EZLippi/Tinyhttpd', 1111, '/* J. David\'s webserver */\r\n/* This is a simple webserver.\r\n * Created November 1999 by J. David Blackstone.\r\n * CSE 4344 (Network concepts), Prof. Zeigler\r\n * University of Texas at Arlington\r\n */\r\n/* This program compiles for Sparc Solaris 2.6.\r\n * To compile for Linux:\r\n * 1) Comment out the #include line.\r\n * 2) Comment out the line that defines the variable newthread.\r\n * 3) Comment out the two lines that run pthread_create().\r\n * 4) Uncomment the line that runs accept_request().\r\n * 5) Remove -lsocket from the Makefile.\r\n */\r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n\r\n#define ISspace(x) isspace((int)(x))\r\n\r\n#define SERVER_STRING \"Server: jdbhttpd/0.1.0\\r\\n\"\r\n#define STDIN 0\r\n#define STDOUT 1\r\n#define STDERR 2\r\n\r\nvoid accept_request(void *);\r\nvoid bad_request(int);\r\nvoid cat(int, FILE *);\r\nvoid cannot_execute(int);\r\nvoid error_die(const char *);\r\nvoid execute_cgi(int, const char *, const char *, const char *);\r\nint get_line(int, char *, int);\r\nvoid headers(int, const char *);\r\nvoid not_found(int);\r\nvoid serve_file(int, const char *);\r\nint startup(u_short *);\r\nvoid unimplemented(int);\r\n\r\n/**********************************************************************/\r\n/* A request has caused a call to accept() on the server port to\r\n * return. Process the request appropriately.\r\n * Parameters: the socket connected to the client */\r\n/**********************************************************************/\r\nvoid accept_request(void *arg)\r\n{\r\n int client = (intptr_t)arg;\r\n char buf[1024];\r\n size_t numchars;\r\n char method[255];\r\n char url[255];\r\n char path[512];\r\n size_t i, j;\r\n struct stat st;\r\n int cgi = 0; /* becomes true if server decides this is a CGI\r\n * program */\r\n char *query_string = NULL;\r\n\r\n numchars = get_line(client, buf, sizeof(buf));\r\n i = 0; j = 0;\r\n while (!ISspace(buf[i]) && (i < sizeof(method) - 1))\r\n {\r\n method[i] = buf[i];\r\n i++;\r\n }\r\n j=i;\r\n method[i] = \'\\0\';\r\n\r\n if (strcasecmp(method, \"GET\") && strcasecmp(method, \"POST\"))\r\n {\r\n unimplemented(client);\r\n return;\r\n }\r\n\r\n if (strcasecmp(method, \"POST\") == 0)\r\n cgi = 1;\r\n\r\n i = 0;\r\n while (ISspace(buf[j]) && (j < numchars))\r\n j++;\r\n while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < numchars))\r\n {\r\n url[i] = buf[j];\r\n i++; j++;\r\n }\r\n url[i] = \'\\0\';\r\n\r\n if (strcasecmp(method, \"GET\") == 0)\r\n {\r\n query_string = url;\r\n while ((*query_string != \'?\') && (*query_string != \'\\0\'))\r\n query_string++;\r\n if (*query_string == \'?\')\r\n {\r\n cgi = 1;\r\n *query_string = \'\\0\';\r\n query_string++;\r\n }\r\n }\r\n\r\n sprintf(path, \"htdocs%s\", url);\r\n if (path[strlen(path) - 1] == \'/\')\r\n strcat(path, \"index.html\");\r\n if (stat(path, &st) == -1) {\r\n while ((numchars > 0) && strcmp(\"\\n\", buf)) /* read & discard headers */\r\n numchars = get_line(client, buf, sizeof(buf));\r\n not_found(client);\r\n }\r\n else\r\n {\r\n if ((st.st_mode & S_IFMT) == S_IFDIR)\r\n strcat(path, \"/index.html\");\r\n if ((st.st_mode & S_IXUSR) ||\r\n (st.st_mode & S_IXGRP) ||\r\n (st.st_mode & S_IXOTH) )\r\n cgi = 1;\r\n if (!cgi)\r\n serve_file(client, path);\r\n else\r\n execute_cgi(client, path, method, query_string);\r\n }\r\n\r\n close(client);\r\n}\r\n\r\n/**********************************************************************/\r\n/* Inform the client that a request it has made has a problem.\r\n * Parameters: client socket */\r\n/**********************************************************************/\r\nvoid bad_request(int client)\r\n{\r\n char buf[1024];\r\n\r\n sprintf(buf, \"HTTP/1.0 400 BAD REQUEST\\r\\n\");\r\n send(client, buf, sizeof(buf), 0);\r\n sprintf(buf, \"Content-type: text/html\\r\\n\");\r\n send(client, buf, sizeof(buf), 0);\r\n sprintf(buf, \"\\r\\n\");\r\n send(client, buf, sizeof(buf), 0);\r\n sprintf(buf, \"

Your browser sent a bad request, \");\r\n send(client, buf, sizeof(buf), 0);\r\n sprintf(buf, \"such as a POST without a Content-Length.\\r\\n\");\r\n send(client, buf, sizeof(buf), 0);\r\n}\r\n\r\n/**********************************************************************/\r\n/* Put the entire contents of a file out on a socket. This function\r\n * is named after the UNIX \"cat\" command, because it might have been\r\n * easier just to do something like pipe, fork, and exec(\"cat\").\r\n * Parameters: the client socket descriptor\r\n * FILE pointer for the file to cat */\r\n/**********************************************************************/\r\nvoid cat(int client, FILE *resource)\r\n{\r\n char buf[1024];\r\n\r\n fgets(buf, sizeof(buf), resource);\r\n while (!feof(resource))\r\n {\r\n send(client, buf, strlen(buf), 0);\r\n fgets(buf, sizeof(buf), resource);\r\n }\r\n}\r\n\r\n/**********************************************************************/\r\n/* Inform the client that a CGI script could not be executed.\r\n * Parameter: the client socket descriptor. */\r\n/**********************************************************************/\r\nvoid cannot_execute(int client)\r\n{\r\n char buf[1024];\r\n\r\n sprintf(buf, \"HTTP/1.0 500 Internal Server Error\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, \"Content-type: text/html\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, \"\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, \"

Error prohibited CGI execution.\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n}\r\n\r\n/**********************************************************************/\r\n/* Print out an error message with perror() (for system errors; based\r\n * on value of errno, which indicates system call errors) and exit the\r\n * program indicating an error. */\r\n/**********************************************************************/\r\nvoid error_die(const char *sc)\r\n{\r\n perror(sc);\r\n exit(1);\r\n}\r\n\r\n/**********************************************************************/\r\n/* Execute a CGI script. Will need to set environment variables as\r\n * appropriate.\r\n * Parameters: client socket descriptor\r\n * path to the CGI script */\r\n/**********************************************************************/\r\nvoid execute_cgi(int client, const char *path,\r\n const char *method, const char *query_string)\r\n{\r\n char buf[1024];\r\n int cgi_output[2];\r\n int cgi_input[2];\r\n pid_t pid;\r\n int status;\r\n int i;\r\n char c;\r\n int numchars = 1;\r\n int content_length = -1;\r\n\r\n buf[0] = \'A\'; buf[1] = \'\\0\';\r\n if (strcasecmp(method, \"GET\") == 0)\r\n while ((numchars > 0) && strcmp(\"\\n\", buf)) /* read & discard headers */\r\n numchars = get_line(client, buf, sizeof(buf));\r\n else if (strcasecmp(method, \"POST\") == 0) /*POST*/\r\n {\r\n numchars = get_line(client, buf, sizeof(buf));\r\n while ((numchars > 0) && strcmp(\"\\n\", buf))\r\n {\r\n buf[15] = \'\\0\';\r\n if (strcasecmp(buf, \"Content-Length:\") == 0)\r\n content_length = atoi(&(buf[16]));\r\n numchars = get_line(client, buf, sizeof(buf));\r\n }\r\n if (content_length == -1) {\r\n bad_request(client);\r\n return;\r\n }\r\n }\r\n else/*HEAD or other*/\r\n {\r\n }\r\n\r\n\r\n if (pipe(cgi_output) < 0) {\r\n cannot_execute(client);\r\n return;\r\n }\r\n if (pipe(cgi_input) < 0) {\r\n cannot_execute(client);\r\n return;\r\n }\r\n\r\n if ( (pid = fork()) < 0 ) {\r\n cannot_execute(client);\r\n return;\r\n }\r\n sprintf(buf, \"HTTP/1.0 200 OK\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n if (pid == 0) /* child: CGI script */\r\n {\r\n char meth_env[255];\r\n char query_env[255];\r\n char length_env[255];\r\n\r\n dup2(cgi_output[1], STDOUT);\r\n dup2(cgi_input[0], STDIN);\r\n close(cgi_output[0]);\r\n close(cgi_input[1]);\r\n sprintf(meth_env, \"REQUEST_METHOD=%s\", method);\r\n putenv(meth_env);\r\n if (strcasecmp(method, \"GET\") == 0) {\r\n sprintf(query_env, \"QUERY_STRING=%s\", query_string);\r\n putenv(query_env);\r\n }\r\n else { /* POST */\r\n sprintf(length_env, \"CONTENT_LENGTH=%d\", content_length);\r\n putenv(length_env);\r\n }\r\n execl(path, NULL);\r\n exit(0);\r\n } else { /* parent */\r\n close(cgi_output[1]);\r\n close(cgi_input[0]);\r\n if (strcasecmp(method, \"POST\") == 0)\r\n for (i = 0; i < content_length; i++) {\r\n recv(client, &c, 1, 0);\r\n write(cgi_input[1], &c, 1);\r\n }\r\n while (read(cgi_output[0], &c, 1) > 0)\r\n send(client, &c, 1, 0);\r\n\r\n close(cgi_output[0]);\r\n close(cgi_input[1]);\r\n waitpid(pid, &status, 0);\r\n }\r\n}\r\n\r\n/**********************************************************************/\r\n/* Get a line from a socket, whether the line ends in a newline,\r\n * carriage return, or a CRLF combination. Terminates the string read\r\n * with a null character. If no newline indicator is found before the\r\n * end of the buffer, the string is terminated with a null. If any of\r\n * the above three line terminators is read, the last character of the\r\n * string will be a linefeed and the string will be terminated with a\r\n * null character.\r\n * Parameters: the socket descriptor\r\n * the buffer to save the data in\r\n * the size of the buffer\r\n * Returns: the number of bytes stored (excluding null) */\r\n/**********************************************************************/\r\nint get_line(int sock, char *buf, int size)\r\n{\r\n int i = 0;\r\n char c = \'\\0\';\r\n int n;\r\n\r\n while ((i < size - 1) && (c != \'\\n\'))\r\n {\r\n n = recv(sock, &c, 1, 0);\r\n /* DEBUG printf(\"%02X\\n\", c); */\r\n if (n > 0)\r\n {\r\n if (c == \'\\r\')\r\n {\r\n n = recv(sock, &c, 1, MSG_PEEK);\r\n /* DEBUG printf(\"%02X\\n\", c); */\r\n if ((n > 0) && (c == \'\\n\'))\r\n recv(sock, &c, 1, 0);\r\n else\r\n c = \'\\n\';\r\n }\r\n buf[i] = c;\r\n i++;\r\n }\r\n else\r\n c = \'\\n\';\r\n }\r\n buf[i] = \'\\0\';\r\n\r\n return(i);\r\n}\r\n\r\n/**********************************************************************/\r\n/* Return the informational HTTP headers about a file. */\r\n/* Parameters: the socket to print the headers on\r\n * the name of the file */\r\n/**********************************************************************/\r\nvoid headers(int client, const char *filename)\r\n{\r\n char buf[1024];\r\n (void)filename; /* could use filename to determine file type */\r\n\r\n strcpy(buf, \"HTTP/1.0 200 OK\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n strcpy(buf, SERVER_STRING);\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, \"Content-Type: text/html\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n strcpy(buf, \"\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n}\r\n\r\n/**********************************************************************/\r\n/* Give a client a 404 not found status message. */\r\n/**********************************************************************/\r\nvoid not_found(int client)\r\n{\r\n char buf[1024];\r\n\r\n sprintf(buf, \"HTTP/1.0 404 NOT FOUND\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, SERVER_STRING);\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, \"Content-Type: text/html\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, \"\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, \"Not Found\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, \"

The server could not fulfill\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, \"your request because the resource specified\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, \"is unavailable or nonexistent.\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, \"\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n}\r\n\r\n/**********************************************************************/\r\n/* Send a regular file to the client. Use headers, and report\r\n * errors to client if they occur.\r\n * Parameters: a pointer to a file structure produced from the socket\r\n * file descriptor\r\n * the name of the file to serve */\r\n/**********************************************************************/\r\nvoid serve_file(int client, const char *filename)\r\n{\r\n FILE *resource = NULL;\r\n int numchars = 1;\r\n char buf[1024];\r\n\r\n buf[0] = \'A\'; buf[1] = \'\\0\';\r\n while ((numchars > 0) && strcmp(\"\\n\", buf)) /* read & discard headers */\r\n numchars = get_line(client, buf, sizeof(buf));\r\n\r\n resource = fopen(filename, \"r\");\r\n if (resource == NULL)\r\n not_found(client);\r\n else\r\n {\r\n headers(client, filename);\r\n cat(client, resource);\r\n }\r\n fclose(resource);\r\n}\r\n\r\n/**********************************************************************/\r\n/* This function starts the process of listening for web connections\r\n * on a specified port. If the port is 0, then dynamically allocate a\r\n * port and modify the original port variable to reflect the actual\r\n * port.\r\n * Parameters: pointer to variable containing the port to connect on\r\n * Returns: the socket */\r\n/**********************************************************************/\r\nint startup(u_short *port)\r\n{\r\n int httpd = 0;\r\n int on = 1;\r\n struct sockaddr_in name;\r\n\r\n httpd = socket(PF_INET, SOCK_STREAM, 0);\r\n if (httpd == -1)\r\n error_die(\"socket\");\r\n memset(&name, 0, sizeof(name));\r\n name.sin_family = AF_INET;\r\n name.sin_port = htons(*port);\r\n name.sin_addr.s_addr = htonl(INADDR_ANY);\r\n if ((setsockopt(httpd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) < 0) \r\n { \r\n error_die(\"setsockopt failed\");\r\n }\r\n if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)\r\n error_die(\"bind\");\r\n if (*port == 0) /* if dynamically allocating a port */\r\n {\r\n socklen_t namelen = sizeof(name);\r\n if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)\r\n error_die(\"getsockname\");\r\n *port = ntohs(name.sin_port);\r\n }\r\n if (listen(httpd, 5) < 0)\r\n error_die(\"listen\");\r\n return(httpd);\r\n}\r\n\r\n/**********************************************************************/\r\n/* Inform the client that the requested web method has not been\r\n * implemented.\r\n * Parameter: the client socket */\r\n/**********************************************************************/\r\nvoid unimplemented(int client)\r\n{\r\n char buf[1024];\r\n\r\n sprintf(buf, \"HTTP/1.0 501 Method Not Implemented\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, SERVER_STRING);\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, \"Content-Type: text/html\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, \"\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, \"Method Not Implemented\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, \"\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, \"

HTTP request method not supported.\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, \"\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n}\r\n\r\n/**********************************************************************/\r\n\r\nint main(void)\r\n{\r\n int server_sock = -1;\r\n u_short port = 4000;\r\n int client_sock = -1;\r\n struct sockaddr_in client_name;\r\n socklen_t client_name_len = sizeof(client_name);\r\n pthread_t newthread;\r\n\r\n server_sock = startup(&port);\r\n printf(\"httpd running on port %d\\n\", port);\r\n\r\n while (1)\r\n {\r\n client_sock = accept(server_sock,\r\n (struct sockaddr *)&client_name,\r\n &client_name_len);\r\n if (client_sock == -1)\r\n error_die(\"accept\");\r\n /* accept_request(&client_sock); */\r\n if (pthread_create(&newthread , NULL, (void *)accept_request, (void *)(intptr_t)client_sock) != 0)\r\n perror(\"pthread_create\");\r\n }\r\n\r\n close(server_sock);\r\n\r\n return(0);\r\n}', 1); +INSERT INTO `onefile` VALUES (2, 'si78c', 'C', '用 C 语言实现的《太空侵略者》命令行游戏', 12, 'https://github.com/loadzero/si78c', 9500, '//\r\n// Space Invaders 1978 in C\r\n// Jason McSweeney\r\n\r\n#define _XOPEN_SOURCE\r\n\r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n\r\n#define OP_BLEND 0\r\n#define OP_ERASE 1\r\n#define OP_COLLIDE 2\r\n#define OP_BLIT 3\r\n\r\n#define SHOT_ACTIVE 0x80\r\n#define SHOT_BLOWUP 0x1\r\n\r\n#define BEAM_VBLANK 0x80\r\n#define BEAM_MIDDLE 0x00\r\n#define XR_MID 0x80\r\n\r\n#define DIP3_SHIPS1 0x1\r\n#define DIP5_SHIPS2 0x2\r\n#define DIP6_BONUS 0x8\r\n#define DIP7_COININFO 0x80\r\n\r\n#define TILT_BIT 0x4\r\n#define COIN_BIT 0x1\r\n\r\n#define INIT 1\r\n#define PROMPT 2\r\n#define SHIELDS 4\r\n\r\n#define ROL_SHOT_PICEND 0xf9\r\n#define PLU_SHOT_PICEND 0xed\r\n#define SQU_SHOT_PICEND 0xdb\r\n\r\n#define PLAYER_ADDR 0x2010\r\n#define PLAYER_SIZE 16\r\n#define PLAYER_SHOT_ADDR 0x2020\r\n#define PLAYER_SHOT_DATA_ADDR 0x2025\r\n#define PLAYER_SHOT_DATA_SIZE 7\r\n#define ROLLING_SHOT_ADDR 0x2030\r\n#define ROLLING_SHOT_SIZE 16\r\n#define PLUNGER_SHOT_ADDR 0x2040\r\n#define PLUNGER_SHOT_SIZE 16\r\n#define SQUIGGLY_SHOT_ADDR 0x2050\r\n#define SQUIGGLY_SHOT_SIZE 16\r\n#define SAUCER_ADDR 0x2083\r\n#define SAUCER_SIZE 10\r\n#define P1_ADDR 0x2100\r\n#define P2_ADDR 0x2200\r\n#define SPLASH_DESC_ADDR 0x20c5\r\n\r\n#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__\r\n#error \"This code is Little Endian only.\"\r\n#endif\r\n\r\nstruct Word\r\n{\r\n union {\r\n\r\n uint16_t u16;\r\n struct {\r\n uint8_t l;\r\n uint8_t h;\r\n };\r\n\r\n struct {\r\n uint8_t y;\r\n uint8_t x;\r\n };\r\n\r\n };\r\n} __attribute__ ((packed)); // 4\r\n\r\ntypedef struct Word Word;\r\n\r\nstruct SprDesc\r\n{\r\n Word spr;\r\n\r\n union {\r\n Word pos; // pixel position\r\n Word sc; // screen address\r\n };\r\n\r\n uint8_t n;\r\n\r\n} __attribute__ ((packed)); // 5\r\n\r\ntypedef struct SprDesc SprDesc;\r\n\r\nstruct GameObjHeader\r\n{\r\n uint8_t TimerMSB;\r\n uint8_t TimerLSB;\r\n uint8_t TimerExtra;\r\n Word Handler;\r\n\r\n} __attribute__ ((packed)); // 5\r\n\r\ntypedef struct GameObjHeader GameObjHeader;\r\n\r\nstruct AShot\r\n{\r\n uint8_t Status;\r\n uint8_t StepCnt;\r\n uint8_t Track;\r\n Word CFir;\r\n uint8_t BlowCnt;\r\n SprDesc Desc;\r\n\r\n} __attribute__ ((packed)); // 11\r\n\r\ntypedef struct AShot AShot;\r\n\r\nstruct Mem {\r\n\r\nuint8_t pad_01[3063]; // 0x0000\r\nuint8_t MSG_TAITO_COP[9]; // 0x0bf7\r\nuint8_t pad_02[3601]; // 0x0c00\r\nuint8_t soundDelayKey[16]; // 0x1a11\r\nuint8_t soundDelayValue[16]; // 0x1a21\r\nuint8_t pad_03[112]; // 0x1a31\r\nuint8_t ShotReloadRate[5]; // 0x1aa1\r\nuint8_t MSG_GAME_OVER__PLAYER___[20]; // 0x1aa6\r\nuint8_t MSG_1_OR_2PLAYERS_BUTTON[20]; // 0x1aba\r\nuint8_t pad_04; // 0x1ace\r\nuint8_t MSG_ONLY_1PLAYER__BUTTON[20]; // 0x1acf\r\nuint8_t pad_05; // 0x1ae3\r\nuint8_t MSG__SCORE_1__HI_SCORE_SCORE_2__[28]; // 0x1ae4\r\nuint8_t pad_06[112]; // 0x1b00\r\nuint8_t MSG_PLAY_PLAYER_1_[14]; // 0x1b70\r\nuint8_t pad_07[66]; // 0x1b7e\r\nuint8_t SPLASH_SHOT_OBJDATA[16]; // 0x1bc0\r\nuint8_t pad_08[144]; // 0x1bd0\r\nuint8_t PLAYER_SPRITES[48]; // 0x1c60\r\nuint8_t pad_09[9]; // 0x1c90\r\nuint8_t MSG__10_POINTS[10]; // 0x1c99\r\nuint8_t MSG__SCORE_ADVANCE_TABLE_[21]; // 0x1ca3\r\nuint8_t AReloadScoreTab[4]; // 0x1cb8\r\nuint8_t MSG_TILT[4]; // 0x1cbc\r\nuint8_t pad_10[28]; // 0x1cc0\r\nuint8_t AlienShotExplodingSprite[6]; // 0x1cdc\r\nuint8_t pad_11[24]; // 0x1ce2\r\nuint8_t MSG_PLAY2[5]; // 0x1cfa\r\nuint8_t pad_12[33]; // 0x1cff\r\nuint8_t SHIELD_SPRITE[44]; // 0x1d20\r\nuint8_t SauScrValueTab[4]; // 0x1d4c\r\nuint8_t SauScrStrTab[4]; // 0x1d50\r\nuint8_t pad_13[40]; // 0x1d54\r\nuint8_t SpriteSaucerExp[24]; // 0x1d7c\r\nuint8_t MSG__50[3]; // 0x1d94\r\nuint8_t MSG_100[3]; // 0x1d97\r\nuint8_t MSG_150[3]; // 0x1d9a\r\nuint8_t MSG_300[3]; // 0x1d9d\r\nuint8_t AlienScores[3]; // 0x1da0\r\nuint8_t AlienStartTable[8]; // 0x1da3\r\nuint8_t MSG_PLAY[4]; // 0x1dab\r\nuint8_t MSG_SPACE__INVADERS[15]; // 0x1daf\r\nuint8_t SCORE_ADV_SPRITE_LIST[17]; // 0x1dbe\r\nuint8_t SCORE_ADV_MSG_LIST[17]; // 0x1dcf\r\nuint8_t MSG____MYSTERY[10]; // 0x1de0\r\nuint8_t MSG__30_POINTS[10]; // 0x1dea\r\nuint8_t MSG__20_POINTS[10]; // 0x1df4\r\nuint8_t pad_14[338]; // 0x1dfe\r\nuint8_t MSG__1_OR_2_PLAYERS___[18]; // 0x1f50\r\nuint8_t pad_15[46]; // 0x1f62\r\nuint8_t MSG_INSERT__COIN[12]; // 0x1f90\r\nuint8_t CREDIT_TABLE[4]; // 0x1f9c\r\nuint8_t CREDIT_TABLE_COINS[9]; // 0x1fa0\r\nuint8_t MSG_CREDIT_[7]; // 0x1fa9\r\nuint8_t pad_16[67]; // 0x1fb0\r\nuint8_t MSG_PUSH[4]; // 0x1ff3\r\nuint8_t pad_17[9]; // 0x1ff7\r\n\r\n// start of ram mirror\r\n\r\nuint8_t waitOnDraw; // 0x2000\r\nuint8_t pad_18; // 0x2001\r\nuint8_t alienIsExploding; // 0x2002\r\nuint8_t expAlienTimer; // 0x2003\r\nuint8_t alienRow; // 0x2004\r\nuint8_t alienFrame; // 0x2005\r\nuint8_t alienCurIndex; // 0x2006\r\nWord refAlienDelta; // 0x2007\r\nWord refAlienPos; // 0x2009\r\nWord alienPos; // 0x200b\r\nuint8_t rackDirection; // 0x200d\r\nuint8_t rackDownDelta; // 0x200e\r\nuint8_t pad_19; // 0x200f\r\nGameObjHeader playerHeader; // 0x2010\r\nuint8_t playerAlive; // 0x2015\r\nuint8_t expAnimateTimer; // 0x2016\r\nuint8_t expAnimateCnt; // 0x2017\r\nSprDesc playerDesc; // 0x2018\r\nuint8_t nextDemoCmd; // 0x201d\r\nuint8_t hidMessSeq; // 0x201e\r\nuint8_t pad_20; // 0x201f\r\nGameObjHeader plyrShotHeader; // 0x2020\r\nuint8_t plyrShotStatus; // 0x2025\r\nuint8_t blowUpTimer; // 0x2026\r\nSprDesc playerShotDesc; // 0x2027\r\nuint8_t shotDeltaYr; // 0x202c\r\nuint8_t fireBounce; // 0x202d\r\nuint8_t pad_21[2]; // 0x202e\r\nGameObjHeader rolShotHeader; // 0x2030\r\nAShot rolShotData; // 0x2035\r\nGameObjHeader pluShotHeader; // 0x2040\r\nAShot pluShotData; // 0x2045\r\nGameObjHeader squShotHeader; // 0x2050\r\nAShot squShotData; // 0x2055\r\nuint8_t pad_22; // 0x2060\r\nuint8_t collision; // 0x2061\r\nSprDesc expAlien; // 0x2062\r\nuint8_t playerDataMSB; // 0x2067\r\nuint8_t playerOK; // 0x2068\r\nuint8_t enableAlienFire; // 0x2069\r\nuint8_t alienFireDelay; // 0x206a\r\nuint8_t pad_23; // 0x206b\r\nuint8_t temp206C; // 0x206c\r\nuint8_t invaded; // 0x206d\r\nuint8_t skipPlunger; // 0x206e\r\nuint8_t pad_24; // 0x206f\r\nuint8_t otherShot1; // 0x2070\r\nuint8_t otherShot2; // 0x2071\r\nuint8_t vblankStatus; // 0x2072\r\nAShot aShot; // 0x2073\r\nuint8_t alienShotDelta; // 0x207e\r\nuint8_t shotPicEnd; // 0x207f\r\nuint8_t shotSync; // 0x2080\r\nuint8_t tmp2081; // 0x2081\r\nuint8_t numAliens; // 0x2082\r\nuint8_t saucerStart; // 0x2083\r\nuint8_t saucerActive; // 0x2084\r\nuint8_t saucerHit; // 0x2085\r\nuint8_t saucerHitTime; // 0x2086\r\nSprDesc saucerDesc; // 0x2087\r\nuint8_t saucerDXr; // 0x208c\r\nWord sauScore; // 0x208d\r\nWord shotCount; // 0x208f\r\nWord saucerTimer; // 0x2091\r\nuint8_t waitStartLoop; // 0x2093\r\nuint8_t soundPort3; // 0x2094\r\nuint8_t changeFleetSnd; // 0x2095\r\nuint8_t fleetSndCnt; // 0x2096\r\nuint8_t fleetSndReload; // 0x2097\r\nuint8_t soundPort5; // 0x2098\r\nuint8_t extraHold; // 0x2099\r\nuint8_t tilt; // 0x209a\r\nuint8_t fleetSndHold; // 0x209b\r\nuint8_t pad_25[36]; // 0x209c\r\n\r\n// end of partial ram restore at 0x20c0\r\n\r\nuint8_t isrDelay; // 0x20c0\r\nuint8_t isrSplashTask; // 0x20c1\r\nuint8_t splashAnForm; // 0x20c2\r\nWord splashDelta; // 0x20c3\r\nWord splashPos; // 0x20c5\r\nWord splashPic; // 0x20c7\r\nuint8_t splashPicSize; // 0x20c9\r\nuint8_t splashTargetX; // 0x20ca\r\nuint8_t splashReached; // 0x20cb\r\nWord splashImRest; // 0x20cc\r\nuint8_t twoPlayers; // 0x20ce\r\nuint8_t aShotReloadRate; // 0x20cf\r\nuint8_t pad_26[21]; // 0x20d0\r\nWord playerExtras; // 0x20e5\r\nWord playerStates; // 0x20e7\r\nuint8_t gameTasksRunning; // 0x20e9\r\nuint8_t coinSwitch; // 0x20ea\r\nuint8_t numCoins; // 0x20eb\r\nuint8_t splashAnimate; // 0x20ec\r\nWord demoCmdPtr; // 0x20ed\r\nuint8_t gameMode; // 0x20ef\r\nuint8_t pad_27; // 0x20f0\r\nuint8_t adjustScore; // 0x20f1\r\nWord scoreDelta; // 0x20f2\r\nWord HiScor; // 0x20f4\r\nuint8_t pad_28[2]; // 0x20f6\r\nWord P1Scor; // 0x20f8\r\nuint8_t pad_29[2]; // 0x20fa\r\nWord P2Scor; // 0x20fc\r\nuint8_t pad_30[68]; // 0x20fe\r\n\r\n// end of ram mirror at 0x2100\r\n\r\nuint8_t p1ShieldBuffer[176]; // 0x2142\r\nuint8_t pad_31[9]; // 0x21f2\r\nuint8_t p1RefAlienDX; // 0x21fb\r\nWord p1RefAlienPos; // 0x21fc\r\nuint8_t p1RackCnt; // 0x21fe\r\nuint8_t p1ShipsRem; // 0x21ff\r\nuint8_t pad_32[66]; // 0x2200\r\nuint8_t p2ShieldBuffer[176]; // 0x2242\r\nuint8_t pad_33[9]; // 0x22f2\r\nuint8_t p2RefAlienDX; // 0x22fb\r\nWord p2RefAlienPos; // 0x22fc\r\nuint8_t p2RackCnt; // 0x22fe\r\nuint8_t p2ShipsRem; // 0x22ff\r\nuint8_t pad_34[256]; // 0x2300\r\nuint8_t vram[7168]; // 0x2400\r\n\r\n// Technically the region below is supposed to be a mirror, but AFAICT,\r\n// that property is not used by the SI code.\r\n//\r\n// The \'PLAy\' animation does do some oob writes during DrawSprite,\r\n// which effectively do nothing because they end up trying to write to ROM\r\n//\r\n// So, as a catchall, we just reserve this area up to the end of the address space.\r\n\r\nuint8_t oob[49152]; // 0x4000\r\n\r\n} __attribute ((packed));\r\n\r\ntypedef struct Mem Mem;\r\n\r\ntypedef struct PriCursor\r\n{\r\n uint8_t* src;\r\n Word sc;\r\n uint8_t* obj;\r\n} PriCursor;\r\n\r\ntypedef struct ShieldBufferCursor\r\n{\r\n Word sc;\r\n uint8_t* iter;\r\n} ShieldBufferCursor;\r\n\r\ntypedef enum YieldReason\r\n{\r\n YIELD_INIT = 0,\r\n YIELD_TIMESLICE,\r\n YIELD_INTFIN,\r\n YIELD_WAIT_FOR_START,\r\n YIELD_PLAYER_DEATH,\r\n YIELD_INVADED,\r\n YIELD_TILT,\r\n YIELD_UNKNOWN,\r\n} YieldReason;\r\n\r\nenum Keys {\r\n KEYS_LEFT = 1,\r\n KEYS_RIGHT = 2,\r\n KEYS_START = 4,\r\n KEYS_START2 = 8,\r\n KEYS_FIRE = 16,\r\n KEYS_COIN = 32,\r\n KEYS_TILT = 64,\r\n KEYS_DIP6 = 128,\r\n KEYS_DIP7 = 256,\r\n KEYS_SPECIAL1 = 512,\r\n KEYS_SPECIAL2 = 1024,\r\n KEYS_QUIT = 2048\r\n};\r\n\r\n#define KEY_LIST \\\r\n KEY_MAP(\'a\', KEYS_LEFT); \\\r\n KEY_MAP(\'d\', KEYS_RIGHT); \\\r\n KEY_MAP(\'1\', KEYS_START); \\\r\n KEY_MAP(\'2\', KEYS_START2); \\\r\n KEY_MAP(\'j\', KEYS_FIRE); \\\r\n KEY_MAP(\'5\', KEYS_COIN); \\\r\n KEY_MAP(\'t\', KEYS_TILT); \\\r\n KEY_MAP(\'6\', KEYS_DIP6); \\\r\n KEY_MAP(\'7\', KEYS_DIP7); \\\r\n KEY_MAP(\'z\', KEYS_SPECIAL1); \\\r\n KEY_MAP(\'x\', KEYS_SPECIAL2); \\\r\n KEY_MAP(SDLK_ESCAPE, KEYS_QUIT);\r\n\r\n#define TRUE 1\r\n#define FALSE 0\r\n\r\nstatic void do_logprintf(const char *file, unsigned line, const char* format, ...);\r\n#define logprintf(...) { do_logprintf(__FILE__, __LINE__, __VA_ARGS__); }\r\n\r\n#include \"si78c_proto.h\"\r\n\r\n// Coordinate Systems\r\n// ------------------\r\n//\r\n// Natural Units\r\n// -------------\r\n//\r\n// For readability, this codebase uses the following coordinate system\r\n// where possible.\r\n//\r\n// The origin is at the bottom left corner.\r\n//\r\n// X goes +ve towards the rhs of the screen.\r\n// Y goes +ve towards the top of the screen.\r\n//\r\n// (0,256)\r\n// ^\r\n// |\r\n// y |\r\n// |\r\n// |----------> (224,0)\r\n// (0,0) x\r\n//\r\n// Game Units\r\n// -------------\r\n//\r\n// The game uses two different coordinate systems, both of which\r\n// fit into a 16-bit word, and come with an offset.\r\n//\r\n// pix - Pixel positions between 0x2000 and 0xffff\r\n// sc - Screen RAM addresses between 0x2400 and 0x3fff\r\n//\r\n// pix coordinates are used to move and draw objects that require per pixel shifting,\r\n// like the aliens and the bullets.\r\n//\r\n// sc coordinates are used for drawing text and other simple sprites, and also when\r\n// blitting sprites after they have been shifted.\r\n//\r\n// The following macros are used to convert between natural units and game units.\r\n\r\n#define xysc(x, y) ((x)*32 + (y)/8)\r\n#define xytosc(x, y) u16_to_word(0x2400 + xysc((x),(y)))\r\n#define xpix(x) ((x)+32)\r\n#define xytopix(x, y) u16_to_word((xpix((x)) << 8) | (y))\r\n\r\n#define STACK_SIZE 65536\r\n\r\n#define CRED1 17152\r\n#define CRED2 16384\r\n\r\nstatic Mem m;\r\nstatic uint8_t *rawmem = (uint8_t*) &m;\r\n\r\nstatic int64_t ticks;\r\nstatic int im;\r\nstatic int irq_state;\r\nstatic int irq_vector;\r\n\r\nstatic uint16_t shift_data;\r\nstatic uint8_t shift_count;\r\n\r\nstatic ucontext_t frontend_ctx;\r\nstatic ucontext_t main_ctx;\r\nstatic ucontext_t int_ctx;\r\n\r\nstatic ucontext_t *prev_ctx;\r\nstatic ucontext_t *curr_ctx;\r\n\r\nstatic uint8_t main_ctx_stack[STACK_SIZE];\r\nstatic uint8_t int_ctx_stack[STACK_SIZE];\r\n\r\nstatic YieldReason yield_reason;\r\n\r\nstatic SDL_Window *window;\r\nstatic SDL_Renderer *renderer;\r\n\r\nstatic const int renderscale = 2;\r\n\r\nstatic uint64_t keystate;\r\n\r\nstatic int exited;\r\n\r\nstatic uint8_t port1;\r\nstatic uint8_t port2;\r\n\r\nint main(int argc, char **argv)\r\n{\r\n init_renderer();\r\n init_game();\r\n\r\n int credit = 0;\r\n size_t frame = -1;\r\n\r\n while (1)\r\n {\r\n frame++;\r\n input();\r\n\r\n if (exited) break;\r\n\r\n // preserves timing compatibility with MAME\r\n if (frame == 1)\r\n credit--;\r\n\r\n // up to mid\r\n\r\n credit += CRED1;\r\n loop_core(&credit);\r\n irq(0xcf);\r\n\r\n // up to vblank\r\n\r\n credit += CRED2;\r\n loop_core(&credit);\r\n irq(0xd7);\r\n\r\n render();\r\n }\r\n\r\n fini_game();\r\n fini_renderer();\r\n\r\n return 0;\r\n}\r\n\r\nstatic void input()\r\n{\r\n SDL_Event event_buffer[64];\r\n size_t num = 0;\r\n\r\n while (num < 64)\r\n {\r\n int has = SDL_PollEvent(&event_buffer[num]);\r\n if (!has) break;\r\n num++;\r\n }\r\n\r\n for (size_t i = 0; i < num; ++i)\r\n {\r\n SDL_Event e = event_buffer[i];\r\n\r\n if (e.type == SDL_QUIT)\r\n {\r\n e.type = SDL_KEYDOWN;\r\n e.key.keysym.sym = SDLK_ESCAPE;\r\n }\r\n\r\n if (! (e.type == SDL_KEYDOWN || e.type == SDL_KEYUP)) continue;\r\n\r\n uint64_t mask = 0;\r\n uint64_t f = e.type == SDL_KEYDOWN;\r\n\r\n switch (e.key.keysym.sym)\r\n {\r\n#define KEY_MAP(x, y) case x: mask = y; break;\r\n KEY_LIST\r\n#undef KEY_MAP\r\n }\r\n\r\n keystate = (keystate & ~mask) | (-f & mask);\r\n }\r\n\r\n#define BIT(x) (!!(keystate & (x)))\r\n\r\n port1 = (BIT(KEYS_RIGHT) << 6) |\r\n (BIT(KEYS_LEFT) << 5) |\r\n (BIT(KEYS_FIRE) << 4) |\r\n (1 << 3) |\r\n (BIT(KEYS_START) << 2) |\r\n (BIT(KEYS_START2) << 1) |\r\n (BIT(KEYS_COIN) << 0);\r\n\r\n port2 = (BIT(KEYS_DIP7) << 7) |\r\n (BIT(KEYS_RIGHT) << 6) |\r\n (BIT(KEYS_LEFT) << 5) |\r\n (BIT(KEYS_FIRE) << 4) |\r\n (BIT(KEYS_DIP6) << 3) |\r\n (BIT(KEYS_TILT) << 2);\r\n\r\n exited = BIT(KEYS_QUIT);\r\n}\r\n\r\nstatic void init_renderer()\r\n{\r\n int rc = SDL_Init(SDL_INIT_EVERYTHING);\r\n assert(rc == 0);\r\n\r\n window = SDL_CreateWindow(\"si78c\", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,\r\n 224 * renderscale, 256 * renderscale, 0);\r\n assert(window);\r\n\r\n SDL_ShowCursor(SDL_DISABLE);\r\n\r\n renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);\r\n assert(renderer);\r\n}\r\n\r\nstatic void fini_renderer()\r\n{\r\n SDL_DestroyRenderer(renderer);\r\n SDL_DestroyWindow(window);\r\n}\r\n\r\nstatic void render()\r\n{\r\n SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);\r\n SDL_RenderClear(renderer);\r\n SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);\r\n\r\n const uint8_t *iter = rawmem + 0x2400;\r\n\r\n for (int y = 0; y < 224; ++y)\r\n {\r\n for (int xi = 0; xi < 32; ++xi)\r\n {\r\n uint8_t byte = *iter++;\r\n\r\n for (int i = 0; i < 8; ++i)\r\n {\r\n int x = xi * 8 + i;\r\n int on = (byte >> i) & 0x1;\r\n\r\n if (on)\r\n {\r\n SDL_Rect rect;\r\n rect.x = y * renderscale;\r\n rect.y = (256 - x - 1) * renderscale;\r\n rect.w = renderscale;\r\n rect.h = renderscale;\r\n\r\n SDL_RenderDrawRect(renderer, &rect);\r\n }\r\n }\r\n }\r\n }\r\n\r\n SDL_RenderPresent(renderer);\r\n}\r\n\r\nstatic void loop_core(int *credit)\r\n{\r\n int allowed = 1;\r\n\r\n while (*credit > 0)\r\n *credit -= execute(allowed);\r\n}\r\n\r\nstatic void init_game()\r\n{\r\n assert(sizeof(m) == 0x10000);\r\n load_rom(&m);\r\n\r\n assert(checksum(&m) == 0x6dfbd7cc);\r\n init_threads(YIELD_INIT);\r\n}\r\n\r\nstatic void fini_game()\r\n{\r\n}\r\n\r\nstatic void init_threads(YieldReason entry_point)\r\n{\r\n int rc = getcontext(&main_ctx);\r\n assert(rc == 0);\r\n\r\n main_ctx.uc_stack.ss_sp = main_ctx_stack;\r\n main_ctx.uc_stack.ss_size = STACK_SIZE;\r\n main_ctx.uc_link = &frontend_ctx;\r\n\r\n makecontext(&main_ctx, (void (*)()) run_main_ctx, 1, entry_point);\r\n\r\n rc = getcontext(&int_ctx);\r\n\r\n int_ctx.uc_stack.ss_sp = &int_ctx_stack;\r\n int_ctx.uc_stack.ss_size = STACK_SIZE;\r\n int_ctx.uc_link = &frontend_ctx;\r\n\r\n makecontext(&int_ctx, run_int_ctx, 0);\r\n prev_ctx = &main_ctx;\r\n curr_ctx = &frontend_ctx;\r\n}\r\n\r\nstatic void run_main_ctx(YieldReason entry)\r\n{\r\n switch(entry)\r\n {\r\n case YIELD_INIT: reset(); break;\r\n case YIELD_WAIT_FOR_START: WaitForStart(); break;\r\n case YIELD_PLAYER_DEATH: player_death(0); break;\r\n case YIELD_INVADED: on_invaded(); break;\r\n case YIELD_TILT: on_tilt(); break;\r\n default: assert(FALSE);\r\n }\r\n}\r\n\r\nstatic void run_int_ctx()\r\n{\r\n while (1)\r\n {\r\n // 0xcf = RST 1 opcode (call 0x8)\r\n // 0xd7 = RST 2 opcode (call 0x16)\r\n\r\n if (irq_vector == 0xcf)\r\n midscreen_int();\r\n else if (irq_vector == 0xd7)\r\n vblank_int();\r\n\r\n enable_interrupts();\r\n yield(YIELD_INTFIN);\r\n }\r\n}\r\n\r\nstatic unsigned checksum(Mem *m)\r\n{\r\n assert(sizeof(*m) == 0x10000);\r\n assert((uintptr_t) m % 4 == 0);\r\n unsigned *ptr = (unsigned*) m;\r\n size_t n = sizeof(*m) / 4;\r\n\r\n unsigned sum = 0;\r\n\r\n for (size_t i = 0; i < n; ++i)\r\n sum += ptr[i];\r\n\r\n return sum;\r\n}\r\n\r\nstatic void rom_load(void *mem, const char* name, size_t offset, size_t len)\r\n{\r\n char fbuf[256]; sprintf(fbuf, \"inv1/%s\", name);\r\n\r\n FILE *romfile = fopen(fbuf, \"r\");\r\n assert(romfile);\r\n\r\n ssize_t rn = fread((char*) mem + offset, 1, len, romfile);\r\n assert((size_t) rn == len);\r\n fclose(romfile);\r\n}\r\n\r\nstatic void load_rom(void *mem)\r\n{\r\n rom_load(mem, \"invaders.h\", 0x0000, 0x0800);\r\n rom_load(mem, \"invaders.g\", 0x0800, 0x0800);\r\n rom_load(mem, \"invaders.f\", 0x1000, 0x0800);\r\n rom_load(mem, \"invaders.e\", 0x1800, 0x0800);\r\n}\r\n\r\nstatic int execute(int allowed)\r\n{\r\n int64_t start = ticks;\r\n\r\n ucontext_t *next = NULL;\r\n\r\n switch (yield_reason)\r\n {\r\n case YIELD_INIT:\r\n case YIELD_TIMESLICE:\r\n next = prev_ctx;\r\n break;\r\n case YIELD_INTFIN:\r\n next = &main_ctx;\r\n break;\r\n case YIELD_PLAYER_DEATH:\r\n case YIELD_WAIT_FOR_START:\r\n case YIELD_INVADED:\r\n init_threads(yield_reason);\r\n enable_interrupts();\r\n next = &main_ctx;\r\n break;\r\n case YIELD_TILT:\r\n init_threads(yield_reason);\r\n next = &main_ctx;\r\n break;\r\n default:\r\n assert(FALSE);\r\n }\r\n\r\n yield_reason = YIELD_UNKNOWN;\r\n\r\n if (allowed && interrupted())\r\n {\r\n next = &int_ctx;\r\n }\r\n\r\n switch_to(next);\r\n\r\n return ticks - start;\r\n}\r\n\r\nstatic void switch_to(ucontext_t *to)\r\n{\r\n co_switch(curr_ctx, to);\r\n}\r\n\r\nstatic void co_switch(ucontext_t *prev, ucontext_t *next)\r\n{\r\n prev_ctx = prev;\r\n curr_ctx = next;\r\n\r\n swapcontext(prev, next);\r\n}\r\n\r\nstatic void timeslice()\r\n{\r\n ticks += 30;\r\n\r\n yield(YIELD_TIMESLICE);\r\n}\r\n\r\nstatic void yield(YieldReason reason)\r\n{\r\n yield_reason = reason;\r\n switch_to(&frontend_ctx);\r\n}\r\n\r\nstatic uint8_t get_input(int64_t ticks, uint8_t port)\r\n{\r\n if (port == 1)\r\n return port1;\r\n\r\n if (port == 2)\r\n return port2;\r\n\r\n fatalerror(\"unknown port %d\\n\", port);\r\n return 0;\r\n}\r\n\r\nstatic uint8_t read_port(uint8_t port)\r\n{\r\n if (port == 3)\r\n return (shift_data << shift_count) >> 8;\r\n\r\n uint8_t val = get_input(ticks, port);\r\n return val;\r\n}\r\n\r\nstatic void write_port(uint16_t port, uint8_t v)\r\n{\r\n if (port == 2)\r\n {\r\n shift_count = v & 0x7;\r\n }\r\n else if (port == 4)\r\n {\r\n shift_data = (v << 8) | (shift_data >> 8);\r\n }\r\n\r\n timeslice();\r\n}\r\n\r\nstatic void enable_interrupts()\r\n{\r\n im = 1;\r\n}\r\n\r\nstatic void irq(uint8_t v)\r\n{\r\n irq_vector = v;\r\n irq_state = 1;\r\n}\r\n\r\nstatic int interrupted()\r\n{\r\n // The two interrupts correspond to midscreen, and start of vblank.\r\n // 0xcf = RST 1 opcode (call 0x8)\r\n // 0xd7 = RST 2 opcode (call 0x16)\r\n\r\n if (irq_state && im)\r\n {\r\n assert(irq_vector == 0xcf || irq_vector == 0xd7);\r\n\r\n irq_state = 0;\r\n im = 0;\r\n return TRUE;\r\n }\r\n\r\n return FALSE;\r\n}\r\n\r\nstatic void fatalerror(const char* format, ...)\r\n{\r\n va_list ap;\r\n va_start(ap, format);\r\n vfprintf(stdout, format, ap);\r\n va_end(ap);\r\n\r\n fflush(stdout);\r\n fflush(stderr);\r\n\r\n exit(1);\r\n}\r\n\r\nstatic inline Word u16_to_word(uint16_t u)\r\n{\r\n Word w; w.u16 = u; return w;\r\n}\r\n\r\nstatic inline Word u8_u8_to_word(uint8_t h, uint8_t l)\r\n{\r\n return u16_to_word((h << 8) | l);\r\n}\r\n\r\nstatic inline uint16_t ptr_to_u16(uint8_t *ptr)\r\n{\r\n return (uint16_t) (ptr - (uint8_t*) &m);\r\n}\r\n\r\nstatic inline Word ptr_to_word(uint8_t *ptr)\r\n{\r\n return u16_to_word(ptr_to_u16(ptr));\r\n}\r\n\r\nstatic inline uint8_t* u16_to_ptr(uint16_t u)\r\n{\r\n return ((uint8_t*) &m) + u;\r\n}\r\n\r\nstatic inline uint8_t* word_to_ptr(Word w)\r\n{\r\n return u16_to_ptr(w.u16);\r\n}\r\n\r\nstatic int is_godmode()\r\n{\r\n uint16_t addr = 0x060f;\r\n uint8_t nops[] = {0,0,0};\r\n\r\n return memcmp(((uint8_t*) &m) + addr, nops, 3) == 0;\r\n}\r\n\r\nstatic uint8_t* rompos(uint8_t* ram)\r\n{\r\n return ram - 0x500;\r\n}\r\n\r\nstatic uint8_t bcd_add(uint8_t bcd, uint8_t a, uint8_t *carry)\r\n{\r\n // Add the given number to the given bcd value, as per ADI / ADDB etc\r\n int q = bcd + a + *carry;\r\n *carry = (q >> 8) & 0x1;\r\n int aux = (bcd ^ q ^ a) & 0x10;\r\n bcd = q;\r\n\r\n // Adjust the result back into bcd as per DAA\r\n uint8_t w = bcd;\r\n\r\n if (aux || ((bcd & 0xf) > 9)) w += 6;\r\n if ((*carry) || (bcd > 0x99)) w += 0x60;\r\n *carry |= bcd > 0x99;\r\n bcd = w;\r\n\r\n return bcd;\r\n}\r\n\r\nstatic void do_logprintf(const char *file, unsigned line, const char* format, ...)\r\n{\r\n fprintf(stdout, \"%s:%d: \", file, line);\r\n\r\n va_list ap;\r\n va_start(ap, format);\r\n vfprintf(stdout, format, ap);\r\n va_end(ap);\r\n\r\n fflush(stdout);\r\n}\r\n\r\nstatic void DebugMessage(Word sc, uint8_t* msg, uint8_t n)\r\n{\r\n uint16_t raw = sc.u16 - 0x2400;\r\n\r\n uint16_t x = raw / 32;\r\n uint16_t y = (raw % 32) * 8;\r\n static const char *alpha = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789<> =*...............?......-\";\r\n\r\n char sbuf[256];\r\n\r\n for (size_t i = 0; i < n; ++i)\r\n sbuf[i] = alpha[msg[i]];\r\n\r\n sbuf[n] = \'\\0\';\r\n\r\n logprintf(\"Print Message %04x %d (%d,%d) \\\"%s\\\"\\n\", ptr_to_word(msg).u16, n, x, y, sbuf);\r\n}\r\n\r\n// GAMECODE START\r\n\r\n// The entry point on reset or power up\r\nstatic void reset()\r\n{\r\n // xref 0000\r\n main_init(0);\r\n}\r\n\r\n// Executed via interrupt when the beam hits the middle of the screen\r\nstatic void midscreen_int()\r\n{\r\n // xref 0008\r\n // xref 008c\r\n m.vblankStatus = BEAM_MIDDLE;\r\n\r\n if (m.gameTasksRunning == 0) return;\r\n if (!m.gameMode && !(m.isrSplashTask & 0x1)) return;\r\n\r\n // xref 00a5\r\n // Run game objects but skip over first entry (player)\r\n\r\n RunGameObjs(u16_to_ptr(PLAYER_SHOT_ADDR));\r\n CursorNextAlien();\r\n}\r\n\r\n// Executed via interrupt when the beam hits the end of the screen\r\nstatic void vblank_int()\r\n{\r\n // xref 0010\r\n m.vblankStatus = BEAM_VBLANK;\r\n m.isrDelay--;\r\n CheckHandleTilt();\r\n vblank_coins();\r\n\r\n if (m.gameTasksRunning == 0)\r\n return;\r\n\r\n if (m.gameMode)\r\n {\r\n // xref 006f\r\n TimeFleetSound();\r\n m.shotSync = m.rolShotHeader.TimerExtra;\r\n DrawAlien();\r\n RunGameObjs(u16_to_ptr(PLAYER_ADDR));\r\n TimeToSaucer();\r\n return;\r\n }\r\n\r\n if (m.numCoins != 0)\r\n {\r\n // xref 005d\r\n if (m.waitStartLoop) return;\r\n\r\n m.waitStartLoop = 1;\r\n yield(YIELD_WAIT_FOR_START);\r\n assert(FALSE);\r\n }\r\n\r\n ISRSplTasks();\r\n}\r\n\r\n// Read coin input and handle debouncing\r\nstatic void vblank_coins()\r\n{\r\n // xref 0020\r\n\r\n if (read_port(1) & COIN_BIT)\r\n {\r\n // xref 0067\r\n m.coinSwitch = 1; // Remember switch state for debounce\r\n\r\n // xref 003f\r\n // useless update\r\n m.coinSwitch = 1;\r\n return;\r\n }\r\n\r\n // xref 0026\r\n // Skip registering the credit if prev and current states were both zero\r\n if (m.coinSwitch == 0)\r\n return;\r\n\r\n uint8_t bcd = m.numCoins;\r\n\r\n // Add credit if it won\'t cause a rollover\r\n if (bcd != 0x99)\r\n {\r\n uint8_t carry = 0;\r\n m.numCoins = bcd_add(bcd, 1, &carry);\r\n DrawNumCredits();\r\n }\r\n\r\n m.coinSwitch = 0;\r\n}\r\n\r\n// Initialize the alien rack speed and position using the current player\'s alien rack data\r\nstatic void InitRack()\r\n{\r\n // xref 00b1\r\n\r\n uint8_t* al_ref_ptr = GetAlRefPtr();\r\n\r\n Word refpos;\r\n refpos.y = *(al_ref_ptr);\r\n refpos.x = *(al_ref_ptr + 1);\r\n\r\n m.refAlienPos = refpos;\r\n m.alienPos = refpos;\r\n\r\n uint8_t dxr = *(al_ref_ptr - 1);\r\n\r\n if (dxr == 3)\r\n --dxr;\r\n\r\n m.refAlienDelta.x = dxr;\r\n m.rackDirection = (dxr == (uint8_t) -2) ? 1 : 0;\r\n}\r\n\r\n// Set up P1 and P2 Rack data\r\nstatic void InitAlienRacks()\r\n{\r\n // xref 00d7\r\n\r\n // Added for si78c testing infrastructure\r\n // In normal operation, dx is hardcoded to 2\r\n uint8_t dx = ((uint8_t*) &m)[0x00d8];\r\n\r\n m.p1RefAlienDX = dx;\r\n m.p2RefAlienDX = dx;\r\n\r\n // xref 08e4\r\n if (m.twoPlayers)\r\n return;\r\n\r\n ClearSmallSprite(xytosc(168, 224), 32, 0);\r\n}\r\n\r\n// Draw the alien, called via vblank_int()\r\n// Only one alien is ever drawn per frame, causing a ripple effect.\r\n// This also somewhat determines the game speed.\r\n//\r\n// The alien rack is effectively moving at\r\n// (2.0 / num_aliens) pixels per frame\r\n//\r\n// If 55 aliens are alive, then it will take almost one second to move all the aliens by 2 pixels.\r\n// If 1 alien is alive, then it will only take one frame to move 2 pixels.\r\nstatic void DrawAlien()\r\n{\r\n // xref 0100\r\n\r\n if (m.alienIsExploding)\r\n {\r\n if (--m.expAlienTimer) return;\r\n\r\n EraseSimpleSprite(m.expAlien.pos, 16);\r\n\r\n m.plyrShotStatus = 4;\r\n m.alienIsExploding = 0;\r\n SoundBits3Off(0xf7);\r\n return;\r\n }\r\n\r\n uint8_t ai = m.alienCurIndex;\r\n uint8_t* alienptr = u16_to_ptr(m.playerDataMSB << 8 | ai);\r\n\r\n if (*alienptr == 0)\r\n {\r\n // The alien is dead, so skip it and tell CursorNextAlien to advance.\r\n m.waitOnDraw = 0;\r\n return;\r\n }\r\n\r\n // Look up the correct alien sprite based on row and anim frame\r\n\r\n uint8_t row = m.alienRow;\r\n static const uint8_t mul[] = {0,0,16,16,32};\r\n uint8_t* sprite = u16_to_ptr(0x1c00 + mul[row] + m.alienFrame * 48);\r\n\r\n SprDesc desc;\r\n desc.pos = m.alienPos;\r\n desc.spr = ptr_to_word(sprite);\r\n desc.n = 16;\r\n\r\n DrawSprite(&desc);\r\n m.waitOnDraw = 0; // This flag is synced with CursorNextAlien\r\n}\r\n\r\n// Find the next live alien to draw. Also detects whether rack has reached the bottom.\r\nstatic void CursorNextAlien()\r\n{\r\n // xref 0141\r\n\r\n if (m.playerOK == 0) return;\r\n if (m.waitOnDraw != 0) return;\r\n\r\n Word ai;\r\n ai.h = m.playerDataMSB;\r\n ai.l = m.alienCurIndex;\r\n uint8_t movecnt = 0x02; // limits MoveRefAlien to be called once\r\n\r\n // Advance the cursor until we find a live alien to draw.\r\n // If the cursor reaches the end, move ref alien, flip anim and reset cursor to zero.\r\n while (TRUE)\r\n {\r\n timeslice();\r\n ++ai.l;\r\n\r\n // inlined MoveRefAlien\r\n if (ai.l == 55)\r\n {\r\n if (--movecnt == 0) return;\r\n\r\n m.alienCurIndex = 0;\r\n ai.l = 0;\r\n\r\n uint8_t dy = m.refAlienDelta.y;\r\n m.refAlienDelta.y = 0;\r\n AddDelta(&m.refAlienDelta.y, dy);\r\n\r\n m.alienFrame = !m.alienFrame;\r\n (void) (uint8_t) (m.playerDataMSB); // unused\r\n }\r\n\r\n // If we found live alien to draw, then break\r\n if (*word_to_ptr(ai)) break;\r\n }\r\n\r\n m.alienCurIndex = ai.l;\r\n\r\n uint8_t row = 0;\r\n Word pixnum = GetAlienCoords(&row, ai.l);\r\n m.alienPos = pixnum;\r\n\r\n if (pixnum.y < 40)\r\n {\r\n // xref 1971\r\n // kill the player due to invasion\r\n\r\n m.invaded = 1;\r\n yield(YIELD_INVADED);\r\n assert(FALSE);\r\n }\r\n\r\n m.alienRow = row;\r\n m.waitOnDraw = 1;\r\n}\r\n\r\n// Given alien index k, return alien position and row\r\nstatic Word GetAlienCoords(uint8_t *rowout, uint8_t k)\r\n{\r\n // xref 017a\r\n\r\n uint8_t row = k / 11;\r\n uint8_t col = k % 11;\r\n\r\n uint8_t y = m.refAlienPos.y + 16 * row;\r\n uint8_t x = m.refAlienPos.x + 16 * col;\r\n\r\n *rowout = row;\r\n return u8_u8_to_word(x, y);\r\n}\r\n\r\n// Init all the aliens at dest to alive\r\nstatic void InitAliensSub(uint8_t* dest)\r\n{\r\n // xref 01c0\r\n for (int i = 0; i < 55; ++i)\r\n *dest++ = 1;\r\n}\r\n\r\n// Init all the aliens for P1\r\nstatic void InitAliensP1()\r\n{\r\n // xref 01c0\r\n InitAliensSub(u16_to_ptr(P1_ADDR));\r\n}\r\n\r\n// Draw a one pixel (leftmost/bottommost in byte) stripe across the screen (224 pix wide)\r\n// 16 pixels above the origin.\r\nstatic void DrawBottomLine()\r\n{\r\n // xref 01cf\r\n ClearSmallSprite(xytosc(0,16), 224, 1);\r\n}\r\n\r\n// Given four bytes at vecptr treat them as two vecs - dy dx, y x\r\n// and do this:\r\n//\r\n// (y,x) += (c, dx)\r\n//\r\n// Used to move objects\r\nstatic uint8_t AddDelta(uint8_t* vecptr, uint8_t c)\r\n{\r\n vecptr++; // skip dy\r\n\r\n uint8_t dy = c;\r\n uint8_t dx = *vecptr++;\r\n\r\n uint8_t y = *vecptr + dy;\r\n *vecptr++ = y;\r\n\r\n uint8_t x = *vecptr + dx;\r\n *vecptr++ = x;\r\n\r\n return x;\r\n}\r\n\r\n// Restore into RAM mirror (addr, n) from ROM\r\nstatic void RestoreFromROM(uint8_t* addr, size_t n)\r\n{\r\n BlockCopy(addr, rompos(addr), n);\r\n}\r\n\r\n// Restore the entire RAM mirror (256 bytes), used at startup\r\nstatic void CopyRomtoRam()\r\n{\r\n // xref 01e6\r\n RestoreFromROM(u16_to_ptr(0x2000), 256);\r\n}\r\n\r\n// Partially restore the RAM mirror (first 192 bytes)\r\n// The last 64 bytes are managed elsewhere, and some are\r\n// persistent across games (hi scores etc)\r\nstatic void CopyRAMMirror()\r\n{\r\n // xref 01e4\r\n RestoreFromROM(u16_to_ptr(0x2000), 192);\r\n}\r\n\r\n// Initialize P1 shields into off screen buffer\r\nstatic void DrawShieldP1()\r\n{\r\n // xref 01ef\r\n DrawShieldCommon(m.p1ShieldBuffer);\r\n}\r\n\r\n// Initialize P2 shields into off screen buffer\r\nstatic void DrawShieldP2()\r\n{\r\n // xref 01f5\r\n DrawShieldCommon(m.p2ShieldBuffer);\r\n}\r\n\r\n// Initialize shields into given buffer\r\nstatic void DrawShieldCommon(uint8_t* dest)\r\n{\r\n // xref 01f8\r\n size_t n = 44;\r\n\r\n for (int i = 0; i < 4; ++i, dest += n)\r\n BlockCopy(dest, m.SHIELD_SPRITE, n);\r\n}\r\n\r\n// Copy on screen shields into P1 off screen buffer to remember damage\r\nstatic void RememberShieldsP1()\r\n{\r\n // xref 0209\r\n CopyShields(1, m.p1ShieldBuffer);\r\n}\r\n\r\n// Copy on screen shields into P2 off screen buffer to remember damage\r\nstatic void RememberShieldsP2()\r\n{\r\n // xref 020e\r\n CopyShields(1, m.p2ShieldBuffer);\r\n}\r\n\r\n// Copy off screen shields from P2 buffer back to screen\r\nstatic void RestoreShieldsP2()\r\n{\r\n // xref 0213\r\n CopyShields(0, m.p2ShieldBuffer);\r\n}\r\n\r\n// Copy off screen shields from P1 buffer back to screen\r\nstatic void RestoreShieldsP1()\r\n{\r\n // xref 021a\r\n CopyShields(0, m.p1ShieldBuffer);\r\n}\r\n\r\n// Generic shield copy routine. Copy into or out of given buffer.\r\n// dir = 0 - buffer to screen\r\n// dir = 1 - screen to buffer\r\nstatic void CopyShields(uint8_t dir, uint8_t* sprbuf)\r\n{\r\n // xref 021e\r\n m.tmp2081 = dir;\r\n\r\n Word sprsz = u8_u8_to_word(22, 2); // 22 rows, 2 bytes per row\r\n\r\n ShieldBufferCursor cursor;\r\n cursor.sc = xytosc(32, 48);\r\n cursor.iter = sprbuf;\r\n\r\n for (int i = 0; i < 4; ++i)\r\n {\r\n uint8_t unused = m.tmp2081;\r\n (void) (unused);\r\n\r\n CopyShieldBuffer(&cursor, sprsz, dir);\r\n\r\n cursor.sc.u16 += xysc(23, 0);\r\n }\r\n}\r\n\r\n// Do the generic (base class) handling for the 5 game objects.\r\n// Checks the timer flags to see if object is ready to run, and if so\r\n// finds the appropriate subclass handler and calls it.\r\nstatic void RunGameObjs(uint8_t* ptr)\r\n{\r\n // xref 0248\r\n\r\n for ( ; ; ptr += 16)\r\n {\r\n GameObjHeader* obj = (GameObjHeader*)(ptr);\r\n uint8_t timer_hi = obj->TimerMSB;\r\n\r\n // end of list check\r\n if (timer_hi == 0xff) return;\r\n\r\n // object skip check\r\n if (timer_hi == 0xfe) continue;\r\n\r\n uint8_t timer_lo = obj->TimerLSB;\r\n\r\n // decrement timer if its not zero\r\n if (timer_hi | timer_lo)\r\n {\r\n // xref 0277\r\n // timers are big endian for some reason.\r\n\r\n if (timer_lo == 0) --timer_hi; // decrement msb if necc\r\n --timer_lo; // decrement lsb\r\n\r\n obj->TimerLSB = timer_lo;\r\n obj->TimerMSB = timer_hi;\r\n\r\n continue;\r\n }\r\n\r\n uint8_t timer_extra = obj->TimerExtra;\r\n\r\n if (timer_extra != 0)\r\n {\r\n // xref 0288\r\n obj->TimerExtra--;\r\n continue;\r\n }\r\n\r\n // The object is ready to run, so grab the handler and run it\r\n\r\n uint16_t fnlo = obj->Handler.l;\r\n uint16_t fnhi = obj->Handler.h;\r\n uint16_t fn = fnhi << 8 | fnlo;\r\n\r\n uint8_t* data = (ptr + sizeof(GameObjHeader));\r\n\r\n switch(fn)\r\n {\r\n case 0x028e: GameObj0(data); break;\r\n case 0x03bb: GameObj1(data); break;\r\n case 0x0476: GameObj2(data); break;\r\n case 0x04b6: GameObj3(data); break;\r\n case 0x0682: GameObj4(data); break;\r\n case 0x050e: ProcessSquigglyShot(); break; // splash anim\r\n default:\r\n assert(FALSE);\r\n break;\r\n }\r\n }\r\n}\r\n\r\n// Handles player movement and rendering\r\n// Unlike other game objects, this is only run during the vblank interrupt, and\r\n// runs on each vblank interrupt\r\nstatic void GameObj0(uint8_t* unused)\r\n{\r\n // xref 028e\r\n uint8_t pstate = m.playerAlive;\r\n\r\n if (pstate != 0xff) // 0xff means player is alive\r\n {\r\n HandleBlowingUpPlayer(pstate);\r\n return;\r\n }\r\n\r\n // xref 033b\r\n m.playerOK = 1;\r\n\r\n // xref 03b0\r\n if (m.enableAlienFire == 0)\r\n {\r\n // xref 03b3\r\n\r\n if (--m.alienFireDelay == 0)\r\n m.enableAlienFire = 1;\r\n }\r\n\r\n // xref 034a\r\n uint8_t x = m.playerDesc.pos.x;\r\n uint8_t input = 0;\r\n\r\n if (m.gameMode == 0)\r\n {\r\n input = m.nextDemoCmd;\r\n }\r\n else\r\n {\r\n input = ReadInputs();\r\n\r\n // Map joystick controls into the same domain as the demo commands\r\n if ((input >> 6) & 0x1)\r\n input = 1;\r\n else if ((input >> 5) & 0x1)\r\n input = 2;\r\n else\r\n input = 0;\r\n }\r\n\r\n if (input == 0)\r\n {\r\n // Do nothing\r\n }\r\n else if (input == 1)\r\n {\r\n // Move player to the right\r\n if (x != xpix(185))\r\n {\r\n ++x;\r\n m.playerDesc.pos.x = x;\r\n }\r\n }\r\n else if (input == 2)\r\n {\r\n // Move player to the left\r\n if (x != xpix(16))\r\n {\r\n --x;\r\n m.playerDesc.pos.x = x;\r\n }\r\n }\r\n else\r\n {\r\n assert(FALSE);\r\n }\r\n\r\n DrawPlayer();\r\n}\r\n\r\n// Handle the player\'s death animation. Resets the stack once complete,\r\n// and re-entry is through player_death(0)\r\nstatic void HandleBlowingUpPlayer(uint8_t anim)\r\n{\r\n // xref 0296\r\n if (--m.expAnimateTimer != 0)\r\n return;\r\n\r\n m.playerOK = 0;\r\n m.enableAlienFire = 0;\r\n m.alienFireDelay = 48;\r\n m.expAnimateTimer = 5;\r\n\r\n if (--m.expAnimateCnt != 0)\r\n {\r\n // Still animating the explosion\r\n anim = !anim;\r\n m.playerAlive = anim;\r\n\r\n m.playerDesc.spr = ptr_to_word((m.PLAYER_SPRITES + (anim+1) * 16));\r\n DrawPlayer();\r\n return;\r\n }\r\n\r\n EraseSimpleSprite(m.playerDesc.pos, 16);\r\n RestoreFromROM(u16_to_ptr(PLAYER_ADDR), PLAYER_SIZE);\r\n\r\n SoundBits3Off(0);\r\n\r\n if (m.invaded)\r\n return;\r\n\r\n // Return to splash screens in demo mode\r\n if (m.gameMode == 0)\r\n return;\r\n\r\n yield(YIELD_PLAYER_DEATH);\r\n assert(FALSE);\r\n}\r\n\r\n// Handle cleanup tasks related to player dying, such as lives and score adjustment,\r\n// switching players and calling GameOver if necessary.\r\n// If invaded is one, the player loses the game, regardless of number of lives left.\r\n// This routine is called after a stack reset, see on_invaded() and HandleBlowingUpPlayer()\r\nstatic void player_death(int invaded)\r\n{\r\n // xref 02d4\r\n int switch_players = player_death_sub(invaded);\r\n\r\n if (!switch_players)\r\n {\r\n if (!*(GetOtherPlayerAliveFlag()) || !m.twoPlayers)\r\n {\r\n RemoveShip();\r\n NewGame(0, 0, INIT | PROMPT | SHIELDS);\r\n }\r\n }\r\n\r\n uint8_t pnum = m.playerDataMSB;\r\n\r\n if (pnum & 0x1)\r\n RememberShieldsP1();\r\n else\r\n RememberShieldsP2();\r\n\r\n uint8_t adx = 0;\r\n Word apos;\r\n uint8_t* aref = GetRefAlienInfo(&adx, &apos);\r\n\r\n *aref = apos.y;\r\n *(aref+1) = apos.x;\r\n\r\n *(aref-1) = adx;\r\n\r\n // about to switch players.\r\n CopyRAMMirror();\r\n\r\n uint8_t carry = pnum & 0x1;\r\n uint8_t pmsb = 0x21; // p1\r\n uint8_t cbit = 0;\r\n\r\n if (carry)\r\n {\r\n cbit = 0x20; // cocktail bit=1\r\n pmsb = 0x22;\r\n }\r\n\r\n m.playerDataMSB = pmsb; // change players\r\n TwoSecDelay();\r\n\r\n m.playerHeader.TimerLSB = 0; // clear player object timer\r\n write_port(5, cbit);\r\n\r\n m.soundPort5 = (cbit + 1);\r\n ClearPlayField();\r\n RemoveShip();\r\n\r\n // jmp to 079b. (newgame + skip)\r\n NewGame(0, 0, INIT);\r\n}\r\n\r\n// Player death cleanup subroutine. Game is lost immediately if invaded=1\r\n// Returns true if caller should switch players, false otherwise\r\n// This subroutine will not return if all players have lost the game, instead it\r\n// will call through to GameOver, which will start a new game\r\nstatic int player_death_sub(int invaded)\r\n{\r\n if (!invaded)\r\n {\r\n DsableGameTasks();\r\n\r\n uint8_t* unused;\r\n\r\n // still got some ships, keep going\r\n if (GetNumberOfShips(&unused) != 0)\r\n return FALSE;\r\n\r\n PrintNumShips(0);\r\n }\r\n\r\n // handle losing the game\r\n\r\n *CurPlyAlive() = 0;\r\n\r\n uint8_t* sc = GetScoreDescriptor();\r\n sc++;\r\n\r\n uint8_t* hi = &m.HiScor.h;\r\n\r\n uint8_t hi_score_msb = *(hi);\r\n uint8_t pl_score_msb = *(sc);\r\n\r\n hi--;\r\n sc--;\r\n\r\n uint8_t hi_score_lsb = *(hi);\r\n uint8_t pl_score_lsb = 0;\r\n\r\n int higher = FALSE;\r\n\r\n if (hi_score_msb == pl_score_msb)\r\n {\r\n // same msb, must check lower\r\n pl_score_lsb = *(sc);\r\n higher = pl_score_lsb > hi_score_lsb;\r\n }\r\n else\r\n {\r\n higher = pl_score_msb > hi_score_msb;\r\n }\r\n\r\n if (higher)\r\n {\r\n *hi++ = *sc++;\r\n *hi = *sc;\r\n PrintHiScore();\r\n }\r\n\r\n // xref 1698\r\n if (m.twoPlayers)\r\n {\r\n // Game over player\r\n Word sc = xytosc(32, 24);\r\n sc = PrintMessageDel(sc, m.MSG_GAME_OVER__PLAYER___, 0x14);\r\n\r\n sc.u16 -= xysc(16, 0); // back up to player indicator\r\n uint8_t b = (m.playerDataMSB & 0x1) ? 0x1b : 0x1c;\r\n\r\n sc = DrawChar(sc, b); // print player num\r\n OneSecDelay();\r\n\r\n if (*(GetOtherPlayerAliveFlag()) == 0)\r\n {\r\n GameOver(); // won\'t return.\r\n assert(FALSE);\r\n }\r\n\r\n // switch players\r\n return TRUE;\r\n }\r\n else\r\n {\r\n GameOver(); // won\'t return\r\n assert(FALSE);\r\n }\r\n\r\n return FALSE;\r\n}\r\n\r\n// Generic player shot drawing routine, originally multiple small fragments\r\nstatic void DrawPlayerShot(int op)\r\n{\r\n // xref 0404\r\n // xref 03f4\r\n SprDesc plyshot = ReadPlyShot();\r\n\r\n if (op == OP_BLEND)\r\n DrawShiftedSprite(&plyshot);\r\n else if (op == OP_ERASE)\r\n EraseShifted(&plyshot);\r\n else\r\n assert(FALSE);\r\n}\r\n\r\n// Handles player bullet movement, collision detection and rendering.\r\n// At the end of the routine, the player\'s shot count is used to\r\n// set up the next saucer direction and score.\r\nstatic void GameObj1(uint8_t* unused)\r\n{\r\n // xref 03bb\r\n //\r\n // Shot states:\r\n //\r\n // :Available(0), :Initiated(1), :Moving(2), :HitNotAlien(3),\r\n // :AlienExploded(4), :AlienExploding(5)\r\n\r\n if (!CompXrToBeam(&m.playerShotDesc.pos.x))\r\n return;\r\n\r\n uint8_t status = m.plyrShotStatus;\r\n\r\n if (status == 0)\r\n return;\r\n\r\n if (status == 1)\r\n {\r\n // xref 03fa InitPlyShot\r\n m.plyrShotStatus = 2;\r\n m.playerShotDesc.pos.x = m.playerDesc.pos.x + 8;\r\n DrawPlayerShot(OP_BLEND);\r\n return;\r\n }\r\n else if (status == 2)\r\n {\r\n // xref 040a MovePlyShot\r\n SprDesc copy = ReadPlyShot();\r\n EraseShifted(©);\r\n\r\n copy.pos.y += m.shotDeltaYr;\r\n m.playerShotDesc.pos.y = copy.pos.y;\r\n\r\n DrawSprCollision(©);\r\n uint8_t collided = m.collision;\r\n\r\n if (!collided) return;\r\n\r\n m.alienIsExploding = collided;\r\n return;\r\n }\r\n\r\n if (status != 3)\r\n {\r\n // xref 042a\r\n if (status == 5) return;\r\n\r\n // continues at EndOfBlowup\r\n }\r\n else\r\n {\r\n // xref 03d7\r\n if (--m.blowUpTimer != 0)\r\n {\r\n // The shot is blowing up\r\n if (m.blowUpTimer != 0x0f) return;\r\n\r\n // Draw the explosion the first time through\r\n // xref 03df\r\n DrawPlayerShot(OP_ERASE);\r\n\r\n // Change the shot sprite to the explosion.\r\n m.playerShotDesc.spr.l++;\r\n\r\n // Modify the coords slightly for the explosion\r\n m.playerShotDesc.pos.y--; // y -= 2\r\n m.playerShotDesc.pos.y--;\r\n\r\n m.playerShotDesc.pos.x--; // x -= 3\r\n m.playerShotDesc.pos.x--;\r\n m.playerShotDesc.pos.x--;\r\n\r\n m.playerShotDesc.n = 8;\r\n DrawPlayerShot(OP_BLEND);\r\n return;\r\n }\r\n }\r\n\r\n // xref 0436 EndOfBlowup\r\n DrawPlayerShot(OP_ERASE);\r\n\r\n // xref 0444\r\n // reset the shot\r\n RestoreFromROM(u16_to_ptr(PLAYER_SHOT_DATA_ADDR), PLAYER_SHOT_DATA_SIZE);\r\n\r\n // The remaining code in GameObj0 is to do with adjusting the saucer bonus and\r\n // direction, which is changed up on every player shot fired.\r\n\r\n // Adjust the saucer bonus.\r\n {\r\n Word table = m.sauScore;\r\n\r\n table.l++;\r\n\r\n if (table.l >= 0x63)\r\n table.l = 0x54;\r\n\r\n m.sauScore = table;\r\n }\r\n\r\n Word shots = m.shotCount;\r\n\r\n shots.l++;\r\n m.shotCount = shots;\r\n\r\n // xref 0461\r\n // If saucer still on screen, don\'t reset the direction.\r\n if (m.saucerActive)\r\n return;\r\n\r\n // xref 0462\r\n //\r\n // This code is using the shot counter as an index into the ROM\r\n // (where some code resides), for the purposes of random number generation.\r\n //\r\n // For the saucer direction logic, only bit 0 of each bytecode is used.\r\n //\r\n // The 256 bytes used reside at 0800 -> 08FF\r\n //\r\n // If you check bit 0 of each byte in that ROM section, you will find that there is no bias,\r\n // and there are exactly 128 0\'s and 128 1\'s.\r\n //\r\n // It seems unlikely that this was an accident, I think Nishikado deliberately constructed\r\n // the ROM this way, and used some well placed NOPs to achieve fair balance.\r\n //\r\n // E.g. these NOPs\r\n //\r\n // 0854: 00 00 00\r\n // 0883: 00 00 00\r\n\r\n // This information can be exploited to the player\'s advantage.\r\n //\r\n // If using the shot counting trick to get high scores, the\r\n // expected saucer direction for the first 6 saucers (if counting),\r\n // will be as follows:\r\n //\r\n // [22,37,52,67,82,97]\r\n // [0, 1, 1, 0, 1, 1]\r\n // [L, R, R, L, R, R]\r\n\r\n uint8_t v = *(word_to_ptr(shots));\r\n\r\n // if lo bit of res is 0, then delta = -2, x = 192 (moving left from rhs)\r\n // if lo bit of res is 1, then delta = +2, x = 9 (moving right from lhs)\r\n\r\n uint8_t delta = -2;\r\n uint8_t x = xpix(192);\r\n\r\n if (v & 0x1)\r\n {\r\n delta = 2;\r\n x = xpix(9);\r\n }\r\n\r\n m.saucerDesc.pos.x = x;\r\n m.saucerDXr = delta;\r\n}\r\n\r\n// Return a copy of the player shot sprite descriptor\r\nstatic SprDesc ReadPlyShot()\r\n{\r\n // xref 0430\r\n return ReadDesc(&m.playerShotDesc);\r\n}\r\n\r\n// Handles alien rolling shot firing, movement, collision detection and rendering.\r\n// This is the shot that specifically targets the player.\r\n// Most of the logic is shared between the 3 types inside HandleAlienShot.\r\nstatic void GameObj2(uint8_t* unused1)\r\n{\r\n // xref 0476\r\n RestoreFromROM(&m.rolShotHeader.TimerExtra, 1);\r\n\r\n if (m.rolShotData.CFir.u16 == 0)\r\n {\r\n // The rolling shot doesn\'t use a firing table to choose a column to fire\r\n // from, because it specifically targets the player.\r\n //\r\n // It just uses this member as a flag to delay firing the first rolling shot\r\n m.rolShotData.CFir.u16 = 0xffff;\r\n return;\r\n }\r\n\r\n ToShotStruct(&m.rolShotData, ROL_SHOT_PICEND);\r\n\r\n m.otherShot1 = m.pluShotData.StepCnt;\r\n m.otherShot2 = m.squShotData.StepCnt;\r\n\r\n HandleAlienShot();\r\n\r\n if (m.aShot.BlowCnt != 0)\r\n {\r\n // shot still running, copy updated data from active -> rolling and return.\r\n FromShotStruct(&m.rolShotData);\r\n return;\r\n }\r\n\r\n RestoreFromROM(u16_to_ptr(ROLLING_SHOT_ADDR), ROLLING_SHOT_SIZE);\r\n}\r\n\r\n// Handles alien plunger shot firing, movement, collision detection and rendering.\r\nstatic void GameObj3(uint8_t* unused)\r\n{\r\n // xref 04b6\r\n if (m.skipPlunger) return;\r\n if (m.shotSync != 1) return;\r\n\r\n ToShotStruct(&m.pluShotData, PLU_SHOT_PICEND);\r\n\r\n m.otherShot1 = m.rolShotData.StepCnt;\r\n m.otherShot2 = m.squShotData.StepCnt;\r\n\r\n HandleAlienShot();\r\n\r\n if (m.aShot.CFir.l >= 16)\r\n {\r\n m.aShot.CFir.l = *(rompos(&m.pluShotData.CFir.l));\r\n }\r\n\r\n if (m.aShot.BlowCnt)\r\n {\r\n FromShotStruct(&m.pluShotData);\r\n return;\r\n }\r\n\r\n RestoreFromROM(u16_to_ptr(PLUNGER_SHOT_ADDR), PLUNGER_SHOT_SIZE);\r\n\r\n if (m.numAliens == 1)\r\n m.skipPlunger = 1;\r\n\r\n m.pluShotData.CFir = m.aShot.CFir;\r\n}\r\n\r\n// Handles alien squiggly shot firing, movement, collision detection and rendering.\r\n// This is very similar logic to the plunger shot except the column firing table\r\n// is different.\r\nstatic void ProcessSquigglyShot()\r\n{\r\n // xref 050f\r\n ToShotStruct(&m.squShotData, SQU_SHOT_PICEND);\r\n\r\n m.otherShot1 = m.pluShotData.StepCnt;\r\n m.otherShot2 = m.rolShotData.StepCnt;\r\n\r\n HandleAlienShot();\r\n\r\n if (m.aShot.CFir.l >= 21)\r\n {\r\n // Restores to the rom lsb values of \'6\'\r\n m.aShot.CFir.l = *(rompos(&m.squShotData.CFir.l));\r\n }\r\n\r\n if (m.aShot.BlowCnt)\r\n {\r\n FromShotStruct(&m.squShotData);\r\n return;\r\n }\r\n\r\n RestoreFromROM(u16_to_ptr(SQUIGGLY_SHOT_ADDR), SQUIGGLY_SHOT_SIZE);\r\n m.squShotData.CFir = m.aShot.CFir;\r\n}\r\n\r\n// Copy an alien shot structure from src into the active alien shot structure,\r\n// and configure the shot animation.\r\nstatic void ToShotStruct(AShot* src, uint8_t picend)\r\n{\r\n // xref 0550\r\n m.shotPicEnd = picend;\r\n BlockCopy(&m.aShot, src, 11);\r\n}\r\n\r\n// Copy the active alien shot structure into dest\r\nstatic void FromShotStruct(AShot* dest)\r\n{\r\n // xref 055b\r\n BlockCopy( dest, &m.aShot, 11);\r\n}\r\n\r\n// This logic is shared between the 3 shot types.\r\n// Handles shot firing, movement, collision detection and rendering.\r\nstatic void HandleAlienShot()\r\n{\r\n // xref 0563\r\n if ((m.aShot.Status & SHOT_ACTIVE) != 0)\r\n {\r\n HandleAlienShotMove();\r\n return;\r\n }\r\n\r\n uint8_t shooting_c = (m.isrSplashTask == 0x04);\r\n uint8_t fire_enabled = m.enableAlienFire;\r\n\r\n if (shooting_c)\r\n {\r\n // Special case for the splash animation\r\n m.aShot.Status |= SHOT_ACTIVE;\r\n m.aShot.StepCnt++;\r\n\r\n return;\r\n }\r\n\r\n if (!fire_enabled)\r\n return;\r\n\r\n m.aShot.StepCnt = 0;\r\n\r\n {\r\n uint8_t steps = m.otherShot1;\r\n if (steps && steps <= m.aShotReloadRate) return;\r\n }\r\n\r\n {\r\n uint8_t steps = m.otherShot2;\r\n if (steps && steps <= m.aShotReloadRate) return;\r\n }\r\n\r\n uint8_t col = 0;\r\n\r\n if (m.aShot.Track == 0)\r\n {\r\n // xref 061b\r\n // Make a tracking shot, by finding the column that is above the player\r\n\r\n Word res = FindColumn(m.playerDesc.pos.x + 8); // find column over centre of player\r\n col = res.h; // res.l unused\r\n\r\n if (col >= 12)\r\n col = 11;\r\n }\r\n else\r\n {\r\n // xref 059c\r\n // Use the firing table pointer to pick the column, and advance it\r\n uint8_t* hl = word_to_ptr(m.aShot.CFir);\r\n col = *hl++;\r\n m.aShot.CFir = ptr_to_word(hl);\r\n }\r\n\r\n // xref 05a5\r\n uint8_t k = 0;\r\n uint8_t found = FindInColumn(&k, col);\r\n\r\n if (!found)\r\n return;\r\n\r\n uint8_t row_unused = 0;\r\n Word pixnum = GetAlienCoords(&row_unused, k);\r\n\r\n pixnum.x += 7;\r\n pixnum.y -= 10;\r\n\r\n m.aShot.Desc.pos = pixnum;\r\n m.aShot.Status |= SHOT_ACTIVE;\r\n m.aShot.StepCnt++;\r\n\r\n return;\r\n}\r\n\r\n// Handle moving the alien shot and some collision detection response.\r\n// Returns 1 if shot status needs to be set to blowing up, 0 if not.\r\nstatic int DoHandleAlienShotMove()\r\n{\r\n if (!CompXrToBeam(&m.aShot.Desc.pos.x))\r\n return 0;\r\n\r\n if (m.aShot.Status & SHOT_BLOWUP)\r\n {\r\n ShotBlowingUp();\r\n return 0;\r\n }\r\n\r\n // xref 05cf\r\n m.aShot.StepCnt++;\r\n EraseAlienShot();\r\n\r\n // Animate the shot\r\n uint8_t shotpic = m.aShot.Desc.spr.l + 3;\r\n\r\n if (shotpic > m.shotPicEnd)\r\n shotpic -= 12;\r\n\r\n m.aShot.Desc.spr.l = shotpic;\r\n m.aShot.Desc.pos.y = m.aShot.Desc.pos.y + m.alienShotDelta;\r\n DrawAlienShot();\r\n\r\n // xref 05f3\r\n uint8_t y = m.aShot.Desc.pos.y;\r\n\r\n if (y < 21)\r\n return 1;\r\n\r\n if (!m.collision)\r\n return 0;\r\n\r\n y = m.aShot.Desc.pos.y;\r\n\r\n // below or above players area ?\r\n if (y < 30 || y >= 39)\r\n return 1;\r\n\r\n if (!is_godmode())\r\n m.playerAlive = 0;\r\n\r\n return 1;\r\n}\r\n\r\n// Handle moving the alien shot and some collision detection response.\r\nstatic void HandleAlienShotMove()\r\n{\r\n // xref 05c1\r\n int exploded = DoHandleAlienShotMove();\r\n\r\n if (exploded)\r\n m.aShot.Status |= SHOT_BLOWUP;\r\n}\r\n\r\n// Find a live alien in the given column.\r\nstatic uint8_t FindInColumn(uint8_t *out, uint8_t col)\r\n{\r\n // xref 062f\r\n Word hl;\r\n\r\n hl.h = m.playerDataMSB;\r\n hl.l = col - 1;\r\n\r\n int found = 0;\r\n\r\n for (int i = 0; i < 5; ++i)\r\n {\r\n if (*word_to_ptr(hl))\r\n {\r\n found = 1;\r\n break;\r\n }\r\n\r\n hl.l += 11;\r\n }\r\n\r\n *out = hl.l;\r\n return found;\r\n}\r\n\r\n// Handle alien shot explosion animation\r\nstatic void ShotBlowingUp()\r\n{\r\n // xref 0644\r\n m.aShot.BlowCnt--;\r\n\r\n uint8_t blowcnt = m.aShot.BlowCnt;\r\n\r\n if (blowcnt == 3)\r\n {\r\n EraseAlienShot();\r\n m.aShot.Desc.spr = ptr_to_word(m.AlienShotExplodingSprite);\r\n\r\n // Offset the explision sprite from the shot by (-2,-2)\r\n\r\n m.aShot.Desc.pos.x--;\r\n m.aShot.Desc.pos.x--;\r\n\r\n m.aShot.Desc.pos.y--;\r\n m.aShot.Desc.pos.y--;\r\n\r\n m.aShot.Desc.n = 6;\r\n\r\n DrawAlienShot();\r\n return;\r\n }\r\n\r\n if (blowcnt)\r\n return;\r\n\r\n EraseAlienShot();\r\n}\r\n\r\n// Draw the active alien shot and do collision detection\r\nstatic void DrawAlienShot()\r\n{\r\n // xref 066c\r\n SprDesc desc = ReadDesc(&m.aShot.Desc);\r\n DrawSprCollision(&desc);\r\n return;\r\n}\r\n\r\n// Erase the active alien shot\r\nstatic void EraseAlienShot()\r\n{\r\n // xref 0675\r\n SprDesc desc = ReadDesc(&m.aShot.Desc);\r\n EraseShifted(&desc);\r\n return;\r\n}\r\n\r\n// Handles either the Squiggly shot or the Saucer, depending on the saucer timer.\r\n// See ProcessSquigglyShot for squiggly shot logic.\r\n// The bulk of this routine handles saucer movement, collision response, rendering\r\n// and scoring.\r\nstatic void GameObj4(uint8_t* unused)\r\n{\r\n // xref 0682\r\n if (m.shotSync != 2) return;\r\n\r\n if (m.saucerStart == 0)\r\n {\r\n ProcessSquigglyShot();\r\n return;\r\n }\r\n\r\n if (m.squShotData.StepCnt)\r\n {\r\n ProcessSquigglyShot();\r\n return;\r\n }\r\n\r\n if (!m.saucerActive)\r\n {\r\n if (m.numAliens < 8)\r\n {\r\n ProcessSquigglyShot();\r\n return;\r\n }\r\n\r\n m.saucerActive = 1;\r\n DrawSaucer();\r\n }\r\n\r\n uint8_t carry = CompXrToBeam(&m.saucerDesc.pos.x);\r\n\r\n if (!carry)\r\n return;\r\n\r\n if (!m.saucerHit)\r\n {\r\n uint8_t x = m.saucerDesc.pos.x;\r\n uint8_t dx = m.saucerDXr;\r\n\r\n m.saucerDesc.pos.x = x + dx;\r\n DrawSaucer();\r\n\r\n x = m.saucerDesc.pos.x;\r\n\r\n // check edges\r\n if (x < xpix(8))\r\n {\r\n RemoveSaucer();\r\n return;\r\n }\r\n\r\n if (x >= xpix(193))\r\n {\r\n RemoveSaucer();\r\n return;\r\n }\r\n\r\n return;\r\n }\r\n\r\n SoundBits3Off(0xfe); // turn off saucer sound\r\n\r\n m.saucerHitTime--;\r\n uint8_t timer = m.saucerHitTime;\r\n\r\n if (timer == 31)\r\n {\r\n // xref 074b\r\n // Turn on the sound and draw the saucer explosion\r\n uint8_t snd = m.soundPort5 | 16;\r\n m.soundPort5 = snd;\r\n SetSoundWithoutFleet(snd);\r\n\r\n m.saucerDesc.spr = ptr_to_word(m.SpriteSaucerExp);\r\n DrawSaucer();\r\n\r\n return;\r\n }\r\n\r\n if (timer == 24)\r\n {\r\n // xref 070c\r\n m.adjustScore = 1;\r\n\r\n // Get the score for the saucer which is set based on shots fired in GameObj0\r\n uint8_t score = *(word_to_ptr(m.sauScore));\r\n\r\n // Find the index of the score in the table\r\n int i = 0;\r\n for (i = 0; i < 4; ++i)\r\n {\r\n if (m.SauScrValueTab[i] == score)\r\n break;\r\n }\r\n\r\n // Use it to find the matching LSB for the score text, and set it in saucerDesc\r\n m.saucerDesc.spr.l = m.SauScrStrTab[i];\r\n\r\n // Multiply the score by 16 (i.e. bcd shift left one digit), to get 50,100,150,300 in BCD\r\n m.scoreDelta.u16 = score * 16;\r\n\r\n // Print the bonus score message, using pointer set in saucerDesc.spr above\r\n SprDesc desc = GetSaucerInfo();\r\n PrintMessage(desc.sc, word_to_ptr(desc.spr), 3);\r\n\r\n return;\r\n }\r\n\r\n // xref 06e8\r\n if (timer != 0)\r\n return;\r\n\r\n uint8_t snd = m.soundPort5 & 0xef;\r\n m.soundPort5 = snd;\r\n write_port(5, snd & 0x20);\r\n\r\n RemoveSaucer();\r\n}\r\n\r\n// Saucer cleanup tasks\r\nstatic void RemoveSaucer()\r\n{\r\n // xref 06f9\r\n SprDesc desc = ReadDesc(&m.saucerDesc);\r\n ClearSmallSprite(ConvToScr(desc.pos), desc.n, 0);\r\n RestoreFromROM(u16_to_ptr(SAUCER_ADDR), SAUCER_SIZE);\r\n SoundBits3Off(0xfe);\r\n}\r\n\r\n// Grab a copy of the saucer sprite descriptor, and set it up\r\n// for rendering before returning it.\r\nstatic SprDesc GetSaucerInfo()\r\n{\r\n // xref 0742\r\n SprDesc desc = ReadDesc(&m.saucerDesc);\r\n desc.sc = ConvToScr(desc.pos);\r\n return desc;\r\n}\r\n\r\n// Draw the player sprite\r\nstatic void DrawPlayer()\r\n{\r\n // xref 036f\r\n SprDesc desc = ReadDesc(&m.playerDesc);\r\n\r\n desc.sc = ConvToScr(desc.pos);\r\n DrawSimpSprite(&desc);\r\n\r\n m.playerHeader.TimerExtra = 0;\r\n}\r\n\r\n// Draw the saucer sprite\r\nstatic void DrawSaucer()\r\n{\r\n // xref 073c\r\n // xref 0742\r\n SprDesc desc = GetSaucerInfo();\r\n DrawSimpSprite(&desc);\r\n}\r\n\r\n// Wait for the player to press 1P or 2P, and then start the game\r\n// with the appropriate flags.\r\n// This loop is entered after the player has inserted a coin outside of game mode, see vblank_int()\r\nstatic void WaitForStart()\r\n{\r\n // xref 076e\r\n {\r\n // xref 1979\r\n // SuspendGameTasks\r\n DsableGameTasks();\r\n DrawNumCredits();\r\n PrintCreditLabel();\r\n }\r\n\r\n ClearPlayField();\r\n PrintMessage(xytosc(96, 152), m.MSG_PUSH, 4);\r\n\r\n while (TRUE)\r\n {\r\n timeslice();\r\n\r\n if ((m.numCoins - 1) != 0)\r\n {\r\n // Enough credits for either 1P or 2P start\r\n PrintMessage(xytosc(32, 128), m.MSG_1_OR_2PLAYERS_BUTTON, 20);\r\n\r\n // Handle 1P or 2P\r\n uint8_t inp = read_port(1);\r\n\r\n if (inp & 0x2) NewGame(1, 0x98, 0);\r\n if (inp & 0x4) NewGame(0, 0x99, 0);\r\n\r\n continue;\r\n }\r\n\r\n // Only enough credits for 1P start\r\n PrintMessage(xytosc(32, 128), m.MSG_ONLY_1PLAYER__BUTTON, 20);\r\n\r\n // Break if 1P start hit.\r\n if (read_port(1) & 0x4)\r\n break;\r\n }\r\n\r\n NewGame(0, 0x99, 0);\r\n}\r\n\r\n// Starts a new game, and runs the game loop.\r\n// This routine is entered via either the WaitForStart() loop after inserting coins\r\n// outside of game mode, or is entered after the player dies via player_death()\r\n// to continue the game.\r\n// is2p - set to true if 2P was pressed\r\n// cost - credits to deduct in bcd (0x99=1, 0x98=2 credits)\r\n// skip - used to skip certain parts of initialization, used for continue\r\nstatic void NewGame(uint8_t is2p, uint8_t cost, int skip)\r\n{\r\n // xref 0798\r\n // xref 079b\r\n int flags = ~skip;\r\n\r\n if (flags & INIT)\r\n {\r\n m.twoPlayers = is2p;\r\n\r\n {\r\n uint8_t unused_carry = 0;\r\n m.numCoins = bcd_add(m.numCoins, cost, &unused_carry);\r\n }\r\n\r\n DrawNumCredits();\r\n\r\n m.P1Scor.u16 = 0;\r\n m.P2Scor.u16 = 0;\r\n\r\n PrintP1Score();\r\n PrintP2Score();\r\n\r\n DsableGameTasks();\r\n\r\n m.gameMode = 1;\r\n\r\n m.playerStates.u16 = 0x0101; // Both players alive\r\n m.playerExtras.u16 = 0x0101; // Both players bonus available\r\n\r\n DrawStatus();\r\n DrawShieldP1();\r\n DrawShieldP2();\r\n\r\n uint8_t ships = GetShipsPerCred();\r\n\r\n m.p1ShipsRem = ships;\r\n m.p2ShipsRem = ships;\r\n\r\n InitAlienRacks();\r\n\r\n m.p1RackCnt = 0;\r\n m.p2RackCnt = 0;\r\n\r\n InitAliensP1();\r\n InitAliensP2();\r\n\r\n m.p1RefAlienPos = xytopix(24, 120);\r\n m.p2RefAlienPos = xytopix(24, 120);\r\n\r\n CopyRAMMirror();\r\n RemoveShip();\r\n }\r\n\r\n if (flags & PROMPT)\r\n {\r\n PromptPlayer();\r\n ClearPlayField();\r\n m.isrSplashTask = 0;\r\n }\r\n\r\n // xref 0804 top of new game loop\r\n while (TRUE)\r\n {\r\n if (flags & SHIELDS)\r\n {\r\n DrawBottomLine();\r\n\r\n if (m.playerDataMSB & 0x1)\r\n {\r\n RestoreShieldsP1();\r\n }\r\n else\r\n {\r\n RestoreShieldsP2();\r\n DrawBottomLine();\r\n }\r\n\r\n // xref 0814\r\n InitRack();\r\n }\r\n else\r\n {\r\n flags |= SHIELDS; // don\'t skip next time\r\n }\r\n\r\n EnableGameTasks();\r\n SoundBits3On(0x20);\r\n\r\n // xref 081f game loop\r\n\r\n while (TRUE)\r\n {\r\n PlrFireOrDemo();\r\n PlyrShotAndBump();\r\n CountAliens();\r\n\r\n AdjustScore();\r\n\r\n if (m.numAliens == 0)\r\n {\r\n HandleEndOfTurn();\r\n break;\r\n }\r\n\r\n AShotReloadRate();\r\n CheckAndHandleExtraShipAward();\r\n SpeedShots();\r\n ShotSound();\r\n\r\n if (! IsPlayerAlive())\r\n SoundBits3On(0x04); // Turn on player hit sound\r\n\r\n uint8_t w = FleetDelayExShip();\r\n write_port(6, w); // Feed the watchdog\r\n CtrlSaucerSound();\r\n }\r\n }\r\n}\r\n\r\n// Get reference alien velocity, position and pointer for the current player\r\nstatic uint8_t* GetRefAlienInfo(uint8_t *dxr, Word *pos)\r\n{\r\n // xref 0878\r\n *dxr = m.refAlienDelta.x;\r\n *pos = m.refAlienPos;\r\n return GetAlRefPtr();\r\n}\r\n\r\n// Get reference alien pointer for the current player\r\nstatic uint8_t* GetAlRefPtr()\r\n{\r\n // xref 0886\r\n return (m.playerDataMSB & 0x1) ? &m.p1RefAlienPos.l :\r\n &m.p2RefAlienPos.l;\r\n}\r\n\r\n// Print \"PLAY PLAYER\" and flash the score at 15 hz for 3 seconds\r\n// This is done upon starting a NewGame in 1P mode, or at the start\r\n// of every turn in 2P mode.\r\nstatic void PromptPlayer()\r\n{\r\n // xref 088d\r\n\r\n // \"PLAY PLAYER<1>\"\r\n PrintMessage(xytosc(56,136), m.MSG_PLAY_PLAYER_1_, 14);\r\n\r\n // replace <1> with <2>\r\n if ((m.playerDataMSB & 0x1) == 0)\r\n DrawChar(xytosc(152, 136), 0x1c);\r\n\r\n m.isrDelay = 176; // 3 sec delay\r\n\r\n // xref 08a9\r\n while (TRUE)\r\n {\r\n timeslice();\r\n uint8_t isrtick = m.isrDelay;\r\n\r\n if (isrtick == 0)\r\n return;\r\n\r\n // Flash player score every 4 isrs\r\n if (isrtick & 0x4)\r\n {\r\n // xref 08bc\r\n Word sc = (m.playerDataMSB & 0x1) ? xytosc(24,224) : xytosc(168,224);\r\n ClearSmallSprite(sc, 32, 0);\r\n continue;\r\n }\r\n\r\n DrawScore(GetScoreDescriptor());\r\n }\r\n}\r\n\r\n// DIP5 and DIP3 control the number of extra lives the player starts with.\r\n// DIP5 and DIP3 are wired into bits 1 and 0 of port 2 respectively.\r\n//\r\n// When read together as a two digit binary number, this is meant to be\r\n// interpreted as the number of extra lives above the default of 3 that\r\n// the player gets.\r\n//\r\n// 0 0 - 3 lives\r\n// 0 1 - 4 lives\r\n// 1 0 - 5 lives\r\n// 1 1 - 6 lives\r\nstatic uint8_t GetShipsPerCred()\r\n{\r\n // xref 08d1\r\n return (read_port(2) & (DIP5_SHIPS2 | DIP3_SHIPS1)) + 3;\r\n}\r\n\r\n// Increase alien shot speed when there are less than nine aliens on screen\r\nstatic void SpeedShots()\r\n{\r\n // xref 08d8\r\n if (m.numAliens >= 9)\r\n return;\r\n\r\n m.alienShotDelta = -5; // from -4 to -5\r\n}\r\n\r\n// Prints a text message (msg, n) on the screen at pos\r\n// Used to print all the splash screen text, and other game messages\r\nstatic void PrintMessage(Word sc, uint8_t* msg, size_t n)\r\n{\r\n // xref 08f3\r\n // DebugMessage(sc, msg, n);\r\n\r\n for (size_t i = 0; i < n; ++i)\r\n {\r\n uint8_t c = msg[i];\r\n sc = DrawChar(sc, c);\r\n }\r\n}\r\n\r\n// Draw a text character c on the screen at pos\r\n// Used by PrintMessage()\r\nstatic Word DrawChar(Word sc, uint8_t c)\r\n{\r\n // xref 08ff\r\n SprDesc desc;\r\n desc.sc = sc;\r\n desc.spr = u16_to_word(0x1e00 + c*8);\r\n desc.n = 8;\r\n\r\n return DrawSimpSprite(&desc);\r\n}\r\n\r\n// Timing logic that controls when the saucer appears (Every 25.6 secs)\r\n// Called via vblank_int()\r\nstatic void TimeToSaucer()\r\n{\r\n // xref 0913\r\n // No ticking until alien rack has dropped down a bit\r\n if (m.refAlienPos.y >= 120)\r\n return;\r\n\r\n uint16_t timer = m.saucerTimer.u16;\r\n\r\n if (timer == 0)\r\n {\r\n timer = 0x600; // reset timer to 1536 game loops (25.6s)\r\n m.saucerStart = 1;\r\n }\r\n\r\n m.saucerTimer.u16 = timer - 1;\r\n}\r\n\r\n// Get number of lives for the current player\r\nstatic uint8_t GetNumberOfShips(uint8_t* *ptr)\r\n{\r\n // xref 092e\r\n *ptr = (GetPlayerDataPtr() + 0xff);\r\n return *(*ptr);\r\n}\r\n\r\n// Award the one and only bonus life if the player\'s score is high enough,\r\n// and fix up the lives indicators to reflect that.\r\nstatic void CheckAndHandleExtraShipAward()\r\n{\r\n // xref 0935\r\n if (*(CurPlyAlive() - 2) == 0)\r\n return;\r\n\r\n // Bonus dip bit - award at 1000 or 1500\r\n uint8_t b = (read_port(2) & DIP6_BONUS) ? 0x10 : 0x15;\r\n uint8_t score_msb = *(GetScoreDescriptor() + 1);\r\n\r\n // score not high enough for bonus yet\r\n if (score_msb < b)\r\n return;\r\n\r\n uint8_t* nships_ptr;\r\n GetNumberOfShips(&nships_ptr);\r\n\r\n // Award the bonus life\r\n (*nships_ptr)++;\r\n\r\n int nships = *nships_ptr;\r\n\r\n SprDesc desc;\r\n desc.sc = xytosc(8 + 16 * nships, 8);\r\n desc.spr = ptr_to_word(m.PLAYER_SPRITES);\r\n desc.n = 16;\r\n\r\n DrawSimpSprite(&desc);\r\n\r\n PrintNumShips(nships+1);\r\n *(CurPlyAlive() - 2) = 0; // Flag extra ship has been awarded\r\n m.extraHold = 0xff; // Handle Extra-ship sound\r\n SoundBits3On(0x10);\r\n}\r\n\r\n// Lookup score for alien based on the given row\r\nstatic uint8_t* AlienScoreValue(uint8_t row)\r\n{\r\n // xref 097c\r\n uint8_t si = 0;\r\n\r\n if (row < 2) si = 0;\r\n else if (row < 4) si = 1;\r\n else si = 2;\r\n\r\n return (m.AlienScores + si);\r\n}\r\n\r\n// Add the score delta to the current player\'s score, and draw it.\r\n// Called as part of the game loop.\r\n//\r\n// scoreDelta is modified in two places:\r\n// PlayerShotHit() upon killing an alien (main thread)\r\n// GameObj4() upon hitting the saucer (either vblank or mid depending on saucer x pos)\r\nstatic void AdjustScore()\r\n{\r\n // xref 0988\r\n uint8_t* sptr = GetScoreDescriptor();\r\n if (m.adjustScore == 0) return;\r\n\r\n m.adjustScore = 0;\r\n\r\n Word adj = m.scoreDelta;\r\n uint8_t carry = 0;\r\n\r\n Word score;\r\n\r\n score.l = *(sptr);\r\n score.l = bcd_add(score.l, adj.l, &carry);\r\n *sptr = score.l;\r\n\r\n score.h = *(sptr+1);\r\n score.h = bcd_add(score.h, adj.h, &carry);\r\n *(sptr+1) = score.h;\r\n\r\n Word sc;\r\n sc.l = *(sptr+2);\r\n sc.h = *(sptr+3);\r\n\r\n Print4Digits(sc, score);\r\n}\r\n\r\n// Print 4 digits using the bcd values in val.h and val.l\r\n// Called via DrawScore and AdjustScore\r\nstatic void Print4Digits(Word sc, Word val)\r\n{\r\n // xref 09ad\r\n sc = DrawHexByte(sc, val.h);\r\n sc = DrawHexByte(sc, val.l);\r\n}\r\n\r\n// Draw the the hi and lo nibble of the bcd value in c at sc\r\nstatic Word DrawHexByte(Word sc, uint8_t c)\r\n{\r\n // xref 09b2\r\n sc = DrawHexByteSub(sc, c >> 4);\r\n sc = DrawHexByteSub(sc, c & 0xf);\r\n return sc;\r\n}\r\n\r\n// Draw the digit in c at sc\r\nstatic Word DrawHexByteSub(Word sc, uint8_t c)\r\n{\r\n // xref 09c5\r\n return DrawChar(sc, c + 0x1a);\r\n}\r\n\r\n// Return a pointer to the score info for the current player\r\nstatic uint8_t* GetScoreDescriptor()\r\n{\r\n // xref 09ca\r\n return (m.playerDataMSB & 0x1) ? &m.P1Scor.l : &m.P2Scor.l;\r\n}\r\n\r\n// Clear the play field in the center of the screen.\r\n// Horizontally, the play field is the full width of the screen.\r\n// Vertically, the play field is the area above the lives and credits (16 pixels)\r\n// and below the scores (32 pixels).\r\nstatic void ClearPlayField()\r\n{\r\n // xref 09d6\r\n uint8_t* screen = m.vram;\r\n\r\n for (int x = 0; x < 224; ++x)\r\n {\r\n screen += 2;\r\n\r\n for (int b = 0; b < 26; ++b)\r\n *screen++ = 0;\r\n\r\n screen += 4;\r\n }\r\n}\r\n\r\n// Called from the game loop when the player has killed all aliens in the rack.\r\nstatic void HandleEndOfTurn()\r\n{\r\n // xref 09ef\r\n HandleEndOfTurnSub(); // wait for player to finish dying if necessary\r\n\r\n m.gameTasksRunning = 0;\r\n ClearPlayField();\r\n\r\n uint8_t pnum = m.playerDataMSB;\r\n\r\n CopyRAMMirror();\r\n\r\n m.playerDataMSB = pnum;\r\n pnum = m.playerDataMSB; // redundant load\r\n\r\n uint8_t rack_cnt = 0;\r\n\r\n uint8_t* rcptr = u16_to_ptr(pnum << 8 | 0xfe);\r\n rack_cnt = (*rcptr % 8) + 1;\r\n *rcptr = rack_cnt;\r\n\r\n // Starting Y coord for rack for new level\r\n uint8_t y = m.AlienStartTable[rack_cnt-1];\r\n\r\n uint8_t* refy = u16_to_ptr(pnum << 8 | 0xfc);\r\n uint8_t* refx = refy + 1;\r\n\r\n *refy = y;\r\n *refx = 56;\r\n\r\n if (!(pnum & 0x1))\r\n {\r\n m.soundPort5 = 0x21; // start fleet with first sound\r\n DrawShieldP2();\r\n InitAliensP2();\r\n }\r\n else\r\n {\r\n DrawShieldP1();\r\n InitAliensP1();\r\n }\r\n}\r\n\r\n// Called at start of HandleEndOfTurnSub() to handle the\r\n// case of the player dying at the end of turn.\r\n// (i.e. last alien and player both kill each other)\r\nstatic void HandleEndOfTurnSub()\r\n{\r\n // xref 0a3c\r\n\r\n if (IsPlayerAlive())\r\n {\r\n m.isrDelay = 48; // wait up to 3/4 of a sec\r\n\r\n do\r\n {\r\n // xref 0a47\r\n timeslice(); // spin\r\n if (m.isrDelay == 0)\r\n return;\r\n\r\n } while (IsPlayerAlive());\r\n }\r\n\r\n // If player is not alive, wait for resurrection\r\n while (!IsPlayerAlive())\r\n {\r\n // xref 0a52\r\n timeslice(); // spin\r\n }\r\n}\r\n\r\n// Returns 1 if player is alive, 0 otherwise\r\nstatic uint8_t IsPlayerAlive()\r\n{\r\n // xref 0a59\r\n return m.playerAlive == 0xff;\r\n}\r\n\r\n// Called as part of the player bullet collision response in PlayerShotHit()\r\n// when the player bullet kills an alien.\r\nstatic void ScoreForAlien(uint8_t row)\r\n{\r\n // xref 0a5f\r\n if (!m.gameMode)\r\n return;\r\n\r\n SoundBits3On(0x08);\r\n\r\n uint8_t score = *(AlienScoreValue(row));\r\n\r\n m.scoreDelta.h = 0;\r\n m.scoreDelta.l = score;\r\n m.adjustScore = 1;\r\n}\r\n\r\n// Companion routine to SplashSprite\r\n// Called from the main thread to initiate and wait for splash animations (CCOIN / PLAy).\r\nstatic void Animate()\r\n{\r\n // xref 0a80\r\n // Directs ISRSplTasks() (in vblank_int()) to call SplashSprite()\r\n m.isrSplashTask = 2;\r\n\r\n // Spin until sprite in animation reaches its target position\r\n do {\r\n // xref 0a85\r\n write_port(6, 2); // feed watchdog and spin\r\n }\r\n while (!m.splashReached);\r\n\r\n // Directs ISRSplTasks() to do nothing\r\n m.isrSplashTask = 0;\r\n}\r\n\r\n// Prints the animated text messages (such as \"PLAY\" \"SPACE INVADERS\"),\r\n// by drawing the characters that make up the message with a short\r\n// delay between them. (7 frames per character)\r\nstatic Word PrintMessageDel(Word sc, uint8_t* str, uint8_t n)\r\n{\r\n // xref 0a93\r\n // DebugMessage(sc, str, n);\r\n\r\n for (int i = 0; i < n; ++i)\r\n {\r\n sc = DrawChar(sc, str[i]);\r\n m.isrDelay = 7;\r\n while (m.isrDelay != 1) { timeslice(); } // spin\r\n }\r\n\r\n return sc;\r\n}\r\n\r\n// Need for the shooting C in CCOIN animation.\r\n// Initiated in AnimateShootingSplashAlien(), and called\r\n// via ISRSplTasks() during vblank_int()\r\nstatic void SplashSquiggly()\r\n{\r\n // xref 0aab\r\n // this works because this is the last game object.\r\n RunGameObjs(u16_to_ptr(SQUIGGLY_SHOT_ADDR));\r\n}\r\n\r\n// Wait for approximately one second. (64 vblanks).\r\nstatic void OneSecDelay()\r\n{\r\n // xref 0ab1\r\n WaitOnDelay(64);\r\n}\r\n\r\n// Wait for approximately two seconds. (128 vblanks).\r\nstatic void TwoSecDelay()\r\n{\r\n // xref 0ab6\r\n WaitOnDelay(128);\r\n}\r\n\r\n// Runs the game objects in demo mode to attract players.\r\n// Initiated from the main thread and called\r\n// via ISRSplTasks() during vblank_int()\r\nstatic void SplashDemo()\r\n{\r\n // xref 0abb\r\n // xref 0072\r\n m.shotSync = m.rolShotHeader.TimerExtra;\r\n\r\n DrawAlien();\r\n RunGameObjs(u16_to_ptr(PLAYER_ADDR)); // incl player\r\n TimeToSaucer();\r\n}\r\n\r\n// Runs the appropriate splash screen task (from vblank_int())\r\nstatic void ISRSplTasks()\r\n{\r\n // xref 0abf\r\n switch (m.isrSplashTask)\r\n {\r\n case 1: SplashDemo(); break; // Attract players with game demo\r\n case 2: SplashSprite(); break; // Moves a sprite to a target location for an animation\r\n case 4: SplashSquiggly(); break; // Run an alien shot for an animation ( CCOIN )\r\n }\r\n}\r\n\r\n// Print an animated message in the center of the screen.\r\nstatic void MessageToCenterOfScreen(uint8_t* str)\r\n{\r\n // xref 0acf\r\n PrintMessageDel(xytosc(56,160), str, 0x0f);\r\n}\r\n\r\n// Wait for n vblank interrupts to occur, using m.isrDelay\r\nstatic void WaitOnDelay(uint8_t n)\r\n{\r\n // xref 0ad7\r\n // Wait on ISR counter to reach 0\r\n m.isrDelay = n;\r\n\r\n while (m.isrDelay != 0) { timeslice(); } // spin\r\n}\r\n\r\n// Copy src into the splash animation structure.\r\n// The four animations copied this way are\r\n//\r\n// (for PLAy animation)\r\n//\r\n// 0x1a95 - Move alien left to grab y\r\n// 0x1bb0 - Move alien (with y) to right edge\r\n// 0x1fc9 - Bring alien back (with Y) to message\r\n//\r\n// (for CCOIN animation)\r\n//\r\n// 0x1fd5 - Move alien to point above extra \'C\'\r\nstatic void IniSplashAni(uint8_t* src)\r\n{\r\n // xref 0ae2\r\n BlockCopy(&m.splashAnForm, src, 12);\r\n}\r\n\r\n// Called during (splash screens) to do some miscellaneous tasks\r\n// a) Player shot collision response\r\n// b) Detecting and handling the alien rack bumping the screen edges\r\n// c) Checking for TAITO COP input sequence\r\nstatic uint8_t CheckPlyrShotAndBump()\r\n{\r\n // xref 0bf1\r\n PlyrShotAndBump();\r\n CheckHiddenMes();\r\n return 0xff;\r\n}\r\n\r\n// Erases a sprite by clearing the four bytes it\r\n// could possibly be in.\r\nstatic void EraseSimpleSprite(Word pos, uint8_t n)\r\n{\r\n // xref 1424\r\n Word sc = CnvtPixNumber(pos);\r\n\r\n for (int i = 0; i < n; ++i, sc.u16 += xysc(1, 0))\r\n {\r\n uint8_t* screen = word_to_ptr(sc);\r\n\r\n *screen++ = 0;\r\n *screen++ = 0;\r\n }\r\n}\r\n\r\n// Draws a non shifted sprite from desc->spr horizontally\r\n// across the screen at desc->pos for desc->n bytes.\r\n// Each byte of the sprite is a vertical 8 pixel strip\r\nstatic Word DrawSimpSprite(SprDesc *desc)\r\n{\r\n // xref 1439\r\n uint8_t* screen = word_to_ptr(desc->sc);\r\n uint8_t* sprite = word_to_ptr(desc->spr);\r\n\r\n for (size_t i = 0; i < desc->n; ++i, screen += xysc(1, 0))\r\n *screen = sprite[i];\r\n\r\n return ptr_to_word(screen);\r\n}\r\n\r\n// Using pixnum, set the shift count on the hardware shifter\r\n// and return the screen coordinates for rendering\r\nstatic Word CnvtPixNumber(Word pos)\r\n{\r\n // xref 1474\r\n write_port(2, (pos.u16 & 0xff) & 0x07);\r\n return ConvToScr(pos);\r\n}\r\n\r\n// Draw a shifted sprite to the screen, blending with screen contents\r\nstatic void DrawShiftedSprite(struct SprDesc *desc)\r\n{\r\n // xref 1400\r\n DrawSpriteGeneric(desc, OP_BLEND);\r\n}\r\n\r\n// Erase a shifted sprite from the screen, zeroing screen contents\r\nstatic void EraseShifted(struct SprDesc *desc)\r\n{\r\n // xref 1452\r\n DrawSpriteGeneric(desc, OP_ERASE);\r\n}\r\n\r\n// Draw a shifted sprite to the screen, blending with screen contents,\r\n// and detect if drawn sprite collided with existing pixels\r\nstatic void DrawSprCollision(struct SprDesc *desc)\r\n{\r\n // xref 1491\r\n DrawSpriteGeneric(desc, OP_COLLIDE);\r\n}\r\n\r\n// Draw a shifted sprite to the screen, overwriting screen contents.\r\nstatic void DrawSprite(struct SprDesc *desc)\r\n{\r\n // xref 15d3\r\n DrawSpriteGeneric(desc, OP_BLIT);\r\n}\r\n\r\n// Generic sprite drawing routine for shifted spries\r\n// desc->spr - source pointer\r\n// desc->pixnum - pixel position to draw at\r\n// desc->n - width\r\n// op - erase | blit | blend | collide\r\nstatic void DrawSpriteGeneric(struct SprDesc *desc, int op)\r\n{\r\n Word sc = CnvtPixNumber(desc->pos);\r\n uint8_t* sprite = word_to_ptr(desc->spr);\r\n\r\n if (op == OP_COLLIDE)\r\n m.collision = 0;\r\n\r\n for (int i = 0; i < desc->n; ++i, sc.u16 += xysc(1,0))\r\n {\r\n uint8_t* screen = word_to_ptr(sc);\r\n\r\n uint8_t shift_in[2];\r\n shift_in[0] = sprite[i];\r\n shift_in[1] = 0;\r\n\r\n for (int j = 0; j < 2; ++j)\r\n {\r\n write_port(4, shift_in[j]); // write into shift reg\r\n uint8_t shifted = read_port(3); // get the shifted pixels (shift based on pix num)\r\n\r\n if (op == OP_COLLIDE && (shifted & *screen))\r\n m.collision = 1;\r\n\r\n if (op == OP_COLLIDE || op == OP_BLEND)\r\n *screen = shifted | *screen;\r\n else if (op == OP_BLIT)\r\n *screen = shifted;\r\n else if (op == OP_ERASE)\r\n *screen = (shifted ^ 0xff) & *screen;\r\n\r\n screen++;\r\n }\r\n }\r\n}\r\n\r\n// Repeat (width n) the pixel strip in byte v horizontally across the screen\r\nstatic Word ClearSmallSprite(Word sc, uint8_t n, uint8_t v)\r\n{\r\n // xref 14cb\r\n for (int i = 0; i < n; ++i, sc.u16 += xysc(1,0))\r\n *(word_to_ptr(sc)) = v;\r\n\r\n return sc;\r\n}\r\n\r\n// Player bullet collision response\r\nstatic void PlayerShotHit()\r\n{\r\n // xref 14d8\r\n uint8_t status = m.plyrShotStatus;\r\n\r\n // if alien explosion state, bail\r\n if (status == 5)\r\n return;\r\n\r\n // if not normal movement, bail\r\n if (status != 2)\r\n return;\r\n\r\n // Get the Y coord\r\n uint8_t shoty = m.playerShotDesc.pos.y;\r\n\r\n if (shoty >= 216)\r\n {\r\n // missed and hit top of screen\r\n m.plyrShotStatus = 3;\r\n m.alienIsExploding = 0;\r\n SoundBits3Off(0xf7);\r\n return;\r\n }\r\n\r\n // xref 14ea\r\n if (!m.alienIsExploding)\r\n return;\r\n\r\n if (shoty >= 206)\r\n {\r\n // hit the saucer\r\n // xref 1579\r\n m.saucerHit = 1;\r\n m.plyrShotStatus = 4;\r\n\r\n // xref 154a\r\n m.alienIsExploding = 0;\r\n SoundBits3Off(0xf7);\r\n return;\r\n }\r\n\r\n shoty += 6;\r\n\r\n {\r\n uint8_t refy = m.refAlienPos.y;\r\n\r\n // refy can wrap around, if the topmost alien row gets near the bottom of the screen\r\n // in usual play, refy will be < 144.\r\n if ((refy < 144) && (refy >= shoty))\r\n {\r\n // hit the shields\r\n m.plyrShotStatus = 3;\r\n m.alienIsExploding = 0;\r\n SoundBits3Off(0xf7);\r\n return;\r\n }\r\n }\r\n\r\n // xref 1504\r\n // Get here if player shot hits an alien or an alien shot.\r\n // There is a subtle bug here, see CodeBug1 in CA\r\n Word res = FindRow(shoty);\r\n\r\n uint8_t row = res.h;\r\n uint8_t ay = res.l;\r\n\r\n res = FindColumn(m.playerShotDesc.pos.x);\r\n\r\n uint8_t col = res.h;\r\n uint8_t ax = res.l;\r\n\r\n m.expAlien.pos = u8_u8_to_word(ax, ay);\r\n m.plyrShotStatus = 5;\r\n\r\n uint8_t* alienptr = GetAlienStatPtr(row, col);\r\n\r\n if (*alienptr == 0)\r\n {\r\n // If alien is dead, then the player shot must have hit an alien shot\r\n m.plyrShotStatus = 3;\r\n m.alienIsExploding = 0;\r\n SoundBits3Off(0xf7);\r\n return;\r\n }\r\n\r\n // Kill the alien\r\n *alienptr = 0;\r\n ScoreForAlien(row);\r\n SprDesc desc = ReadDesc(&m.expAlien);\r\n DrawSprite(&desc);\r\n m.expAlienTimer = 16;\r\n}\r\n\r\n// Counts the number of 16s between *v and target.\r\n// This is roughly (tgt - *v) / 16.\r\nstatic uint8_t Cnt16s(uint8_t *v, uint8_t tgt)\r\n{\r\n // xref 1554\r\n uint8_t n = 0;\r\n\r\n if ((*v) >= tgt)\r\n {\r\n do\r\n {\r\n // wrap ref\r\n n++;\r\n (*v) += 16;\r\n\r\n } while ((*v) & 0x80);\r\n }\r\n\r\n while ((*v) < tgt)\r\n {\r\n (*v) += 16;\r\n n++;\r\n }\r\n\r\n return n;\r\n}\r\n\r\n// Find alien row given y pos\r\nstatic Word FindRow(uint8_t y)\r\n{\r\n // xref 1562\r\n uint8_t ry = m.refAlienPos.y;\r\n uint8_t rnum = Cnt16s(&ry, y) - 1;\r\n uint8_t coord = (ry - 16);\r\n\r\n return u8_u8_to_word(rnum, coord);\r\n}\r\n\r\n// Find alien column given x pos\r\nstatic Word FindColumn(uint8_t x)\r\n{\r\n // xref 156f\r\n uint8_t rx = m.refAlienPos.x;\r\n uint8_t cnum = Cnt16s(&rx, x);\r\n uint8_t coord = (rx - 16);\r\n\r\n return u8_u8_to_word(cnum, coord);\r\n}\r\n\r\n// Return a pointer to the alien status for the current player\r\n// given the row and column of the alien\r\nstatic uint8_t* GetAlienStatPtr(uint8_t row, uint8_t col)\r\n{\r\n // xref 1581\r\n // row is 0 based\r\n // col is 1 based\r\n\r\n uint8_t idx = row * 11 + (col - 1);\r\n return u16_to_ptr(m.playerDataMSB << 8 | idx);\r\n}\r\n\r\n// Change alien deltaX and deltaY when alien rack bumps edges\r\nstatic void RackBump()\r\n{\r\n // xref 1597\r\n // Change alien deltaX and deltaY when rack bumps edges\r\n uint8_t dx = 0;\r\n uint8_t dir = 0;\r\n\r\n if (m.rackDirection == 0)\r\n {\r\n // xref 159e check right edge\r\n if (!RackBumpEdge(xytosc(213,32)))\r\n return;\r\n\r\n dx = -2;\r\n dir = 1; // rack now moving left\r\n }\r\n else\r\n {\r\n // check left edge\r\n if (!RackBumpEdge(xytosc(9,32)))\r\n return;\r\n\r\n // rack now moving right\r\n // inline 18f1\r\n dx = m.numAliens == 1 ? 3 : 2; // go faster if only one alien remaining\r\n dir = 0; // rack now moving right\r\n }\r\n\r\n m.rackDirection = dir;\r\n m.refAlienDelta.x = dx;\r\n m.refAlienDelta.y = m.rackDownDelta;\r\n}\r\n\r\n// Check 23 bytes vertically up from sc for pixels.\r\n// Used by RackBump to detect whether alien rack is hitting the edges of the play area.\r\nstatic uint8_t RackBumpEdge(Word sc)\r\n{\r\n // xref 15c5\r\n uint8_t* screen = word_to_ptr(sc);\r\n\r\n for (int i = 0; i < 23; ++i)\r\n {\r\n timeslice();\r\n\r\n // found some pixels\r\n if (*screen++)\r\n return 1;\r\n }\r\n\r\n return 0;\r\n}\r\n\r\n// Count the number of live aliens for the current player\r\nstatic void CountAliens()\r\n{\r\n // xref 15f3\r\n uint8_t* iter = GetPlayerDataPtr(); // Get active player descriptor\r\n uint8_t n = 0;\r\n\r\n for (int i = 0; i < 55; ++i)\r\n {\r\n timeslice();\r\n\r\n if (*iter++ != 0)\r\n ++n;\r\n }\r\n\r\n m.numAliens = n;\r\n\r\n if (n != 1)\r\n return;\r\n\r\n // Apparently unused\r\n *(u16_to_ptr(0x206b)) = 1;\r\n}\r\n\r\n// Return a pointer the the current player\'s RAM area\r\nstatic uint8_t* GetPlayerDataPtr()\r\n{\r\n // xref 1611\r\n return u16_to_ptr(m.playerDataMSB << 8);\r\n}\r\n\r\n// Handles player firing in game and demo mode.\r\n// In both cases, nothing happens if there is a shot on the screen.\r\n// In game mode, reads the fire button and debounces to detect press.\r\n// In demo mode, initiates a shot always, and consumes the next movement command from the buffer.\r\nstatic void PlrFireOrDemo()\r\n{\r\n // xref 1618\r\n if (m.playerAlive != 0xff)\r\n return;\r\n\r\n uint8_t timer_hi = m.playerHeader.TimerMSB;\r\n uint8_t timer_lo = m.playerHeader.TimerLSB;\r\n\r\n // Return if not ready yet.\r\n if (timer_lo | timer_hi)\r\n return;\r\n\r\n // Return if player has a shot still on screen\r\n if (m.plyrShotStatus)\r\n return;\r\n\r\n // xref 162b\r\n if (m.gameMode)\r\n {\r\n // Handle fire button reading and debouncing\r\n uint8_t prev = m.fireBounce;\r\n uint8_t cur = (ReadInputs() & 0x10) != 0;\r\n\r\n if (prev == cur)\r\n return;\r\n\r\n if (cur)\r\n m.plyrShotStatus = 1; // Flag shot active\r\n\r\n m.fireBounce = cur;\r\n return;\r\n }\r\n else\r\n {\r\n // Demo player constantly fires\r\n\r\n m.plyrShotStatus = 1;\r\n\r\n // Consume demo command from circular buffer 0x1f74 <-> 0x1f7e\r\n //\r\n // DemoCommands:\r\n //; (1=Right, 2=Left)\r\n // 74 75 76 77 78 79 7A 7B 7C 7D 7E\r\n //1F74: 01 01 00 00 01 00 02 01 00 02 01\r\n\r\n Word iter = u16_to_word(m.demoCmdPtr.u16 + 1);\r\n\r\n if (iter.l >= 0x7e)\r\n iter.l = 0x74; // wrap\r\n\r\n m.demoCmdPtr = iter;\r\n m.nextDemoCmd = *(word_to_ptr(iter));\r\n }\r\n}\r\n\r\n// Called when all players are dead in game mode\r\n// Prints \"GAME OVER\" and sets things up to reenter splash screens.\r\nstatic void GameOver()\r\n{\r\n // xref 16c9\r\n\r\n PrintMessageDel(xytosc(72,192), m.MSG_GAME_OVER__PLAYER___, 10);\r\n TwoSecDelay();\r\n ClearPlayField();\r\n m.gameMode = 0;\r\n write_port(5, 0); // all sound off\r\n EnableGameTasks();\r\n main_init(1);\r\n assert(FALSE); // won\'t return\r\n}\r\n\r\n// Called when the player loses the game upon an alien reaching the bottom\r\nstatic void on_invaded()\r\n{\r\n // xref 16ea\r\n m.playerAlive = 0;\r\n\r\n do {\r\n PlayerShotHit();\r\n SoundBits3On(4);\r\n }\r\n while (!IsPlayerAlive());\r\n\r\n DsableGameTasks();\r\n DrawNumShipsSub(xytosc(24, 8)); // 19fa\r\n PrintNumShips(0);\r\n\r\n // xref 196b\r\n SoundBits3Off(0xfb);\r\n player_death(1);\r\n\r\n // won\'t return\r\n assert(FALSE);\r\n}\r\n\r\n// Increases the difficulty of the game as the player\'s score gets higher by\r\n// decreasing time between alien shots.\r\nstatic void AShotReloadRate()\r\n{\r\n // xref 170e\r\n uint8_t score_hi = *(GetScoreDescriptor() + 1);\r\n\r\n // Uses these tables, in decimal\r\n // 02 16 32 48 (AReloadScoreTab)\r\n // 48 16 11 08 07 (ShotReloadRate)\r\n\r\n int i = 0;\r\n\r\n // xref 171c\r\n for (i = 0; i < 4; ++i)\r\n {\r\n if (m.AReloadScoreTab[i] >= score_hi)\r\n break;\r\n }\r\n\r\n // xref 1727\r\n m.aShotReloadRate = m.ShotReloadRate[i];\r\n}\r\n\r\n// Turn player shot sound on/off depending on m.plyrShotStatus\r\nstatic void ShotSound()\r\n{\r\n // xref 172c\r\n if (m.plyrShotStatus == 0)\r\n {\r\n SoundBits3Off(0xfd);\r\n return;\r\n }\r\n\r\n SoundBits3On(0x02);\r\n}\r\n\r\n// Ticks down and reset the timers that determines when the alien sound is changed.\r\n// The sound is actually changed in FleetDelayExShip()\r\nstatic void TimeFleetSound()\r\n{\r\n // xref 1740\r\n if (--m.fleetSndHold == 0)\r\n DisableFleetSound();\r\n\r\n if (m.playerOK == 0)\r\n {\r\n DisableFleetSound();\r\n return;\r\n }\r\n\r\n if (--m.fleetSndCnt != 0)\r\n return;\r\n\r\n write_port(5, m.soundPort5);\r\n\r\n if (m.numAliens == 0)\r\n {\r\n DisableFleetSound();\r\n return;\r\n }\r\n\r\n m.fleetSndCnt = m.fleetSndReload;\r\n m.changeFleetSnd = 0x01;\r\n m.fleetSndHold = 0x04;\r\n}\r\n\r\n// Turn off the fleet movement sounds\r\nstatic void DisableFleetSound()\r\n{\r\n // xref 176d\r\n SetSoundWithoutFleet(m.soundPort5);\r\n}\r\n\r\n// Mask fleet movement sound off from given byte, and use it to set sound\r\nstatic void SetSoundWithoutFleet(uint8_t v)\r\n{\r\n // xref 1770\r\n write_port(5, v & 0x30);\r\n}\r\n\r\n// Handles rotating the fleet sounds if it is time to do so, and determines the\r\n// delay between them using a table keyed by the number of live aliens.\r\n// The bonus ship sound is also handled here.\r\nstatic uint8_t FleetDelayExShip()\r\n{\r\n // xref 1775\r\n // The two sound tables (in decimal):\r\n // [ 50 43 36 28 22 17 13 10 08 07 06 05 04 03 02 01 ] (soundDelayKey)\r\n // [ 52 46 39 34 28 24 21 19 16 14 13 12 11 09 07 05 ] (soundDelayValue)\r\n\r\n uint8_t snd_b = 0;\r\n uint8_t snd_a = 0;\r\n\r\n if (m.changeFleetSnd)\r\n {\r\n uint8_t n = m.numAliens;\r\n\r\n // xref 1785\r\n int i = 0;\r\n\r\n for (i = 0; i < 16; ++i)\r\n {\r\n if (n >= m.soundDelayKey[i])\r\n break;\r\n }\r\n\r\n m.fleetSndReload = m.soundDelayValue[i];\r\n\r\n snd_b = m.soundPort5 & 0x30; // Mask off all fleet movement sounds\r\n snd_a = m.soundPort5 & 0x0f; // Mask off all except fleet sounds\r\n\r\n // Rotate to next sound and wrap if neccessary\r\n snd_a = (snd_a << 1) | (snd_a >> 7);\r\n\r\n if (snd_a == 0x10)\r\n snd_a = 0x01;\r\n\r\n m.soundPort5 = snd_a | snd_b;\r\n m.changeFleetSnd = 0;\r\n }\r\n\r\n // xref 17aa\r\n if (--m.extraHold == 0)\r\n SoundBits3Off(0xef);\r\n\r\n return snd_a;\r\n}\r\n\r\n// Read the input port corresponding to the current player.\r\nstatic uint8_t ReadInputs()\r\n{\r\n // xref 17c0\r\n uint8_t port = m.playerDataMSB & 0x1 ? 1 : 2;\r\n return read_port(port);\r\n}\r\n\r\n// End the game if tilt switch is activated.\r\nstatic void CheckHandleTilt()\r\n{\r\n // xref 17cd\r\n if (!(read_port(2) & TILT_BIT)) return;\r\n if (m.tilt) return;\r\n\r\n yield(YIELD_TILT);\r\n assert(FALSE);\r\n}\r\n\r\n// This routine is entered after the stack reset in CheckHandleTilt\r\n// It prints the tilt message and ends the game, cycling back to the splash screen.\r\nstatic void on_tilt()\r\n{\r\n // xref 17dc\r\n for (size_t i = 0; i < 4; ++i)\r\n ClearPlayField();\r\n\r\n m.tilt = 1;\r\n DsableGameTasks();\r\n enable_interrupts();\r\n\r\n PrintMessageDel(xytosc(96,176), m.MSG_TILT, 4);\r\n OneSecDelay();\r\n\r\n m.tilt = 0;\r\n m.waitStartLoop = 0;\r\n\r\n GameOver(); // does not return.\r\n assert(FALSE);\r\n}\r\n\r\n// Play appropriate sounds based on saucer state.\r\nstatic void CtrlSaucerSound()\r\n{\r\n // xref 1804\r\n if (m.saucerActive == 0)\r\n {\r\n SoundBits3Off(0xfe);\r\n return;\r\n }\r\n\r\n if (m.saucerHit)\r\n return;\r\n\r\n SoundBits3On(0x01);\r\n}\r\n\r\n// Draws the text and sprites for the \"SCORE ADVANCE TABLE\" in the splash screens.\r\nstatic void DrawAdvTable()\r\n{\r\n // xref 1815\r\n PrintMessage(xytosc(32,128), m.MSG__SCORE_ADVANCE_TABLE_, 0x15);\r\n\r\n // PrintMessageAdv uses this\r\n m.temp206C = 10;\r\n\r\n PriCursor cursor;\r\n\r\n // Sprite display list for score advance table\r\n cursor.src = m.SCORE_ADV_SPRITE_LIST;\r\n\r\n while (!ReadPriStruct(&cursor))\r\n Draw16ByteSprite(cursor.sc, cursor.obj);\r\n\r\n // Message display list for score advance table\r\n cursor.src = m.SCORE_ADV_MSG_LIST;\r\n\r\n while (!ReadPriStruct(&cursor))\r\n PrintMessageAdv(cursor.sc, cursor.obj);\r\n}\r\n\r\n// Used when drawing the Score Advance Table to draw the alien and saucer sprites\r\nstatic void Draw16ByteSprite(Word sc, uint8_t* sprite)\r\n{\r\n // xref 1844\r\n SprDesc desc;\r\n desc.sc = sc;\r\n desc.spr = ptr_to_word(sprite);\r\n desc.n = 16;\r\n\r\n DrawSimpSprite(&desc);\r\n}\r\n\r\n// Used when drawing the Score Advance Table to draw the text\r\nstatic void PrintMessageAdv(Word sc, uint8_t* msg)\r\n{\r\n // xref 184c\r\n size_t n = m.temp206C;\r\n PrintMessageDel(sc, msg, n);\r\n}\r\n\r\n// A PriStruct is a display list, containing a list of\r\n// objects to display (either sprites or text), and their position.\r\n// This routine is used to iterate through the list and read each member.\r\nstatic int ReadPriStruct(PriCursor *pri)\r\n{\r\n // xref 1856\r\n pri->sc.l = *pri->src++;\r\n\r\n if (pri->sc.l == 0xff)\r\n return TRUE; // hit end\r\n\r\n pri->sc.h = *pri->src++;\r\n\r\n Word obj;\r\n obj.l = *pri->src++;\r\n obj.h = *pri->src++;\r\n\r\n pri->obj = word_to_ptr(obj);\r\n\r\n return FALSE; // keep going\r\n}\r\n\r\n// Required for CCOIN and PLAy splash animations\r\n// The animation structure used here is set up by IniSplashAni()\r\n// Moves and animates a sprite until it reaches a target position\r\nstatic void SplashSprite()\r\n{\r\n // xref 1868\r\n ++m.splashAnForm;\r\n uint8_t* vptr = &m.splashDelta.y;\r\n\r\n uint8_t dy = *vptr;\r\n uint8_t x = AddDelta(vptr, dy);\r\n\r\n if (m.splashTargetX != x)\r\n {\r\n uint8_t flip = m.splashAnForm & 0x04;\r\n Word spr = m.splashImRest;\r\n\r\n if (flip == 0) { spr.u16 += 48; }\r\n\r\n m.splashPic = spr;\r\n\r\n SprDesc desc = ReadDesc((SprDesc*) u16_to_ptr(SPLASH_DESC_ADDR));\r\n\r\n // splash desc is out of order, needs swapping.\r\n Word tmp = desc.pos;\r\n\r\n desc.pos = desc.spr;\r\n desc.spr = tmp;\r\n\r\n DrawSprite(&desc);\r\n return;\r\n }\r\n\r\n // xref 1898\r\n m.splashReached = 1;\r\n}\r\n\r\n// Handles the shooting part of the CCOIN splash animation\r\n// Companion routine is SplashSquiggly()\r\nstatic void AnimateShootingSplashAlien()\r\n{\r\n // xref 189e\r\n BlockCopy(u16_to_ptr(SQUIGGLY_SHOT_ADDR), m.SPLASH_SHOT_OBJDATA, 16);\r\n\r\n m.shotSync = 2;\r\n m.alienShotDelta = 0xff;\r\n m.isrSplashTask = 4;\r\n\r\n // spin until shot collides\r\n while ((m.squShotData.Status & SHOT_BLOWUP) == 0)\r\n {\r\n // xref 18b8\r\n timeslice(); // spin\r\n }\r\n\r\n // spin until shot explosion finishes\r\n while ((m.squShotData.Status & SHOT_BLOWUP) != 0)\r\n {\r\n // xref 18c0\r\n timeslice(); // spin\r\n }\r\n\r\n // replace extra \'C\' with blank space\r\n DrawChar(xytosc(120,136), 0x26);\r\n\r\n TwoSecDelay();\r\n}\r\n\r\n// Handle initialization and splash screens.\r\n// Initially entered upon startup via reset(), with skip=0\r\n// Is is terminated when the stack is reset upon the insertion of credits, and is\r\n// replaced by WaitForStart()\r\n// The routine is eventually re-entered with skip=0 via GameOver()\r\nstatic void main_init(int skip)\r\n{\r\n // xref 18d4\r\n int init = !skip;\r\n\r\n if (init)\r\n {\r\n CopyRomtoRam();\r\n DrawStatus();\r\n }\r\n\r\n // 18df\r\n while (TRUE)\r\n {\r\n if (init)\r\n {\r\n m.aShotReloadRate = 8;\r\n\r\n write_port(3, 0); // turn off sound 1\r\n write_port(5, 0); // turn off sound 2\r\n\r\n // SetISRSplashTask\r\n m.isrSplashTask = 0;\r\n\r\n // xref 0af2\r\n enable_interrupts();\r\n\r\n OneSecDelay();\r\n\r\n // xref 0af6\r\n PrintMessageDel(xytosc(96, 184),\r\n m.splashAnimate ? m.MSG_PLAY : m.MSG_PLAY2,\r\n 4);\r\n\r\n // xref 0b08\r\n MessageToCenterOfScreen(m.MSG_SPACE__INVADERS);\r\n\r\n // xref 0b0e\r\n OneSecDelay();\r\n DrawAdvTable();\r\n TwoSecDelay();\r\n\r\n // xref 0b17\r\n if (m.splashAnimate == 0)\r\n {\r\n // run script for alien twiddling with upside down \'Y\'\r\n IniSplashAni(u16_to_ptr(0x1a95));\r\n Animate();\r\n IniSplashAni(u16_to_ptr(0x1bb0));\r\n Animate();\r\n\r\n OneSecDelay();\r\n\r\n IniSplashAni(u16_to_ptr(0x1fc9));\r\n Animate();\r\n\r\n OneSecDelay();\r\n\r\n ClearSmallSprite(xytosc(125, 184), 10, 0);\r\n TwoSecDelay();\r\n }\r\n\r\n // xref 0b4a\r\n ClearPlayField();\r\n\r\n if (m.p1ShipsRem == 0)\r\n {\r\n // xref 0b54\r\n m.p1ShipsRem = GetShipsPerCred();\r\n RemoveShip();\r\n }\r\n\r\n // xref 0b5d\r\n CopyRAMMirror();\r\n InitAliensP1();\r\n\r\n DrawShieldP1();\r\n RestoreShieldsP1();\r\n m.isrSplashTask = 1;\r\n\r\n DrawBottomLine();\r\n\r\n // xref 0b71\r\n\r\n do\r\n {\r\n PlrFireOrDemo();\r\n\r\n // xref 0b74\r\n // check player shot, and aliens bumping screen, also handle hidden message\r\n uint8_t a2 = CheckPlyrShotAndBump();\r\n\r\n // feed the watchdog\r\n write_port(6, a2);\r\n\r\n } while (IsPlayerAlive());\r\n\r\n // xref 0b7f\r\n m.plyrShotStatus = 0;\r\n\r\n // wait for demo player to finish exploding.\r\n while (! IsPlayerAlive() )\r\n {\r\n // xref 0b83\r\n timeslice(); // spin\r\n }\r\n }\r\n\r\n // xref 0b89\r\n m.isrSplashTask = 0;\r\n OneSecDelay();\r\n ClearPlayField();\r\n\r\n PrintMessage(xytosc(64,136), m.MSG_INSERT__COIN, 12);\r\n\r\n // draw extra \'C\' for CCOIN\r\n if (m.splashAnimate == 0)\r\n DrawChar(xytosc(120,136), 2);\r\n\r\n PriCursor cursor;\r\n cursor.src = m.CREDIT_TABLE;\r\n\r\n ReadPriStruct(&cursor);\r\n PrintMessageAdv(cursor.sc, cursor.obj);\r\n\r\n // Only print coin info if DIP7 is set\r\n if ((read_port(2) & DIP7_COININFO) == 0)\r\n {\r\n cursor.src = m.CREDIT_TABLE_COINS;\r\n\r\n // xref 183a\r\n while (!ReadPriStruct(&cursor))\r\n PrintMessageAdv(cursor.sc, cursor.obj);\r\n }\r\n\r\n TwoSecDelay();\r\n\r\n // xref 0bc6\r\n if (m.splashAnimate == 0)\r\n {\r\n // xref 0bce\r\n // shoot C animation\r\n IniSplashAni(u16_to_ptr(0x1fd5));\r\n Animate();\r\n AnimateShootingSplashAlien();\r\n }\r\n\r\n // xref 0bda\r\n m.splashAnimate = !m.splashAnimate;\r\n ClearPlayField();\r\n }\r\n}\r\n\r\n// Return pointer to non current player\'s alive status\r\nstatic uint8_t* GetOtherPlayerAliveFlag()\r\n{\r\n // xref 18e7\r\n return (m.playerDataMSB & 0x1 ? &m.playerStates.h : &m.playerStates.l);\r\n}\r\n\r\n// Use a mask to enable sounds on port 3\r\nstatic void SoundBits3On(uint8_t mask)\r\n{\r\n // xref 18fa\r\n uint8_t snd = m.soundPort3;\r\n snd |= mask;\r\n m.soundPort3 = snd;\r\n write_port(3, snd);\r\n}\r\n\r\n// Init all the aliens for P2\r\nstatic void InitAliensP2()\r\n{\r\n // xref 1904\r\n InitAliensSub(u16_to_ptr(P2_ADDR));\r\n}\r\n\r\n// Called from the main thread to do some miscellaneous tasks\r\n// a) Player shot collision response\r\n// b) Detecting and handling the alien rack bumping the screen edges\r\nstatic void PlyrShotAndBump()\r\n{\r\n // xref 190a\r\n PlayerShotHit();\r\n RackBump();\r\n}\r\n\r\n// Return pointer to current player\'s alive status\r\nstatic uint8_t* CurPlyAlive()\r\n{\r\n // xref 1910\r\n return (m.playerDataMSB & 0x1 ? &m.playerStates.l : &m.playerStates.h);\r\n}\r\n\r\n// Draw the score text labels at the top of the screen\r\nstatic void DrawScoreHead()\r\n{\r\n // xref 191a\r\n PrintMessage(xytosc(0,240), m.MSG__SCORE_1__HI_SCORE_SCORE_2__, 28);\r\n}\r\n\r\n// Draw the score for P1\r\nstatic void PrintP1Score()\r\n{\r\n // xref 1925\r\n DrawScore(&m.P1Scor.l);\r\n}\r\n\r\n// Draw the score for P2\r\nstatic void PrintP2Score()\r\n{\r\n // xref 192b\r\n DrawScore(&m.P2Scor.l);\r\n}\r\n\r\n// Draw the score using the given descriptor in iter\r\nstatic void DrawScore(uint8_t* iter)\r\n{\r\n // xref 1931\r\n\r\n Word pos;\r\n Word val;\r\n\r\n val.l = *iter++;\r\n val.h = *iter++;\r\n pos.l = *iter++;\r\n pos.h = *iter++;\r\n\r\n Print4Digits(pos, val);\r\n}\r\n\r\n// Draw the credit text label at the bottom right\r\nstatic void PrintCreditLabel()\r\n{\r\n // xref 193c\r\n PrintMessage(xytosc(136,8), m.MSG_CREDIT_, 7);\r\n}\r\n\r\n// Draw the number of credits at the bottom right\r\nstatic void DrawNumCredits()\r\n{\r\n // xref 1947\r\n DrawHexByte(xytosc(192,8), m.numCoins);\r\n}\r\n\r\n// Draw the high score\r\nstatic void PrintHiScore()\r\n{\r\n // xref 1950\r\n DrawScore(&m.HiScor.l);\r\n}\r\n\r\n// Clear the screen and draw all the text surrounding the playfield\r\nstatic void DrawStatus()\r\n{\r\n // xref 1956\r\n ClearScreen();\r\n DrawScoreHead();\r\n\r\n PrintP1Score();\r\n PrintP2Score();\r\n\r\n PrintHiScore();\r\n\r\n PrintCreditLabel();\r\n DrawNumCredits();\r\n\r\n // Midway patched this out\r\n // PrintTaitoCorporation();\r\n}\r\n\r\n// Prints \"* TAITO CORPORATION *\" at the bottom of the screen\r\n// This is a bit of dead code due to the patching out in DrawStatus()\r\nstatic void PrintTaitoCorporation()\r\n{\r\n // xref 198b\r\n PrintMessage(xytosc(32, 24), u16_to_ptr(0x19be), 0x13);\r\n}\r\n\r\n// Called during the game demo to check if player has entered the correct\r\n// button combos to display the easter egg, \"TAITO COP\"\r\nstatic void CheckHiddenMes()\r\n{\r\n // xref 199a\r\n uint8_t a = 0;\r\n\r\n if (m.hidMessSeq == 0)\r\n {\r\n a = read_port(1);\r\n a &= 0x76;\r\n a -= 0x72;\r\n\r\n if (a)\r\n return;\r\n\r\n m.hidMessSeq = 1;\r\n }\r\n\r\n a = read_port(1);\r\n a &= 0x76;\r\n a -= 0x34;\r\n\r\n if (a)\r\n return;\r\n\r\n PrintMessage(xytosc(80,216), m.MSG_TAITO_COP, 9);\r\n}\r\n\r\n// Allow game related tasks in interrupt routines\r\nstatic void EnableGameTasks()\r\n{\r\n // xref 19d1\r\n m.gameTasksRunning = 1;\r\n}\r\n\r\n// Disallow game related tasks in interrupt routines\r\nstatic void DsableGameTasks()\r\n{\r\n // xref 19d7\r\n m.gameTasksRunning = 0;\r\n}\r\n\r\n// Use a mask to turn off sounds in port 3\r\nstatic void SoundBits3Off(uint8_t mask)\r\n{\r\n // xref 19dc\r\n uint8_t snd = m.soundPort3;\r\n snd &= mask;\r\n m.soundPort3 = snd;\r\n write_port(3, snd);\r\n}\r\n\r\n// Draw the sprites representing the number of lives (0 based) remaining at the bottom left\r\nstatic void DrawNumShips(uint8_t n)\r\n{\r\n // xref 19e6\r\n SprDesc desc;\r\n\r\n desc.sc = xytosc(24,8);\r\n desc.spr = ptr_to_word(m.PLAYER_SPRITES);\r\n desc.n = 16;\r\n\r\n for (int i = 0; i < n; ++i)\r\n desc.sc = DrawSimpSprite(&desc);\r\n\r\n DrawNumShipsSub(desc.sc);\r\n}\r\n\r\n// Clears the space to the right of the ship sprites (used to remove lives)\r\nstatic void DrawNumShipsSub(Word pos)\r\n{\r\n // xref 19fa\r\n // Clear up to x = 136 (start of credit label)\r\n\r\n do\r\n {\r\n pos = ClearSmallSprite(pos, 16, 0);\r\n } while ((pos.x != 0x35));\r\n}\r\n\r\n// Returns true if given obj is positioned on the half of screen that is not currently being drawn.\r\n// Used to control which interrupt draws a particular game object.\r\n// This prevents flicker.\r\nstatic uint8_t CompXrToBeam(uint8_t* posaddr)\r\n{\r\n // xref 1a06\r\n uint8_t b = m.vblankStatus;\r\n uint8_t a = *posaddr & XR_MID;\r\n\r\n return a == b;\r\n}\r\n\r\n// memcpy\r\nstatic void BlockCopy(void *_dest, void *_src, size_t n)\r\n{\r\n // xref 1a32\r\n\r\n uint8_t* dest = (uint8_t*) (_dest);\r\n uint8_t* src = (uint8_t*) (_src);\r\n\r\n for (size_t i = 0; i < n; ++i)\r\n *dest++ = *src++;\r\n}\r\n\r\n// Return a copy of a Sprite Descriptor from src\r\nstatic SprDesc ReadDesc(SprDesc* src)\r\n{\r\n // xref 1a3b\r\n SprDesc desc;\r\n desc.spr.l = src->spr.l;\r\n desc.spr.h = src->spr.h;\r\n\r\n desc.pos.l = src->pos.l;\r\n desc.pos.h = src->pos.h;\r\n\r\n desc.n = src->n;\r\n return desc;\r\n}\r\n\r\n// Convert a pixel pos to a screen address\r\n// Pixel positions in memory are pre-offset by +32 pixels, meaning that when\r\n// they are converted to a screen coordinate, 0x400 has already been added.\r\n// Hence the or with 0x2000 below, instead of +0x2400\r\n// See xpix()\r\nstatic Word ConvToScr(Word pos)\r\n{\r\n // xref 1a47\r\n return u16_to_word((pos.u16 >> 3) | 0x2000);\r\n}\r\n\r\n// bzero the 7168 bytes of vram\r\nstatic void ClearScreen()\r\n{\r\n // xref 1a5c\r\n uint8_t* screen = m.vram;\r\n size_t n = 7168;\r\n\r\n for (size_t i = 0; i < n; ++i)\r\n screen[i] = 0;\r\n}\r\n\r\n// CopyShields() subroutine.\r\n// cursor - used to iterate through the screen and the player buffer\r\n// spr_size - contains the number of rows and bytes per row of the sprite\r\n// dir=0 - Copy a shield from the buffer to the screen\r\n// dir=1 - Copy a shield from the screen to the buffer\r\nstatic void CopyShieldBuffer(ShieldBufferCursor *cursor, Word spr_size, uint8_t dir)\r\n{\r\n // xref 1a69\r\n uint8_t nr = spr_size.h;\r\n uint8_t nb = spr_size.l;\r\n\r\n for (int i = 0; i < nr; ++i, cursor->sc.u16 += xysc(1,0))\r\n {\r\n uint8_t* screen = word_to_ptr(cursor->sc);\r\n\r\n for (int j = 0; j < nb; ++j)\r\n {\r\n if (dir == 0)\r\n *screen = *cursor->iter | *screen;\r\n else\r\n *cursor->iter = *screen;\r\n\r\n cursor->iter++;\r\n screen++;\r\n }\r\n }\r\n}\r\n\r\n// Take a life away from the player, and update the indicators\r\nstatic void RemoveShip()\r\n{\r\n // xref 1a7f\r\n uint8_t* nships_ptr;\r\n uint8_t num = GetNumberOfShips(&nships_ptr);\r\n\r\n if (num == 0)\r\n return;\r\n\r\n *nships_ptr = num-1;\r\n\r\n // The sprite indicator is 0 based.\r\n DrawNumShips(num-1);\r\n\r\n // The text indicator is 1 based.\r\n DrawHexByteSub(xytosc(8,8), num & 0xf);\r\n}\r\n\r\n// Print the numeric lives indicator at the bottom left\r\nstatic void PrintNumShips(uint8_t num)\r\n{\r\n // xref 1a8b\r\n DrawChar(xytosc(8,8), (num & 0x0f) + 0x1a);\r\n}\r\n\r\n// GAMECODE FINISH', 1); +INSERT INTO `onefile` VALUES (3, 'minilisp', 'C', '用 C 语言写的 Lisp 解释器。实现了整数、符号、局部变量、条件语句、宏和垃圾回收等功能', 12, 'https://github.com/rui314/minilisp', 7600, '// This software is in the public domain.\r\n\r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n\r\nstatic __attribute((noreturn)) void error(char *fmt, ...) {\r\n va_list ap;\r\n va_start(ap, fmt);\r\n vfprintf(stderr, fmt, ap);\r\n fprintf(stderr, \"\\n\");\r\n va_end(ap);\r\n exit(1);\r\n}\r\n\r\n//======================================================================\r\n// Lisp objects\r\n//======================================================================\r\n\r\n// The Lisp object type\r\nenum {\r\n // Regular objects visible from the user\r\n TINT = 1,\r\n TCELL,\r\n TSYMBOL,\r\n TPRIMITIVE,\r\n TFUNCTION,\r\n TMACRO,\r\n TENV,\r\n // The marker that indicates the object has been moved to other location by GC. The new location\r\n // can be found at the forwarding pointer. Only the functions to do garbage collection set and\r\n // handle the object of this type. Other functions will never see the object of this type.\r\n TMOVED,\r\n // Const objects. They are statically allocated and will never be managed by GC.\r\n TTRUE,\r\n TNIL,\r\n TDOT,\r\n TCPAREN,\r\n};\r\n\r\n// Typedef for the primitive function\r\nstruct Obj;\r\ntypedef struct Obj *Primitive(void *root, struct Obj **env, struct Obj **args);\r\n\r\n// The object type\r\ntypedef struct Obj {\r\n // The first word of the object represents the type of the object. Any code that handles object\r\n // needs to check its type first, then access the following union members.\r\n int type;\r\n\r\n // The total size of the object, including \"type\" field, this field, the contents, and the\r\n // padding at the end of the object.\r\n int size;\r\n\r\n // Object values.\r\n union {\r\n // Int\r\n int value;\r\n // Cell\r\n struct {\r\n struct Obj *car;\r\n struct Obj *cdr;\r\n };\r\n // Symbol\r\n char name[1];\r\n // Primitive\r\n Primitive *fn;\r\n // Function or Macro\r\n struct {\r\n struct Obj *params;\r\n struct Obj *body;\r\n struct Obj *env;\r\n };\r\n // Environment frame. This is a linked list of association lists\r\n // containing the mapping from symbols to their value.\r\n struct {\r\n struct Obj *vars;\r\n struct Obj *up;\r\n };\r\n // Forwarding pointer\r\n void *moved;\r\n };\r\n} Obj;\r\n\r\n// Constants\r\nstatic Obj *True = &(Obj){ TTRUE };\r\nstatic Obj *Nil = &(Obj){ TNIL };\r\nstatic Obj *Dot = &(Obj){ TDOT };\r\nstatic Obj *Cparen = &(Obj){ TCPAREN };\r\n\r\n// The list containing all symbols. Such data structure is traditionally called the \"obarray\", but I\r\n// avoid using it as a variable name as this is not an array but a list.\r\nstatic Obj *Symbols;\r\n\r\n//======================================================================\r\n// Memory management\r\n//======================================================================\r\n\r\n// The size of the heap in byte\r\n#define MEMORY_SIZE 65536\r\n\r\n// The pointer pointing to the beginning of the current heap\r\nstatic void *memory;\r\n\r\n// The pointer pointing to the beginning of the old heap\r\nstatic void *from_space;\r\n\r\n// The number of bytes allocated from the heap\r\nstatic size_t mem_nused = 0;\r\n\r\n// Flags to debug GC\r\nstatic bool gc_running = false;\r\nstatic bool debug_gc = false;\r\nstatic bool always_gc = false;\r\n\r\nstatic void gc(void *root);\r\n\r\n// Currently we are using Cheney\'s copying GC algorithm, with which the available memory is split\r\n// into two halves and all objects are moved from one half to another every time GC is invoked. That\r\n// means the address of the object keeps changing. If you take the address of an object and keep it\r\n// in a C variable, dereferencing it could cause SEGV because the address becomes invalid after GC\r\n// runs.\r\n//\r\n// In order to deal with that, all access from C to Lisp objects will go through two levels of\r\n// pointer dereferences. The C local variable is pointing to a pointer on the C stack, and the\r\n// pointer is pointing to the Lisp object. GC is aware of the pointers in the stack and updates\r\n// their contents with the objects\' new addresses when GC happens.\r\n//\r\n// The following is a macro to reserve the area in the C stack for the pointers. The contents of\r\n// this area are considered to be GC root.\r\n//\r\n// Be careful not to bypass the two levels of pointer indirections. If you create a direct pointer\r\n// to an object, it\'ll cause a subtle bug. Such code would work in most cases but fails with SEGV if\r\n// GC happens during the execution of the code. Any code that allocates memory may invoke GC.\r\n\r\n#define ROOT_END ((void *)-1)\r\n\r\n#define ADD_ROOT(size) \\\r\n void *root_ADD_ROOT_[size + 2]; \\\r\n root_ADD_ROOT_[0] = root; \\\r\n for (int i = 1; i <= size; i++) \\\r\n root_ADD_ROOT_[i] = NULL; \\\r\n root_ADD_ROOT_[size + 1] = ROOT_END; \\\r\n root = root_ADD_ROOT_\r\n\r\n#define DEFINE1(var1) \\\r\n ADD_ROOT(1); \\\r\n Obj **var1 = (Obj **)(root_ADD_ROOT_ + 1)\r\n\r\n#define DEFINE2(var1, var2) \\\r\n ADD_ROOT(2); \\\r\n Obj **var1 = (Obj **)(root_ADD_ROOT_ + 1); \\\r\n Obj **var2 = (Obj **)(root_ADD_ROOT_ + 2)\r\n\r\n#define DEFINE3(var1, var2, var3) \\\r\n ADD_ROOT(3); \\\r\n Obj **var1 = (Obj **)(root_ADD_ROOT_ + 1); \\\r\n Obj **var2 = (Obj **)(root_ADD_ROOT_ + 2); \\\r\n Obj **var3 = (Obj **)(root_ADD_ROOT_ + 3)\r\n\r\n#define DEFINE4(var1, var2, var3, var4) \\\r\n ADD_ROOT(4); \\\r\n Obj **var1 = (Obj **)(root_ADD_ROOT_ + 1); \\\r\n Obj **var2 = (Obj **)(root_ADD_ROOT_ + 2); \\\r\n Obj **var3 = (Obj **)(root_ADD_ROOT_ + 3); \\\r\n Obj **var4 = (Obj **)(root_ADD_ROOT_ + 4)\r\n\r\n// Round up the given value to a multiple of size. Size must be a power of 2. It adds size - 1\r\n// first, then zero-ing the least significant bits to make the result a multiple of size. I know\r\n// these bit operations may look a little bit tricky, but it\'s efficient and thus frequently used.\r\nstatic inline size_t roundup(size_t var, size_t size) {\r\n return (var + size - 1) & ~(size - 1);\r\n}\r\n\r\n// Allocates memory block. This may start GC if we don\'t have enough memory.\r\nstatic Obj *alloc(void *root, int type, size_t size) {\r\n // The object must be large enough to contain a pointer for the forwarding pointer. Make it\r\n // larger if it\'s smaller than that.\r\n size = roundup(size, sizeof(void *));\r\n\r\n // Add the size of the type tag and size fields.\r\n size += offsetof(Obj, value);\r\n\r\n // Round up the object size to the nearest alignment boundary, so that the next object will be\r\n // allocated at the proper alignment boundary. Currently we align the object at the same\r\n // boundary as the pointer.\r\n size = roundup(size, sizeof(void *));\r\n\r\n // If the debug flag is on, allocate a new memory space to force all the existing objects to\r\n // move to new addresses, to invalidate the old addresses. By doing this the GC behavior becomes\r\n // more predictable and repeatable. If there\'s a memory bug that the C variable has a direct\r\n // reference to a Lisp object, the pointer will become invalid by this GC call. Dereferencing\r\n // that will immediately cause SEGV.\r\n if (always_gc && !gc_running)\r\n gc(root);\r\n\r\n // Otherwise, run GC only when the available memory is not large enough.\r\n if (!always_gc && MEMORY_SIZE < mem_nused + size)\r\n gc(root);\r\n\r\n // Terminate the program if we couldn\'t satisfy the memory request. This can happen if the\r\n // requested size was too large or the from-space was filled with too many live objects.\r\n if (MEMORY_SIZE < mem_nused + size)\r\n error(\"Memory exhausted\");\r\n\r\n // Allocate the object.\r\n Obj *obj = memory + mem_nused;\r\n obj->type = type;\r\n obj->size = size;\r\n mem_nused += size;\r\n return obj;\r\n}\r\n\r\n//======================================================================\r\n// Garbage collector\r\n//======================================================================\r\n\r\n// Cheney\'s algorithm uses two pointers to keep track of GC status. At first both pointers point to\r\n// the beginning of the to-space. As GC progresses, they are moved towards the end of the\r\n// to-space. The objects before \"scan1\" are the objects that are fully copied. The objects between\r\n// \"scan1\" and \"scan2\" have already been copied, but may contain pointers to the from-space. \"scan2\"\r\n// points to the beginning of the free space.\r\nstatic Obj *scan1;\r\nstatic Obj *scan2;\r\n\r\n// Moves one object from the from-space to the to-space. Returns the object\'s new address. If the\r\n// object has already been moved, does nothing but just returns the new address.\r\nstatic inline Obj *forward(Obj *obj) {\r\n // If the object\'s address is not in the from-space, the object is not managed by GC nor it\r\n // has already been moved to the to-space.\r\n ptrdiff_t offset = (uint8_t *)obj - (uint8_t *)from_space;\r\n if (offset < 0 || MEMORY_SIZE <= offset)\r\n return obj;\r\n\r\n // The pointer is pointing to the from-space, but the object there was a tombstone. Follow the\r\n // forwarding pointer to find the new location of the object.\r\n if (obj->type == TMOVED)\r\n return obj->moved;\r\n\r\n // Otherwise, the object has not been moved yet. Move it.\r\n Obj *newloc = scan2;\r\n memcpy(newloc, obj, obj->size);\r\n scan2 = (Obj *)((uint8_t *)scan2 + obj->size);\r\n\r\n // Put a tombstone at the location where the object used to occupy, so that the following call\r\n // of forward() can find the object\'s new location.\r\n obj->type = TMOVED;\r\n obj->moved = newloc;\r\n return newloc;\r\n}\r\n\r\nstatic void *alloc_semispace() {\r\n return mmap(NULL, MEMORY_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);\r\n}\r\n\r\n// Copies the root objects.\r\nstatic void forward_root_objects(void *root) {\r\n Symbols = forward(Symbols);\r\n for (void **frame = root; frame; frame = *(void ***)frame)\r\n for (int i = 1; frame[i] != ROOT_END; i++)\r\n if (frame[i])\r\n frame[i] = forward(frame[i]);\r\n}\r\n\r\n// Implements Cheney\'s copying garbage collection algorithm.\r\n// http://en.wikipedia.org/wiki/Cheney%27s_algorithm\r\nstatic void gc(void *root) {\r\n assert(!gc_running);\r\n gc_running = true;\r\n\r\n // Allocate a new semi-space.\r\n from_space = memory;\r\n memory = alloc_semispace();\r\n\r\n // Initialize the two pointers for GC. Initially they point to the beginning of the to-space.\r\n scan1 = scan2 = memory;\r\n\r\n // Copy the GC root objects first. This moves the pointer scan2.\r\n forward_root_objects(root);\r\n\r\n // Copy the objects referenced by the GC root objects located between scan1 and scan2. Once it\'s\r\n // finished, all live objects (i.e. objects reachable from the root) will have been copied to\r\n // the to-space.\r\n while (scan1 < scan2) {\r\n switch (scan1->type) {\r\n case TINT:\r\n case TSYMBOL:\r\n case TPRIMITIVE:\r\n // Any of the above types does not contain a pointer to a GC-managed object.\r\n break;\r\n case TCELL:\r\n scan1->car = forward(scan1->car);\r\n scan1->cdr = forward(scan1->cdr);\r\n break;\r\n case TFUNCTION:\r\n case TMACRO:\r\n scan1->params = forward(scan1->params);\r\n scan1->body = forward(scan1->body);\r\n scan1->env = forward(scan1->env);\r\n break;\r\n case TENV:\r\n scan1->vars = forward(scan1->vars);\r\n scan1->up = forward(scan1->up);\r\n break;\r\n default:\r\n error(\"Bug: copy: unknown type %d\", scan1->type);\r\n }\r\n scan1 = (Obj *)((uint8_t *)scan1 + scan1->size);\r\n }\r\n\r\n // Finish up GC.\r\n munmap(from_space, MEMORY_SIZE);\r\n size_t old_nused = mem_nused;\r\n mem_nused = (size_t)((uint8_t *)scan1 - (uint8_t *)memory);\r\n if (debug_gc)\r\n fprintf(stderr, \"GC: %zu bytes out of %zu bytes copied.\\n\", mem_nused, old_nused);\r\n gc_running = false;\r\n}\r\n\r\n//======================================================================\r\n// Constructors\r\n//======================================================================\r\n\r\nstatic Obj *make_int(void *root, int value) {\r\n Obj *r = alloc(root, TINT, sizeof(int));\r\n r->value = value;\r\n return r;\r\n}\r\n\r\nstatic Obj *cons(void *root, Obj **car, Obj **cdr) {\r\n Obj *cell = alloc(root, TCELL, sizeof(Obj *) * 2);\r\n cell->car = *car;\r\n cell->cdr = *cdr;\r\n return cell;\r\n}\r\n\r\nstatic Obj *make_symbol(void *root, char *name) {\r\n Obj *sym = alloc(root, TSYMBOL, strlen(name) + 1);\r\n strcpy(sym->name, name);\r\n return sym;\r\n}\r\n\r\nstatic Obj *make_primitive(void *root, Primitive *fn) {\r\n Obj *r = alloc(root, TPRIMITIVE, sizeof(Primitive *));\r\n r->fn = fn;\r\n return r;\r\n}\r\n\r\nstatic Obj *make_function(void *root, Obj **env, int type, Obj **params, Obj **body) {\r\n assert(type == TFUNCTION || type == TMACRO);\r\n Obj *r = alloc(root, type, sizeof(Obj *) * 3);\r\n r->params = *params;\r\n r->body = *body;\r\n r->env = *env;\r\n return r;\r\n}\r\n\r\nstruct Obj *make_env(void *root, Obj **vars, Obj **up) {\r\n Obj *r = alloc(root, TENV, sizeof(Obj *) * 2);\r\n r->vars = *vars;\r\n r->up = *up;\r\n return r;\r\n}\r\n\r\n// Returns ((x . y) . a)\r\nstatic Obj *acons(void *root, Obj **x, Obj **y, Obj **a) {\r\n DEFINE1(cell);\r\n *cell = cons(root, x, y);\r\n return cons(root, cell, a);\r\n}\r\n\r\n//======================================================================\r\n// Parser\r\n//\r\n// This is a hand-written recursive-descendent parser.\r\n//======================================================================\r\n\r\n#define SYMBOL_MAX_LEN 200\r\nconst char symbol_chars[] = \"~!@#$%^&*-_=+:/?<>\";\r\n\r\nstatic Obj *read_expr(void *root);\r\n\r\nstatic int peek(void) {\r\n int c = getchar();\r\n ungetc(c, stdin);\r\n return c;\r\n}\r\n\r\n// Destructively reverses the given list.\r\nstatic Obj *reverse(Obj *p) {\r\n Obj *ret = Nil;\r\n while (p != Nil) {\r\n Obj *head = p;\r\n p = p->cdr;\r\n head->cdr = ret;\r\n ret = head;\r\n }\r\n return ret;\r\n}\r\n\r\n// Skips the input until newline is found. Newline is one of \\r, \\r\\n or \\n.\r\nstatic void skip_line(void) {\r\n for (;;) {\r\n int c = getchar();\r\n if (c == EOF || c == \'\\n\')\r\n return;\r\n if (c == \'\\r\') {\r\n if (peek() == \'\\n\')\r\n getchar();\r\n return;\r\n }\r\n }\r\n}\r\n\r\n// Reads a list. Note that \'(\' has already been read.\r\nstatic Obj *read_list(void *root) {\r\n DEFINE3(obj, head, last);\r\n *head = Nil;\r\n for (;;) {\r\n *obj = read_expr(root);\r\n if (!*obj)\r\n error(\"Unclosed parenthesis\");\r\n if (*obj == Cparen)\r\n return reverse(*head);\r\n if (*obj == Dot) {\r\n *last = read_expr(root);\r\n if (read_expr(root) != Cparen)\r\n error(\"Closed parenthesis expected after dot\");\r\n Obj *ret = reverse(*head);\r\n (*head)->cdr = *last;\r\n return ret;\r\n }\r\n *head = cons(root, obj, head);\r\n }\r\n}\r\n\r\n// May create a new symbol. If there\'s a symbol with the same name, it will not create a new symbol\r\n// but return the existing one.\r\nstatic Obj *intern(void *root, char *name) {\r\n for (Obj *p = Symbols; p != Nil; p = p->cdr)\r\n if (strcmp(name, p->car->name) == 0)\r\n return p->car;\r\n DEFINE1(sym);\r\n *sym = make_symbol(root, name);\r\n Symbols = cons(root, sym, &Symbols);\r\n return *sym;\r\n}\r\n\r\n// Reader marcro \' (single quote). It reads an expression and returns (quote ).\r\nstatic Obj *read_quote(void *root) {\r\n DEFINE2(sym, tmp);\r\n *sym = intern(root, \"quote\");\r\n *tmp = read_expr(root);\r\n *tmp = cons(root, tmp, &Nil);\r\n *tmp = cons(root, sym, tmp);\r\n return *tmp;\r\n}\r\n\r\nstatic int read_number(int val) {\r\n while (isdigit(peek()))\r\n val = val * 10 + (getchar() - \'0\');\r\n return val;\r\n}\r\n\r\nstatic Obj *read_symbol(void *root, char c) {\r\n char buf[SYMBOL_MAX_LEN + 1];\r\n buf[0] = c;\r\n int len = 1;\r\n while (isalnum(peek()) || strchr(symbol_chars, peek())) {\r\n if (SYMBOL_MAX_LEN <= len)\r\n error(\"Symbol name too long\");\r\n buf[len++] = getchar();\r\n }\r\n buf[len] = \'\\0\';\r\n return intern(root, buf);\r\n}\r\n\r\nstatic Obj *read_expr(void *root) {\r\n for (;;) {\r\n int c = getchar();\r\n if (c == \' \' || c == \'\\n\' || c == \'\\r\' || c == \'\\t\')\r\n continue;\r\n if (c == EOF)\r\n return NULL;\r\n if (c == \';\') {\r\n skip_line();\r\n continue;\r\n }\r\n if (c == \'(\')\r\n return read_list(root);\r\n if (c == \')\')\r\n return Cparen;\r\n if (c == \'.\')\r\n return Dot;\r\n if (c == \'\\\'\')\r\n return read_quote(root);\r\n if (isdigit(c))\r\n return make_int(root, read_number(c - \'0\'));\r\n if (c == \'-\' && isdigit(peek()))\r\n return make_int(root, -read_number(0));\r\n if (isalpha(c) || strchr(symbol_chars, c))\r\n return read_symbol(root, c);\r\n error(\"Don\'t know how to handle %c\", c);\r\n }\r\n}\r\n\r\n// Prints the given object.\r\nstatic void print(Obj *obj) {\r\n switch (obj->type) {\r\n case TCELL:\r\n printf(\"(\");\r\n for (;;) {\r\n print(obj->car);\r\n if (obj->cdr == Nil)\r\n break;\r\n if (obj->cdr->type != TCELL) {\r\n printf(\" . \");\r\n print(obj->cdr);\r\n break;\r\n }\r\n printf(\" \");\r\n obj = obj->cdr;\r\n }\r\n printf(\")\");\r\n return;\r\n\r\n#define CASE(type, ...) \\\r\n case type: \\\r\n printf(__VA_ARGS__); \\\r\n return\r\n CASE(TINT, \"%d\", obj->value);\r\n CASE(TSYMBOL, \"%s\", obj->name);\r\n CASE(TPRIMITIVE, \"\");\r\n CASE(TFUNCTION, \"\");\r\n CASE(TMACRO, \"\");\r\n CASE(TMOVED, \"\");\r\n CASE(TTRUE, \"t\");\r\n CASE(TNIL, \"()\");\r\n#undef CASE\r\n default:\r\n error(\"Bug: print: Unknown tag type: %d\", obj->type);\r\n }\r\n}\r\n\r\n// Returns the length of the given list. -1 if it\'s not a proper list.\r\nstatic int length(Obj *list) {\r\n int len = 0;\r\n for (; list->type == TCELL; list = list->cdr)\r\n len++;\r\n return list == Nil ? len : -1;\r\n}\r\n\r\n//======================================================================\r\n// Evaluator\r\n//======================================================================\r\n\r\nstatic Obj *eval(void *root, Obj **env, Obj **obj);\r\n\r\nstatic void add_variable(void *root, Obj **env, Obj **sym, Obj **val) {\r\n DEFINE2(vars, tmp);\r\n *vars = (*env)->vars;\r\n *tmp = acons(root, sym, val, vars);\r\n (*env)->vars = *tmp;\r\n}\r\n\r\n// Returns a newly created environment frame.\r\nstatic Obj *push_env(void *root, Obj **env, Obj **vars, Obj **vals) {\r\n DEFINE3(map, sym, val);\r\n *map = Nil;\r\n for (; (*vars)->type == TCELL; *vars = (*vars)->cdr, *vals = (*vals)->cdr) {\r\n if ((*vals)->type != TCELL)\r\n error(\"Cannot apply function: number of argument does not match\");\r\n *sym = (*vars)->car;\r\n *val = (*vals)->car;\r\n *map = acons(root, sym, val, map);\r\n }\r\n if (*vars != Nil)\r\n *map = acons(root, vars, vals, map);\r\n return make_env(root, map, env);\r\n}\r\n\r\n// Evaluates the list elements from head and returns the last return value.\r\nstatic Obj *progn(void *root, Obj **env, Obj **list) {\r\n DEFINE2(lp, r);\r\n for (*lp = *list; *lp != Nil; *lp = (*lp)->cdr) {\r\n *r = (*lp)->car;\r\n *r = eval(root, env, r);\r\n }\r\n return *r;\r\n}\r\n\r\n// Evaluates all the list elements and returns their return values as a new list.\r\nstatic Obj *eval_list(void *root, Obj **env, Obj **list) {\r\n DEFINE4(head, lp, expr, result);\r\n *head = Nil;\r\n for (lp = list; *lp != Nil; *lp = (*lp)->cdr) {\r\n *expr = (*lp)->car;\r\n *result = eval(root, env, expr);\r\n *head = cons(root, result, head);\r\n }\r\n return reverse(*head);\r\n}\r\n\r\nstatic bool is_list(Obj *obj) {\r\n return obj == Nil || obj->type == TCELL;\r\n}\r\n\r\nstatic Obj *apply_func(void *root, Obj **env, Obj **fn, Obj **args) {\r\n DEFINE3(params, newenv, body);\r\n *params = (*fn)->params;\r\n *newenv = (*fn)->env;\r\n *newenv = push_env(root, newenv, params, args);\r\n *body = (*fn)->body;\r\n return progn(root, newenv, body);\r\n}\r\n\r\n// Apply fn with args.\r\nstatic Obj *apply(void *root, Obj **env, Obj **fn, Obj **args) {\r\n if (!is_list(*args))\r\n error(\"argument must be a list\");\r\n if ((*fn)->type == TPRIMITIVE)\r\n return (*fn)->fn(root, env, args);\r\n if ((*fn)->type == TFUNCTION) {\r\n DEFINE1(eargs);\r\n *eargs = eval_list(root, env, args);\r\n return apply_func(root, env, fn, eargs);\r\n }\r\n error(\"not supported\");\r\n}\r\n\r\n// Searches for a variable by symbol. Returns null if not found.\r\nstatic Obj *find(Obj **env, Obj *sym) {\r\n for (Obj *p = *env; p != Nil; p = p->up) {\r\n for (Obj *cell = p->vars; cell != Nil; cell = cell->cdr) {\r\n Obj *bind = cell->car;\r\n if (sym == bind->car)\r\n return bind;\r\n }\r\n }\r\n return NULL;\r\n}\r\n\r\n// Expands the given macro application form.\r\nstatic Obj *macroexpand(void *root, Obj **env, Obj **obj) {\r\n if ((*obj)->type != TCELL || (*obj)->car->type != TSYMBOL)\r\n return *obj;\r\n DEFINE3(bind, macro, args);\r\n *bind = find(env, (*obj)->car);\r\n if (!*bind || (*bind)->cdr->type != TMACRO)\r\n return *obj;\r\n *macro = (*bind)->cdr;\r\n *args = (*obj)->cdr;\r\n return apply_func(root, env, macro, args);\r\n}\r\n\r\n// Evaluates the S expression.\r\nstatic Obj *eval(void *root, Obj **env, Obj **obj) {\r\n switch ((*obj)->type) {\r\n case TINT:\r\n case TPRIMITIVE:\r\n case TFUNCTION:\r\n case TTRUE:\r\n case TNIL:\r\n // Self-evaluating objects\r\n return *obj;\r\n case TSYMBOL: {\r\n // Variable\r\n Obj *bind = find(env, *obj);\r\n if (!bind)\r\n error(\"Undefined symbol: %s\", (*obj)->name);\r\n return bind->cdr;\r\n }\r\n case TCELL: {\r\n // Function application form\r\n DEFINE3(fn, expanded, args);\r\n *expanded = macroexpand(root, env, obj);\r\n if (*expanded != *obj)\r\n return eval(root, env, expanded);\r\n *fn = (*obj)->car;\r\n *fn = eval(root, env, fn);\r\n *args = (*obj)->cdr;\r\n if ((*fn)->type != TPRIMITIVE && (*fn)->type != TFUNCTION)\r\n error(\"The head of a list must be a function\");\r\n return apply(root, env, fn, args);\r\n }\r\n default:\r\n error(\"Bug: eval: Unknown tag type: %d\", (*obj)->type);\r\n }\r\n}\r\n\r\n//======================================================================\r\n// Primitive functions and special forms\r\n//======================================================================\r\n\r\n// \'expr\r\nstatic Obj *prim_quote(void *root, Obj **env, Obj **list) {\r\n if (length(*list) != 1)\r\n error(\"Malformed quote\");\r\n return (*list)->car;\r\n}\r\n\r\n// (cons expr expr)\r\nstatic Obj *prim_cons(void *root, Obj **env, Obj **list) {\r\n if (length(*list) != 2)\r\n error(\"Malformed cons\");\r\n Obj *cell = eval_list(root, env, list);\r\n cell->cdr = cell->cdr->car;\r\n return cell;\r\n}\r\n\r\n// (car )\r\nstatic Obj *prim_car(void *root, Obj **env, Obj **list) {\r\n Obj *args = eval_list(root, env, list);\r\n if (args->car->type != TCELL || args->cdr != Nil)\r\n error(\"Malformed car\");\r\n return args->car->car;\r\n}\r\n\r\n// (cdr )\r\nstatic Obj *prim_cdr(void *root, Obj **env, Obj **list) {\r\n Obj *args = eval_list(root, env, list);\r\n if (args->car->type != TCELL || args->cdr != Nil)\r\n error(\"Malformed cdr\");\r\n return args->car->cdr;\r\n}\r\n\r\n// (setq expr)\r\nstatic Obj *prim_setq(void *root, Obj **env, Obj **list) {\r\n if (length(*list) != 2 || (*list)->car->type != TSYMBOL)\r\n error(\"Malformed setq\");\r\n DEFINE2(bind, value);\r\n *bind = find(env, (*list)->car);\r\n if (!*bind)\r\n error(\"Unbound variable %s\", (*list)->car->name);\r\n *value = (*list)->cdr->car;\r\n *value = eval(root, env, value);\r\n (*bind)->cdr = *value;\r\n return *value;\r\n}\r\n\r\n// (setcar expr)\r\nstatic Obj *prim_setcar(void *root, Obj **env, Obj **list) {\r\n DEFINE1(args);\r\n *args = eval_list(root, env, list);\r\n if (length(*args) != 2 || (*args)->car->type != TCELL)\r\n error(\"Malformed setcar\");\r\n (*args)->car->car = (*args)->cdr->car;\r\n return (*args)->car;\r\n}\r\n\r\n// (while cond expr ...)\r\nstatic Obj *prim_while(void *root, Obj **env, Obj **list) {\r\n if (length(*list) < 2)\r\n error(\"Malformed while\");\r\n DEFINE2(cond, exprs);\r\n *cond = (*list)->car;\r\n while (eval(root, env, cond) != Nil) {\r\n *exprs = (*list)->cdr;\r\n eval_list(root, env, exprs);\r\n }\r\n return Nil;\r\n}\r\n\r\n// (gensym)\r\nstatic Obj *prim_gensym(void *root, Obj **env, Obj **list) {\r\n static int count = 0;\r\n char buf[10];\r\n snprintf(buf, sizeof(buf), \"G__%d\", count++);\r\n return make_symbol(root, buf);\r\n}\r\n\r\n// (+ ...)\r\nstatic Obj *prim_plus(void *root, Obj **env, Obj **list) {\r\n int sum = 0;\r\n for (Obj *args = eval_list(root, env, list); args != Nil; args = args->cdr) {\r\n if (args->car->type != TINT)\r\n error(\"+ takes only numbers\");\r\n sum += args->car->value;\r\n }\r\n return make_int(root, sum);\r\n}\r\n\r\n// (- ...)\r\nstatic Obj *prim_minus(void *root, Obj **env, Obj **list) {\r\n Obj *args = eval_list(root, env, list);\r\n for (Obj *p = args; p != Nil; p = p->cdr)\r\n if (p->car->type != TINT)\r\n error(\"- takes only numbers\");\r\n if (args->cdr == Nil)\r\n return make_int(root, -args->car->value);\r\n int r = args->car->value;\r\n for (Obj *p = args->cdr; p != Nil; p = p->cdr)\r\n r -= p->car->value;\r\n return make_int(root, r);\r\n}\r\n\r\n// (< )\r\nstatic Obj *prim_lt(void *root, Obj **env, Obj **list) {\r\n Obj *args = eval_list(root, env, list);\r\n if (length(args) != 2)\r\n error(\"malformed <\");\r\n Obj *x = args->car;\r\n Obj *y = args->cdr->car;\r\n if (x->type != TINT || y->type != TINT)\r\n error(\"< takes only numbers\");\r\n return x->value < y->value ? True : Nil;\r\n}\r\n\r\nstatic Obj *handle_function(void *root, Obj **env, Obj **list, int type) {\r\n if ((*list)->type != TCELL || !is_list((*list)->car) || (*list)->cdr->type != TCELL)\r\n error(\"Malformed lambda\");\r\n Obj *p = (*list)->car;\r\n for (; p->type == TCELL; p = p->cdr)\r\n if (p->car->type != TSYMBOL)\r\n error(\"Parameter must be a symbol\");\r\n if (p != Nil && p->type != TSYMBOL)\r\n error(\"Parameter must be a symbol\");\r\n DEFINE2(params, body);\r\n *params = (*list)->car;\r\n *body = (*list)->cdr;\r\n return make_function(root, env, type, params, body);\r\n}\r\n\r\n// (lambda ( ...) expr ...)\r\nstatic Obj *prim_lambda(void *root, Obj **env, Obj **list) {\r\n return handle_function(root, env, list, TFUNCTION);\r\n}\r\n\r\nstatic Obj *handle_defun(void *root, Obj **env, Obj **list, int type) {\r\n if ((*list)->car->type != TSYMBOL || (*list)->cdr->type != TCELL)\r\n error(\"Malformed defun\");\r\n DEFINE3(fn, sym, rest);\r\n *sym = (*list)->car;\r\n *rest = (*list)->cdr;\r\n *fn = handle_function(root, env, rest, type);\r\n add_variable(root, env, sym, fn);\r\n return *fn;\r\n}\r\n\r\n// (defun ( ...) expr ...)\r\nstatic Obj *prim_defun(void *root, Obj **env, Obj **list) {\r\n return handle_defun(root, env, list, TFUNCTION);\r\n}\r\n\r\n// (define expr)\r\nstatic Obj *prim_define(void *root, Obj **env, Obj **list) {\r\n if (length(*list) != 2 || (*list)->car->type != TSYMBOL)\r\n error(\"Malformed define\");\r\n DEFINE2(sym, value);\r\n *sym = (*list)->car;\r\n *value = (*list)->cdr->car;\r\n *value = eval(root, env, value);\r\n add_variable(root, env, sym, value);\r\n return *value;\r\n}\r\n\r\n// (defmacro ( ...) expr ...)\r\nstatic Obj *prim_defmacro(void *root, Obj **env, Obj **list) {\r\n return handle_defun(root, env, list, TMACRO);\r\n}\r\n\r\n// (macroexpand expr)\r\nstatic Obj *prim_macroexpand(void *root, Obj **env, Obj **list) {\r\n if (length(*list) != 1)\r\n error(\"Malformed macroexpand\");\r\n DEFINE1(body);\r\n *body = (*list)->car;\r\n return macroexpand(root, env, body);\r\n}\r\n\r\n// (println expr)\r\nstatic Obj *prim_println(void *root, Obj **env, Obj **list) {\r\n DEFINE1(tmp);\r\n *tmp = (*list)->car;\r\n print(eval(root, env, tmp));\r\n printf(\"\\n\");\r\n return Nil;\r\n}\r\n\r\n// (if expr expr expr ...)\r\nstatic Obj *prim_if(void *root, Obj **env, Obj **list) {\r\n if (length(*list) < 2)\r\n error(\"Malformed if\");\r\n DEFINE3(cond, then, els);\r\n *cond = (*list)->car;\r\n *cond = eval(root, env, cond);\r\n if (*cond != Nil) {\r\n *then = (*list)->cdr->car;\r\n return eval(root, env, then);\r\n }\r\n *els = (*list)->cdr->cdr;\r\n return *els == Nil ? Nil : progn(root, env, els);\r\n}\r\n\r\n// (= )\r\nstatic Obj *prim_num_eq(void *root, Obj **env, Obj **list) {\r\n if (length(*list) != 2)\r\n error(\"Malformed =\");\r\n Obj *values = eval_list(root, env, list);\r\n Obj *x = values->car;\r\n Obj *y = values->cdr->car;\r\n if (x->type != TINT || y->type != TINT)\r\n error(\"= only takes numbers\");\r\n return x->value == y->value ? True : Nil;\r\n}\r\n\r\n// (eq expr expr)\r\nstatic Obj *prim_eq(void *root, Obj **env, Obj **list) {\r\n if (length(*list) != 2)\r\n error(\"Malformed eq\");\r\n Obj *values = eval_list(root, env, list);\r\n return values->car == values->cdr->car ? True : Nil;\r\n}\r\n\r\nstatic void add_primitive(void *root, Obj **env, char *name, Primitive *fn) {\r\n DEFINE2(sym, prim);\r\n *sym = intern(root, name);\r\n *prim = make_primitive(root, fn);\r\n add_variable(root, env, sym, prim);\r\n}\r\n\r\nstatic void define_constants(void *root, Obj **env) {\r\n DEFINE1(sym);\r\n *sym = intern(root, \"t\");\r\n add_variable(root, env, sym, &True);\r\n}\r\n\r\nstatic void define_primitives(void *root, Obj **env) {\r\n add_primitive(root, env, \"quote\", prim_quote);\r\n add_primitive(root, env, \"cons\", prim_cons);\r\n add_primitive(root, env, \"car\", prim_car);\r\n add_primitive(root, env, \"cdr\", prim_cdr);\r\n add_primitive(root, env, \"setq\", prim_setq);\r\n add_primitive(root, env, \"setcar\", prim_setcar);\r\n add_primitive(root, env, \"while\", prim_while);\r\n add_primitive(root, env, \"gensym\", prim_gensym);\r\n add_primitive(root, env, \"+\", prim_plus);\r\n add_primitive(root, env, \"-\", prim_minus);\r\n add_primitive(root, env, \"<\", prim_lt);\r\n add_primitive(root, env, \"define\", prim_define);\r\n add_primitive(root, env, \"defun\", prim_defun);\r\n add_primitive(root, env, \"defmacro\", prim_defmacro);\r\n add_primitive(root, env, \"macroexpand\", prim_macroexpand);\r\n add_primitive(root, env, \"lambda\", prim_lambda);\r\n add_primitive(root, env, \"if\", prim_if);\r\n add_primitive(root, env, \"=\", prim_num_eq);\r\n add_primitive(root, env, \"eq\", prim_eq);\r\n add_primitive(root, env, \"println\", prim_println);\r\n}\r\n\r\n//======================================================================\r\n// Entry point\r\n//======================================================================\r\n\r\n// Returns true if the environment variable is defined and not the empty string.\r\nstatic bool getEnvFlag(char *name) {\r\n char *val = getenv(name);\r\n return val && val[0];\r\n}\r\n\r\nint main(int argc, char **argv) {\r\n // Debug flags\r\n debug_gc = getEnvFlag(\"MINILISP_DEBUG_GC\");\r\n always_gc = getEnvFlag(\"MINILISP_ALWAYS_GC\");\r\n\r\n // Memory allocation\r\n memory = alloc_semispace();\r\n\r\n // Constants and primitives\r\n Symbols = Nil;\r\n void *root = NULL;\r\n DEFINE2(env, expr);\r\n *env = make_env(root, &Nil, &Nil);\r\n define_constants(root, env);\r\n define_primitives(root, env);\r\n\r\n // The main loop\r\n for (;;) {\r\n *expr = read_expr(root);\r\n if (!*expr)\r\n return 0;\r\n if (*expr == Cparen)\r\n error(\"Stray close parenthesis\");\r\n if (*expr == Dot)\r\n error(\"Stray dot\");\r\n print(eval(root, env, expr));\r\n printf(\"\\n\");\r\n }\r\n}', 1); +INSERT INTO `onefile` VALUES (4, 'cJSON', 'C', '标准 C(C89) 实现的超轻量的 JSON 解析库\r\n', 12, 'https://github.com/DaveGamble/cJSON', 180, '/*\r\n Copyright (c) 2009-2017 Dave Gamble and cJSON contributors\r\n\r\n Permission is hereby granted, free of charge, to any person obtaining a copy\r\n of this software and associated documentation files (the \"Software\"), to deal\r\n in the Software without restriction, including without limitation the rights\r\n to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n copies of the Software, and to permit persons to whom the Software is\r\n furnished to do so, subject to the following conditions:\r\n\r\n The above copyright notice and this permission notice shall be included in\r\n all copies or substantial portions of the Software.\r\n\r\n THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n THE SOFTWARE.\r\n*/\r\n\r\n/* cJSON */\r\n/* JSON parser in C. */\r\n\r\n/* disable warnings about old C89 functions in MSVC */\r\n#if !defined(_CRT_SECURE_NO_DEPRECATE) && defined(_MSC_VER)\r\n#define _CRT_SECURE_NO_DEPRECATE\r\n#endif\r\n\r\n#ifdef __GNUC__\r\n#pragma GCC visibility push(default)\r\n#endif\r\n#if defined(_MSC_VER)\r\n#pragma warning (push)\r\n/* disable warning about single line comments in system headers */\r\n#pragma warning (disable : 4001)\r\n#endif\r\n\r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n\r\n#ifdef ENABLE_LOCALES\r\n#include \r\n#endif\r\n\r\n#if defined(_MSC_VER)\r\n#pragma warning (pop)\r\n#endif\r\n#ifdef __GNUC__\r\n#pragma GCC visibility pop\r\n#endif\r\n\r\n#include \"cJSON.h\"\r\n\r\n/* define our own boolean type */\r\n#ifdef true\r\n#undef true\r\n#endif\r\n#define true ((cJSON_bool)1)\r\n\r\n#ifdef false\r\n#undef false\r\n#endif\r\n#define false ((cJSON_bool)0)\r\n\r\n/* define isnan and isinf for ANSI C, if in C99 or above, isnan and isinf has been defined in math.h */\r\n#ifndef isinf\r\n#define isinf(d) (isnan((d - d)) && !isnan(d))\r\n#endif\r\n#ifndef isnan\r\n#define isnan(d) (d != d)\r\n#endif\r\n\r\n#ifndef NAN\r\n#ifdef _WIN32\r\n#define NAN sqrt(-1.0)\r\n#else\r\n#define NAN 0.0/0.0\r\n#endif\r\n#endif\r\n\r\ntypedef struct {\r\n const unsigned char *json;\r\n size_t position;\r\n} error;\r\nstatic error global_error = { NULL, 0 };\r\n\r\nCJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void)\r\n{\r\n return (const char*) (global_error.json + global_error.position);\r\n}\r\n\r\nCJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item)\r\n{\r\n if (!cJSON_IsString(item))\r\n {\r\n return NULL;\r\n }\r\n\r\n return item->valuestring;\r\n}\r\n\r\nCJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item)\r\n{\r\n if (!cJSON_IsNumber(item))\r\n {\r\n return (double) NAN;\r\n }\r\n\r\n return item->valuedouble;\r\n}\r\n\r\n/* This is a safeguard to prevent copy-pasters from using incompatible C and header files */\r\n#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 15)\r\n #error cJSON.h and cJSON.c have different versions. Make sure that both have the same.\r\n#endif\r\n\r\nCJSON_PUBLIC(const char*) cJSON_Version(void)\r\n{\r\n static char version[15];\r\n sprintf(version, \"%i.%i.%i\", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH);\r\n\r\n return version;\r\n}\r\n\r\n/* Case insensitive string comparison, doesn\'t consider two NULL pointers equal though */\r\nstatic int case_insensitive_strcmp(const unsigned char *string1, const unsigned char *string2)\r\n{\r\n if ((string1 == NULL) || (string2 == NULL))\r\n {\r\n return 1;\r\n }\r\n\r\n if (string1 == string2)\r\n {\r\n return 0;\r\n }\r\n\r\n for(; tolower(*string1) == tolower(*string2); (void)string1++, string2++)\r\n {\r\n if (*string1 == \'\\0\')\r\n {\r\n return 0;\r\n }\r\n }\r\n\r\n return tolower(*string1) - tolower(*string2);\r\n}\r\n\r\ntypedef struct internal_hooks\r\n{\r\n void *(CJSON_CDECL *allocate)(size_t size);\r\n void (CJSON_CDECL *deallocate)(void *pointer);\r\n void *(CJSON_CDECL *reallocate)(void *pointer, size_t size);\r\n} internal_hooks;\r\n\r\n#if defined(_MSC_VER)\r\n/* work around MSVC error C2322: \'...\' address of dllimport \'...\' is not static */\r\nstatic void * CJSON_CDECL internal_malloc(size_t size)\r\n{\r\n return malloc(size);\r\n}\r\nstatic void CJSON_CDECL internal_free(void *pointer)\r\n{\r\n free(pointer);\r\n}\r\nstatic void * CJSON_CDECL internal_realloc(void *pointer, size_t size)\r\n{\r\n return realloc(pointer, size);\r\n}\r\n#else\r\n#define internal_malloc malloc\r\n#define internal_free free\r\n#define internal_realloc realloc\r\n#endif\r\n\r\n/* strlen of character literals resolved at compile time */\r\n#define static_strlen(string_literal) (sizeof(string_literal) - sizeof(\"\"))\r\n\r\nstatic internal_hooks global_hooks = { internal_malloc, internal_free, internal_realloc };\r\n\r\nstatic unsigned char* cJSON_strdup(const unsigned char* string, const internal_hooks * const hooks)\r\n{\r\n size_t length = 0;\r\n unsigned char *copy = NULL;\r\n\r\n if (string == NULL)\r\n {\r\n return NULL;\r\n }\r\n\r\n length = strlen((const char*)string) + sizeof(\"\");\r\n copy = (unsigned char*)hooks->allocate(length);\r\n if (copy == NULL)\r\n {\r\n return NULL;\r\n }\r\n memcpy(copy, string, length);\r\n\r\n return copy;\r\n}\r\n\r\nCJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks)\r\n{\r\n if (hooks == NULL)\r\n {\r\n /* Reset hooks */\r\n global_hooks.allocate = malloc;\r\n global_hooks.deallocate = free;\r\n global_hooks.reallocate = realloc;\r\n return;\r\n }\r\n\r\n global_hooks.allocate = malloc;\r\n if (hooks->malloc_fn != NULL)\r\n {\r\n global_hooks.allocate = hooks->malloc_fn;\r\n }\r\n\r\n global_hooks.deallocate = free;\r\n if (hooks->free_fn != NULL)\r\n {\r\n global_hooks.deallocate = hooks->free_fn;\r\n }\r\n\r\n /* use realloc only if both free and malloc are used */\r\n global_hooks.reallocate = NULL;\r\n if ((global_hooks.allocate == malloc) && (global_hooks.deallocate == free))\r\n {\r\n global_hooks.reallocate = realloc;\r\n }\r\n}\r\n\r\n/* Internal constructor. */\r\nstatic cJSON *cJSON_New_Item(const internal_hooks * const hooks)\r\n{\r\n cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON));\r\n if (node)\r\n {\r\n memset(node, \'\\0\', sizeof(cJSON));\r\n }\r\n\r\n return node;\r\n}\r\n\r\n/* Delete a cJSON structure. */\r\nCJSON_PUBLIC(void) cJSON_Delete(cJSON *item)\r\n{\r\n cJSON *next = NULL;\r\n while (item != NULL)\r\n {\r\n next = item->next;\r\n if (!(item->type & cJSON_IsReference) && (item->child != NULL))\r\n {\r\n cJSON_Delete(item->child);\r\n }\r\n if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL))\r\n {\r\n global_hooks.deallocate(item->valuestring);\r\n }\r\n if (!(item->type & cJSON_StringIsConst) && (item->string != NULL))\r\n {\r\n global_hooks.deallocate(item->string);\r\n }\r\n global_hooks.deallocate(item);\r\n item = next;\r\n }\r\n}\r\n\r\n/* get the decimal point character of the current locale */\r\nstatic unsigned char get_decimal_point(void)\r\n{\r\n#ifdef ENABLE_LOCALES\r\n struct lconv *lconv = localeconv();\r\n return (unsigned char) lconv->decimal_point[0];\r\n#else\r\n return \'.\';\r\n#endif\r\n}\r\n\r\ntypedef struct\r\n{\r\n const unsigned char *content;\r\n size_t length;\r\n size_t offset;\r\n size_t depth; /* How deeply nested (in arrays/objects) is the input at the current offset. */\r\n internal_hooks hooks;\r\n} parse_buffer;\r\n\r\n/* check if the given size is left to read in a given parse buffer (starting with 1) */\r\n#define can_read(buffer, size) ((buffer != NULL) && (((buffer)->offset + size) <= (buffer)->length))\r\n/* check if the buffer can be accessed at the given index (starting with 0) */\r\n#define can_access_at_index(buffer, index) ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length))\r\n#define cannot_access_at_index(buffer, index) (!can_access_at_index(buffer, index))\r\n/* get a pointer to the buffer at the position */\r\n#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset)\r\n\r\n/* Parse the input text to generate a number, and populate the result into item. */\r\nstatic cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_buffer)\r\n{\r\n double number = 0;\r\n unsigned char *after_end = NULL;\r\n unsigned char number_c_string[64];\r\n unsigned char decimal_point = get_decimal_point();\r\n size_t i = 0;\r\n\r\n if ((input_buffer == NULL) || (input_buffer->content == NULL))\r\n {\r\n return false;\r\n }\r\n\r\n /* copy the number into a temporary buffer and replace \'.\' with the decimal point\r\n * of the current locale (for strtod)\r\n * This also takes care of \'\\0\' not necessarily being available for marking the end of the input */\r\n for (i = 0; (i < (sizeof(number_c_string) - 1)) && can_access_at_index(input_buffer, i); i++)\r\n {\r\n switch (buffer_at_offset(input_buffer)[i])\r\n {\r\n case \'0\':\r\n case \'1\':\r\n case \'2\':\r\n case \'3\':\r\n case \'4\':\r\n case \'5\':\r\n case \'6\':\r\n case \'7\':\r\n case \'8\':\r\n case \'9\':\r\n case \'+\':\r\n case \'-\':\r\n case \'e\':\r\n case \'E\':\r\n number_c_string[i] = buffer_at_offset(input_buffer)[i];\r\n break;\r\n\r\n case \'.\':\r\n number_c_string[i] = decimal_point;\r\n break;\r\n\r\n default:\r\n goto loop_end;\r\n }\r\n }\r\nloop_end:\r\n number_c_string[i] = \'\\0\';\r\n\r\n number = strtod((const char*)number_c_string, (char**)&after_end);\r\n if (number_c_string == after_end)\r\n {\r\n return false; /* parse_error */\r\n }\r\n\r\n item->valuedouble = number;\r\n\r\n /* use saturation in case of overflow */\r\n if (number >= INT_MAX)\r\n {\r\n item->valueint = INT_MAX;\r\n }\r\n else if (number <= (double)INT_MIN)\r\n {\r\n item->valueint = INT_MIN;\r\n }\r\n else\r\n {\r\n item->valueint = (int)number;\r\n }\r\n\r\n item->type = cJSON_Number;\r\n\r\n input_buffer->offset += (size_t)(after_end - number_c_string);\r\n return true;\r\n}\r\n\r\n/* don\'t ask me, but the original cJSON_SetNumberValue returns an integer or double */\r\nCJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number)\r\n{\r\n if (number >= INT_MAX)\r\n {\r\n object->valueint = INT_MAX;\r\n }\r\n else if (number <= (double)INT_MIN)\r\n {\r\n object->valueint = INT_MIN;\r\n }\r\n else\r\n {\r\n object->valueint = (int)number;\r\n }\r\n\r\n return object->valuedouble = number;\r\n}\r\n\r\nCJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring)\r\n{\r\n char *copy = NULL;\r\n /* if object\'s type is not cJSON_String or is cJSON_IsReference, it should not set valuestring */\r\n if (!(object->type & cJSON_String) || (object->type & cJSON_IsReference))\r\n {\r\n return NULL;\r\n }\r\n if (strlen(valuestring) <= strlen(object->valuestring))\r\n {\r\n strcpy(object->valuestring, valuestring);\r\n return object->valuestring;\r\n }\r\n copy = (char*) cJSON_strdup((const unsigned char*)valuestring, &global_hooks);\r\n if (copy == NULL)\r\n {\r\n return NULL;\r\n }\r\n if (object->valuestring != NULL)\r\n {\r\n cJSON_free(object->valuestring);\r\n }\r\n object->valuestring = copy;\r\n\r\n return copy;\r\n}\r\n\r\ntypedef struct\r\n{\r\n unsigned char *buffer;\r\n size_t length;\r\n size_t offset;\r\n size_t depth; /* current nesting depth (for formatted printing) */\r\n cJSON_bool noalloc;\r\n cJSON_bool format; /* is this print a formatted print */\r\n internal_hooks hooks;\r\n} printbuffer;\r\n\r\n/* realloc printbuffer if necessary to have at least \"needed\" bytes more */\r\nstatic unsigned char* ensure(printbuffer * const p, size_t needed)\r\n{\r\n unsigned char *newbuffer = NULL;\r\n size_t newsize = 0;\r\n\r\n if ((p == NULL) || (p->buffer == NULL))\r\n {\r\n return NULL;\r\n }\r\n\r\n if ((p->length > 0) && (p->offset >= p->length))\r\n {\r\n /* make sure that offset is valid */\r\n return NULL;\r\n }\r\n\r\n if (needed > INT_MAX)\r\n {\r\n /* sizes bigger than INT_MAX are currently not supported */\r\n return NULL;\r\n }\r\n\r\n needed += p->offset + 1;\r\n if (needed <= p->length)\r\n {\r\n return p->buffer + p->offset;\r\n }\r\n\r\n if (p->noalloc) {\r\n return NULL;\r\n }\r\n\r\n /* calculate new buffer size */\r\n if (needed > (INT_MAX / 2))\r\n {\r\n /* overflow of int, use INT_MAX if possible */\r\n if (needed <= INT_MAX)\r\n {\r\n newsize = INT_MAX;\r\n }\r\n else\r\n {\r\n return NULL;\r\n }\r\n }\r\n else\r\n {\r\n newsize = needed * 2;\r\n }\r\n\r\n if (p->hooks.reallocate != NULL)\r\n {\r\n /* reallocate with realloc if available */\r\n newbuffer = (unsigned char*)p->hooks.reallocate(p->buffer, newsize);\r\n if (newbuffer == NULL)\r\n {\r\n p->hooks.deallocate(p->buffer);\r\n p->length = 0;\r\n p->buffer = NULL;\r\n\r\n return NULL;\r\n }\r\n }\r\n else\r\n {\r\n /* otherwise reallocate manually */\r\n newbuffer = (unsigned char*)p->hooks.allocate(newsize);\r\n if (!newbuffer)\r\n {\r\n p->hooks.deallocate(p->buffer);\r\n p->length = 0;\r\n p->buffer = NULL;\r\n\r\n return NULL;\r\n }\r\n\r\n memcpy(newbuffer, p->buffer, p->offset + 1);\r\n p->hooks.deallocate(p->buffer);\r\n }\r\n p->length = newsize;\r\n p->buffer = newbuffer;\r\n\r\n return newbuffer + p->offset;\r\n}\r\n\r\n/* calculate the new length of the string in a printbuffer and update the offset */\r\nstatic void update_offset(printbuffer * const buffer)\r\n{\r\n const unsigned char *buffer_pointer = NULL;\r\n if ((buffer == NULL) || (buffer->buffer == NULL))\r\n {\r\n return;\r\n }\r\n buffer_pointer = buffer->buffer + buffer->offset;\r\n\r\n buffer->offset += strlen((const char*)buffer_pointer);\r\n}\r\n\r\n/* securely comparison of floating-point variables */\r\nstatic cJSON_bool compare_double(double a, double b)\r\n{\r\n double maxVal = fabs(a) > fabs(b) ? fabs(a) : fabs(b);\r\n return (fabs(a - b) <= maxVal * DBL_EPSILON);\r\n}\r\n\r\n/* Render the number nicely from the given item into a string. */\r\nstatic cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer)\r\n{\r\n unsigned char *output_pointer = NULL;\r\n double d = item->valuedouble;\r\n int length = 0;\r\n size_t i = 0;\r\n unsigned char number_buffer[26] = {0}; /* temporary buffer to print the number into */\r\n unsigned char decimal_point = get_decimal_point();\r\n double test = 0.0;\r\n\r\n if (output_buffer == NULL)\r\n {\r\n return false;\r\n }\r\n\r\n /* This checks for NaN and Infinity */\r\n if (isnan(d) || isinf(d))\r\n {\r\n length = sprintf((char*)number_buffer, \"null\");\r\n }\r\n else if(d == (double)item->valueint)\r\n {\r\n length = sprintf((char*)number_buffer, \"%d\", item->valueint);\r\n }\r\n else\r\n {\r\n /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */\r\n length = sprintf((char*)number_buffer, \"%1.15g\", d);\r\n\r\n /* Check whether the original double can be recovered */\r\n if ((sscanf((char*)number_buffer, \"%lg\", &test) != 1) || !compare_double((double)test, d))\r\n {\r\n /* If not, print with 17 decimal places of precision */\r\n length = sprintf((char*)number_buffer, \"%1.17g\", d);\r\n }\r\n }\r\n\r\n /* sprintf failed or buffer overrun occurred */\r\n if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1)))\r\n {\r\n return false;\r\n }\r\n\r\n /* reserve appropriate space in the output */\r\n output_pointer = ensure(output_buffer, (size_t)length + sizeof(\"\"));\r\n if (output_pointer == NULL)\r\n {\r\n return false;\r\n }\r\n\r\n /* copy the printed number to the output and replace locale\r\n * dependent decimal point with \'.\' */\r\n for (i = 0; i < ((size_t)length); i++)\r\n {\r\n if (number_buffer[i] == decimal_point)\r\n {\r\n output_pointer[i] = \'.\';\r\n continue;\r\n }\r\n\r\n output_pointer[i] = number_buffer[i];\r\n }\r\n output_pointer[i] = \'\\0\';\r\n\r\n output_buffer->offset += (size_t)length;\r\n\r\n return true;\r\n}\r\n\r\n/* parse 4 digit hexadecimal number */\r\nstatic unsigned parse_hex4(const unsigned char * const input)\r\n{\r\n unsigned int h = 0;\r\n size_t i = 0;\r\n\r\n for (i = 0; i < 4; i++)\r\n {\r\n /* parse digit */\r\n if ((input[i] >= \'0\') && (input[i] <= \'9\'))\r\n {\r\n h += (unsigned int) input[i] - \'0\';\r\n }\r\n else if ((input[i] >= \'A\') && (input[i] <= \'F\'))\r\n {\r\n h += (unsigned int) 10 + input[i] - \'A\';\r\n }\r\n else if ((input[i] >= \'a\') && (input[i] <= \'f\'))\r\n {\r\n h += (unsigned int) 10 + input[i] - \'a\';\r\n }\r\n else /* invalid */\r\n {\r\n return 0;\r\n }\r\n\r\n if (i < 3)\r\n {\r\n /* shift left to make place for the next nibble */\r\n h = h << 4;\r\n }\r\n }\r\n\r\n return h;\r\n}\r\n\r\n/* converts a UTF-16 literal to UTF-8\r\n * A literal can be one or two sequences of the form \\uXXXX */\r\nstatic unsigned char utf16_literal_to_utf8(const unsigned char * const input_pointer, const unsigned char * const input_end, unsigned char **output_pointer)\r\n{\r\n long unsigned int codepoint = 0;\r\n unsigned int first_code = 0;\r\n const unsigned char *first_sequence = input_pointer;\r\n unsigned char utf8_length = 0;\r\n unsigned char utf8_position = 0;\r\n unsigned char sequence_length = 0;\r\n unsigned char first_byte_mark = 0;\r\n\r\n if ((input_end - first_sequence) < 6)\r\n {\r\n /* input ends unexpectedly */\r\n goto fail;\r\n }\r\n\r\n /* get the first utf16 sequence */\r\n first_code = parse_hex4(first_sequence + 2);\r\n\r\n /* check that the code is valid */\r\n if (((first_code >= 0xDC00) && (first_code <= 0xDFFF)))\r\n {\r\n goto fail;\r\n }\r\n\r\n /* UTF16 surrogate pair */\r\n if ((first_code >= 0xD800) && (first_code <= 0xDBFF))\r\n {\r\n const unsigned char *second_sequence = first_sequence + 6;\r\n unsigned int second_code = 0;\r\n sequence_length = 12; /* \\uXXXX\\uXXXX */\r\n\r\n if ((input_end - second_sequence) < 6)\r\n {\r\n /* input ends unexpectedly */\r\n goto fail;\r\n }\r\n\r\n if ((second_sequence[0] != \'\\\\\') || (second_sequence[1] != \'u\'))\r\n {\r\n /* missing second half of the surrogate pair */\r\n goto fail;\r\n }\r\n\r\n /* get the second utf16 sequence */\r\n second_code = parse_hex4(second_sequence + 2);\r\n /* check that the code is valid */\r\n if ((second_code < 0xDC00) || (second_code > 0xDFFF))\r\n {\r\n /* invalid second half of the surrogate pair */\r\n goto fail;\r\n }\r\n\r\n\r\n /* calculate the unicode codepoint from the surrogate pair */\r\n codepoint = 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF));\r\n }\r\n else\r\n {\r\n sequence_length = 6; /* \\uXXXX */\r\n codepoint = first_code;\r\n }\r\n\r\n /* encode as UTF-8\r\n * takes at maximum 4 bytes to encode:\r\n * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */\r\n if (codepoint < 0x80)\r\n {\r\n /* normal ascii, encoding 0xxxxxxx */\r\n utf8_length = 1;\r\n }\r\n else if (codepoint < 0x800)\r\n {\r\n /* two bytes, encoding 110xxxxx 10xxxxxx */\r\n utf8_length = 2;\r\n first_byte_mark = 0xC0; /* 11000000 */\r\n }\r\n else if (codepoint < 0x10000)\r\n {\r\n /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */\r\n utf8_length = 3;\r\n first_byte_mark = 0xE0; /* 11100000 */\r\n }\r\n else if (codepoint <= 0x10FFFF)\r\n {\r\n /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */\r\n utf8_length = 4;\r\n first_byte_mark = 0xF0; /* 11110000 */\r\n }\r\n else\r\n {\r\n /* invalid unicode codepoint */\r\n goto fail;\r\n }\r\n\r\n /* encode as utf8 */\r\n for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; utf8_position--)\r\n {\r\n /* 10xxxxxx */\r\n (*output_pointer)[utf8_position] = (unsigned char)((codepoint | 0x80) & 0xBF);\r\n codepoint >>= 6;\r\n }\r\n /* encode first byte */\r\n if (utf8_length > 1)\r\n {\r\n (*output_pointer)[0] = (unsigned char)((codepoint | first_byte_mark) & 0xFF);\r\n }\r\n else\r\n {\r\n (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F);\r\n }\r\n\r\n *output_pointer += utf8_length;\r\n\r\n return sequence_length;\r\n\r\nfail:\r\n return 0;\r\n}\r\n\r\n/* Parse the input text into an unescaped cinput, and populate item. */\r\nstatic cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_buffer)\r\n{\r\n const unsigned char *input_pointer = buffer_at_offset(input_buffer) + 1;\r\n const unsigned char *input_end = buffer_at_offset(input_buffer) + 1;\r\n unsigned char *output_pointer = NULL;\r\n unsigned char *output = NULL;\r\n\r\n /* not a string */\r\n if (buffer_at_offset(input_buffer)[0] != \'\\\"\')\r\n {\r\n goto fail;\r\n }\r\n\r\n {\r\n /* calculate approximate size of the output (overestimate) */\r\n size_t allocation_length = 0;\r\n size_t skipped_bytes = 0;\r\n while (((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != \'\\\"\'))\r\n {\r\n /* is escape sequence */\r\n if (input_end[0] == \'\\\\\')\r\n {\r\n if ((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length)\r\n {\r\n /* prevent buffer overflow when last input character is a backslash */\r\n goto fail;\r\n }\r\n skipped_bytes++;\r\n input_end++;\r\n }\r\n input_end++;\r\n }\r\n if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != \'\\\"\'))\r\n {\r\n goto fail; /* string ended unexpectedly */\r\n }\r\n\r\n /* This is at most how much we need for the output */\r\n allocation_length = (size_t) (input_end - buffer_at_offset(input_buffer)) - skipped_bytes;\r\n output = (unsigned char*)input_buffer->hooks.allocate(allocation_length + sizeof(\"\"));\r\n if (output == NULL)\r\n {\r\n goto fail; /* allocation failure */\r\n }\r\n }\r\n\r\n output_pointer = output;\r\n /* loop through the string literal */\r\n while (input_pointer < input_end)\r\n {\r\n if (*input_pointer != \'\\\\\')\r\n {\r\n *output_pointer++ = *input_pointer++;\r\n }\r\n /* escape sequence */\r\n else\r\n {\r\n unsigned char sequence_length = 2;\r\n if ((input_end - input_pointer) < 1)\r\n {\r\n goto fail;\r\n }\r\n\r\n switch (input_pointer[1])\r\n {\r\n case \'b\':\r\n *output_pointer++ = \'\\b\';\r\n break;\r\n case \'f\':\r\n *output_pointer++ = \'\\f\';\r\n break;\r\n case \'n\':\r\n *output_pointer++ = \'\\n\';\r\n break;\r\n case \'r\':\r\n *output_pointer++ = \'\\r\';\r\n break;\r\n case \'t\':\r\n *output_pointer++ = \'\\t\';\r\n break;\r\n case \'\\\"\':\r\n case \'\\\\\':\r\n case \'/\':\r\n *output_pointer++ = input_pointer[1];\r\n break;\r\n\r\n /* UTF-16 literal */\r\n case \'u\':\r\n sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer);\r\n if (sequence_length == 0)\r\n {\r\n /* failed to convert UTF16-literal to UTF-8 */\r\n goto fail;\r\n }\r\n break;\r\n\r\n default:\r\n goto fail;\r\n }\r\n input_pointer += sequence_length;\r\n }\r\n }\r\n\r\n /* zero terminate the output */\r\n *output_pointer = \'\\0\';\r\n\r\n item->type = cJSON_String;\r\n item->valuestring = (char*)output;\r\n\r\n input_buffer->offset = (size_t) (input_end - input_buffer->content);\r\n input_buffer->offset++;\r\n\r\n return true;\r\n\r\nfail:\r\n if (output != NULL)\r\n {\r\n input_buffer->hooks.deallocate(output);\r\n }\r\n\r\n if (input_pointer != NULL)\r\n {\r\n input_buffer->offset = (size_t)(input_pointer - input_buffer->content);\r\n }\r\n\r\n return false;\r\n}\r\n\r\n/* Render the cstring provided to an escaped version that can be printed. */\r\nstatic cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer)\r\n{\r\n const unsigned char *input_pointer = NULL;\r\n unsigned char *output = NULL;\r\n unsigned char *output_pointer = NULL;\r\n size_t output_length = 0;\r\n /* numbers of additional characters needed for escaping */\r\n size_t escape_characters = 0;\r\n\r\n if (output_buffer == NULL)\r\n {\r\n return false;\r\n }\r\n\r\n /* empty string */\r\n if (input == NULL)\r\n {\r\n output = ensure(output_buffer, sizeof(\"\\\"\\\"\"));\r\n if (output == NULL)\r\n {\r\n return false;\r\n }\r\n strcpy((char*)output, \"\\\"\\\"\");\r\n\r\n return true;\r\n }\r\n\r\n /* set \"flag\" to 1 if something needs to be escaped */\r\n for (input_pointer = input; *input_pointer; input_pointer++)\r\n {\r\n switch (*input_pointer)\r\n {\r\n case \'\\\"\':\r\n case \'\\\\\':\r\n case \'\\b\':\r\n case \'\\f\':\r\n case \'\\n\':\r\n case \'\\r\':\r\n case \'\\t\':\r\n /* one character escape sequence */\r\n escape_characters++;\r\n break;\r\n default:\r\n if (*input_pointer < 32)\r\n {\r\n /* UTF-16 escape sequence uXXXX */\r\n escape_characters += 5;\r\n }\r\n break;\r\n }\r\n }\r\n output_length = (size_t)(input_pointer - input) + escape_characters;\r\n\r\n output = ensure(output_buffer, output_length + sizeof(\"\\\"\\\"\"));\r\n if (output == NULL)\r\n {\r\n return false;\r\n }\r\n\r\n /* no characters have to be escaped */\r\n if (escape_characters == 0)\r\n {\r\n output[0] = \'\\\"\';\r\n memcpy(output + 1, input, output_length);\r\n output[output_length + 1] = \'\\\"\';\r\n output[output_length + 2] = \'\\0\';\r\n\r\n return true;\r\n }\r\n\r\n output[0] = \'\\\"\';\r\n output_pointer = output + 1;\r\n /* copy the string */\r\n for (input_pointer = input; *input_pointer != \'\\0\'; (void)input_pointer++, output_pointer++)\r\n {\r\n if ((*input_pointer > 31) && (*input_pointer != \'\\\"\') && (*input_pointer != \'\\\\\'))\r\n {\r\n /* normal character, copy */\r\n *output_pointer = *input_pointer;\r\n }\r\n else\r\n {\r\n /* character needs to be escaped */\r\n *output_pointer++ = \'\\\\\';\r\n switch (*input_pointer)\r\n {\r\n case \'\\\\\':\r\n *output_pointer = \'\\\\\';\r\n break;\r\n case \'\\\"\':\r\n *output_pointer = \'\\\"\';\r\n break;\r\n case \'\\b\':\r\n *output_pointer = \'b\';\r\n break;\r\n case \'\\f\':\r\n *output_pointer = \'f\';\r\n break;\r\n case \'\\n\':\r\n *output_pointer = \'n\';\r\n break;\r\n case \'\\r\':\r\n *output_pointer = \'r\';\r\n break;\r\n case \'\\t\':\r\n *output_pointer = \'t\';\r\n break;\r\n default:\r\n /* escape and print as unicode codepoint */\r\n sprintf((char*)output_pointer, \"u%04x\", *input_pointer);\r\n output_pointer += 4;\r\n break;\r\n }\r\n }\r\n }\r\n output[output_length + 1] = \'\\\"\';\r\n output[output_length + 2] = \'\\0\';\r\n\r\n return true;\r\n}\r\n\r\n/* Invoke print_string_ptr (which is useful) on an item. */\r\nstatic cJSON_bool print_string(const cJSON * const item, printbuffer * const p)\r\n{\r\n return print_string_ptr((unsigned char*)item->valuestring, p);\r\n}\r\n\r\n/* Predeclare these prototypes. */\r\nstatic cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer);\r\nstatic cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer);\r\nstatic cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer);\r\nstatic cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer);\r\nstatic cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer);\r\nstatic cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer);\r\n\r\n/* Utility to jump whitespace and cr/lf */\r\nstatic parse_buffer *buffer_skip_whitespace(parse_buffer * const buffer)\r\n{\r\n if ((buffer == NULL) || (buffer->content == NULL))\r\n {\r\n return NULL;\r\n }\r\n\r\n if (cannot_access_at_index(buffer, 0))\r\n {\r\n return buffer;\r\n }\r\n\r\n while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32))\r\n {\r\n buffer->offset++;\r\n }\r\n\r\n if (buffer->offset == buffer->length)\r\n {\r\n buffer->offset--;\r\n }\r\n\r\n return buffer;\r\n}\r\n\r\n/* skip the UTF-8 BOM (byte order mark) if it is at the beginning of a buffer */\r\nstatic parse_buffer *skip_utf8_bom(parse_buffer * const buffer)\r\n{\r\n if ((buffer == NULL) || (buffer->content == NULL) || (buffer->offset != 0))\r\n {\r\n return NULL;\r\n }\r\n\r\n if (can_access_at_index(buffer, 4) && (strncmp((const char*)buffer_at_offset(buffer), \"\\xEF\\xBB\\xBF\", 3) == 0))\r\n {\r\n buffer->offset += 3;\r\n }\r\n\r\n return buffer;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated)\r\n{\r\n size_t buffer_length;\r\n\r\n if (NULL == value)\r\n {\r\n return NULL;\r\n }\r\n\r\n /* Adding null character size due to require_null_terminated. */\r\n buffer_length = strlen(value) + sizeof(\"\");\r\n\r\n return cJSON_ParseWithLengthOpts(value, buffer_length, return_parse_end, require_null_terminated);\r\n}\r\n\r\n/* Parse an object - create a new root, and populate. */\r\nCJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated)\r\n{\r\n parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } };\r\n cJSON *item = NULL;\r\n\r\n /* reset error position */\r\n global_error.json = NULL;\r\n global_error.position = 0;\r\n\r\n if (value == NULL || 0 == buffer_length)\r\n {\r\n goto fail;\r\n }\r\n\r\n buffer.content = (const unsigned char*)value;\r\n buffer.length = buffer_length;\r\n buffer.offset = 0;\r\n buffer.hooks = global_hooks;\r\n\r\n item = cJSON_New_Item(&global_hooks);\r\n if (item == NULL) /* memory fail */\r\n {\r\n goto fail;\r\n }\r\n\r\n if (!parse_value(item, buffer_skip_whitespace(skip_utf8_bom(&buffer))))\r\n {\r\n /* parse failure. ep is set. */\r\n goto fail;\r\n }\r\n\r\n /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */\r\n if (require_null_terminated)\r\n {\r\n buffer_skip_whitespace(&buffer);\r\n if ((buffer.offset >= buffer.length) || buffer_at_offset(&buffer)[0] != \'\\0\')\r\n {\r\n goto fail;\r\n }\r\n }\r\n if (return_parse_end)\r\n {\r\n *return_parse_end = (const char*)buffer_at_offset(&buffer);\r\n }\r\n\r\n return item;\r\n\r\nfail:\r\n if (item != NULL)\r\n {\r\n cJSON_Delete(item);\r\n }\r\n\r\n if (value != NULL)\r\n {\r\n error local_error;\r\n local_error.json = (const unsigned char*)value;\r\n local_error.position = 0;\r\n\r\n if (buffer.offset < buffer.length)\r\n {\r\n local_error.position = buffer.offset;\r\n }\r\n else if (buffer.length > 0)\r\n {\r\n local_error.position = buffer.length - 1;\r\n }\r\n\r\n if (return_parse_end != NULL)\r\n {\r\n *return_parse_end = (const char*)local_error.json + local_error.position;\r\n }\r\n\r\n global_error = local_error;\r\n }\r\n\r\n return NULL;\r\n}\r\n\r\n/* Default options for cJSON_Parse */\r\nCJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value)\r\n{\r\n return cJSON_ParseWithOpts(value, 0, 0);\r\n}\r\n\r\nCJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length)\r\n{\r\n return cJSON_ParseWithLengthOpts(value, buffer_length, 0, 0);\r\n}\r\n\r\n#define cjson_min(a, b) (((a) < (b)) ? (a) : (b))\r\n\r\nstatic unsigned char *print(const cJSON * const item, cJSON_bool format, const internal_hooks * const hooks)\r\n{\r\n static const size_t default_buffer_size = 256;\r\n printbuffer buffer[1];\r\n unsigned char *printed = NULL;\r\n\r\n memset(buffer, 0, sizeof(buffer));\r\n\r\n /* create buffer */\r\n buffer->buffer = (unsigned char*) hooks->allocate(default_buffer_size);\r\n buffer->length = default_buffer_size;\r\n buffer->format = format;\r\n buffer->hooks = *hooks;\r\n if (buffer->buffer == NULL)\r\n {\r\n goto fail;\r\n }\r\n\r\n /* print the value */\r\n if (!print_value(item, buffer))\r\n {\r\n goto fail;\r\n }\r\n update_offset(buffer);\r\n\r\n /* check if reallocate is available */\r\n if (hooks->reallocate != NULL)\r\n {\r\n printed = (unsigned char*) hooks->reallocate(buffer->buffer, buffer->offset + 1);\r\n if (printed == NULL) {\r\n goto fail;\r\n }\r\n buffer->buffer = NULL;\r\n }\r\n else /* otherwise copy the JSON over to a new buffer */\r\n {\r\n printed = (unsigned char*) hooks->allocate(buffer->offset + 1);\r\n if (printed == NULL)\r\n {\r\n goto fail;\r\n }\r\n memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1));\r\n printed[buffer->offset] = \'\\0\'; /* just to be sure */\r\n\r\n /* free the buffer */\r\n hooks->deallocate(buffer->buffer);\r\n }\r\n\r\n return printed;\r\n\r\nfail:\r\n if (buffer->buffer != NULL)\r\n {\r\n hooks->deallocate(buffer->buffer);\r\n }\r\n\r\n if (printed != NULL)\r\n {\r\n hooks->deallocate(printed);\r\n }\r\n\r\n return NULL;\r\n}\r\n\r\n/* Render a cJSON item/entity/structure to text. */\r\nCJSON_PUBLIC(char *) cJSON_Print(const cJSON *item)\r\n{\r\n return (char*)print(item, true, &global_hooks);\r\n}\r\n\r\nCJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item)\r\n{\r\n return (char*)print(item, false, &global_hooks);\r\n}\r\n\r\nCJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt)\r\n{\r\n printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } };\r\n\r\n if (prebuffer < 0)\r\n {\r\n return NULL;\r\n }\r\n\r\n p.buffer = (unsigned char*)global_hooks.allocate((size_t)prebuffer);\r\n if (!p.buffer)\r\n {\r\n return NULL;\r\n }\r\n\r\n p.length = (size_t)prebuffer;\r\n p.offset = 0;\r\n p.noalloc = false;\r\n p.format = fmt;\r\n p.hooks = global_hooks;\r\n\r\n if (!print_value(item, &p))\r\n {\r\n global_hooks.deallocate(p.buffer);\r\n return NULL;\r\n }\r\n\r\n return (char*)p.buffer;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format)\r\n{\r\n printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } };\r\n\r\n if ((length < 0) || (buffer == NULL))\r\n {\r\n return false;\r\n }\r\n\r\n p.buffer = (unsigned char*)buffer;\r\n p.length = (size_t)length;\r\n p.offset = 0;\r\n p.noalloc = true;\r\n p.format = format;\r\n p.hooks = global_hooks;\r\n\r\n return print_value(item, &p);\r\n}\r\n\r\n/* Parser core - when encountering text, process appropriately. */\r\nstatic cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer)\r\n{\r\n if ((input_buffer == NULL) || (input_buffer->content == NULL))\r\n {\r\n return false; /* no input */\r\n }\r\n\r\n /* parse the different types of values */\r\n /* null */\r\n if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), \"null\", 4) == 0))\r\n {\r\n item->type = cJSON_NULL;\r\n input_buffer->offset += 4;\r\n return true;\r\n }\r\n /* false */\r\n if (can_read(input_buffer, 5) && (strncmp((const char*)buffer_at_offset(input_buffer), \"false\", 5) == 0))\r\n {\r\n item->type = cJSON_False;\r\n input_buffer->offset += 5;\r\n return true;\r\n }\r\n /* true */\r\n if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), \"true\", 4) == 0))\r\n {\r\n item->type = cJSON_True;\r\n item->valueint = 1;\r\n input_buffer->offset += 4;\r\n return true;\r\n }\r\n /* string */\r\n if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == \'\\\"\'))\r\n {\r\n return parse_string(item, input_buffer);\r\n }\r\n /* number */\r\n if (can_access_at_index(input_buffer, 0) && ((buffer_at_offset(input_buffer)[0] == \'-\') || ((buffer_at_offset(input_buffer)[0] >= \'0\') && (buffer_at_offset(input_buffer)[0] <= \'9\'))))\r\n {\r\n return parse_number(item, input_buffer);\r\n }\r\n /* array */\r\n if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == \'[\'))\r\n {\r\n return parse_array(item, input_buffer);\r\n }\r\n /* object */\r\n if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == \'{\'))\r\n {\r\n return parse_object(item, input_buffer);\r\n }\r\n\r\n return false;\r\n}\r\n\r\n/* Render a value to text. */\r\nstatic cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer)\r\n{\r\n unsigned char *output = NULL;\r\n\r\n if ((item == NULL) || (output_buffer == NULL))\r\n {\r\n return false;\r\n }\r\n\r\n switch ((item->type) & 0xFF)\r\n {\r\n case cJSON_NULL:\r\n output = ensure(output_buffer, 5);\r\n if (output == NULL)\r\n {\r\n return false;\r\n }\r\n strcpy((char*)output, \"null\");\r\n return true;\r\n\r\n case cJSON_False:\r\n output = ensure(output_buffer, 6);\r\n if (output == NULL)\r\n {\r\n return false;\r\n }\r\n strcpy((char*)output, \"false\");\r\n return true;\r\n\r\n case cJSON_True:\r\n output = ensure(output_buffer, 5);\r\n if (output == NULL)\r\n {\r\n return false;\r\n }\r\n strcpy((char*)output, \"true\");\r\n return true;\r\n\r\n case cJSON_Number:\r\n return print_number(item, output_buffer);\r\n\r\n case cJSON_Raw:\r\n {\r\n size_t raw_length = 0;\r\n if (item->valuestring == NULL)\r\n {\r\n return false;\r\n }\r\n\r\n raw_length = strlen(item->valuestring) + sizeof(\"\");\r\n output = ensure(output_buffer, raw_length);\r\n if (output == NULL)\r\n {\r\n return false;\r\n }\r\n memcpy(output, item->valuestring, raw_length);\r\n return true;\r\n }\r\n\r\n case cJSON_String:\r\n return print_string(item, output_buffer);\r\n\r\n case cJSON_Array:\r\n return print_array(item, output_buffer);\r\n\r\n case cJSON_Object:\r\n return print_object(item, output_buffer);\r\n\r\n default:\r\n return false;\r\n }\r\n}\r\n\r\n/* Build an array from input text. */\r\nstatic cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer)\r\n{\r\n cJSON *head = NULL; /* head of the linked list */\r\n cJSON *current_item = NULL;\r\n\r\n if (input_buffer->depth >= CJSON_NESTING_LIMIT)\r\n {\r\n return false; /* to deeply nested */\r\n }\r\n input_buffer->depth++;\r\n\r\n if (buffer_at_offset(input_buffer)[0] != \'[\')\r\n {\r\n /* not an array */\r\n goto fail;\r\n }\r\n\r\n input_buffer->offset++;\r\n buffer_skip_whitespace(input_buffer);\r\n if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == \']\'))\r\n {\r\n /* empty array */\r\n goto success;\r\n }\r\n\r\n /* check if we skipped to the end of the buffer */\r\n if (cannot_access_at_index(input_buffer, 0))\r\n {\r\n input_buffer->offset--;\r\n goto fail;\r\n }\r\n\r\n /* step back to character in front of the first element */\r\n input_buffer->offset--;\r\n /* loop through the comma separated array elements */\r\n do\r\n {\r\n /* allocate next item */\r\n cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks));\r\n if (new_item == NULL)\r\n {\r\n goto fail; /* allocation failure */\r\n }\r\n\r\n /* attach next item to list */\r\n if (head == NULL)\r\n {\r\n /* start the linked list */\r\n current_item = head = new_item;\r\n }\r\n else\r\n {\r\n /* add to the end and advance */\r\n current_item->next = new_item;\r\n new_item->prev = current_item;\r\n current_item = new_item;\r\n }\r\n\r\n /* parse next value */\r\n input_buffer->offset++;\r\n buffer_skip_whitespace(input_buffer);\r\n if (!parse_value(current_item, input_buffer))\r\n {\r\n goto fail; /* failed to parse value */\r\n }\r\n buffer_skip_whitespace(input_buffer);\r\n }\r\n while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == \',\'));\r\n\r\n if (cannot_access_at_index(input_buffer, 0) || buffer_at_offset(input_buffer)[0] != \']\')\r\n {\r\n goto fail; /* expected end of array */\r\n }\r\n\r\nsuccess:\r\n input_buffer->depth--;\r\n\r\n if (head != NULL) {\r\n head->prev = current_item;\r\n }\r\n\r\n item->type = cJSON_Array;\r\n item->child = head;\r\n\r\n input_buffer->offset++;\r\n\r\n return true;\r\n\r\nfail:\r\n if (head != NULL)\r\n {\r\n cJSON_Delete(head);\r\n }\r\n\r\n return false;\r\n}\r\n\r\n/* Render an array to text */\r\nstatic cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer)\r\n{\r\n unsigned char *output_pointer = NULL;\r\n size_t length = 0;\r\n cJSON *current_element = item->child;\r\n\r\n if (output_buffer == NULL)\r\n {\r\n return false;\r\n }\r\n\r\n /* Compose the output array. */\r\n /* opening square bracket */\r\n output_pointer = ensure(output_buffer, 1);\r\n if (output_pointer == NULL)\r\n {\r\n return false;\r\n }\r\n\r\n *output_pointer = \'[\';\r\n output_buffer->offset++;\r\n output_buffer->depth++;\r\n\r\n while (current_element != NULL)\r\n {\r\n if (!print_value(current_element, output_buffer))\r\n {\r\n return false;\r\n }\r\n update_offset(output_buffer);\r\n if (current_element->next)\r\n {\r\n length = (size_t) (output_buffer->format ? 2 : 1);\r\n output_pointer = ensure(output_buffer, length + 1);\r\n if (output_pointer == NULL)\r\n {\r\n return false;\r\n }\r\n *output_pointer++ = \',\';\r\n if(output_buffer->format)\r\n {\r\n *output_pointer++ = \' \';\r\n }\r\n *output_pointer = \'\\0\';\r\n output_buffer->offset += length;\r\n }\r\n current_element = current_element->next;\r\n }\r\n\r\n output_pointer = ensure(output_buffer, 2);\r\n if (output_pointer == NULL)\r\n {\r\n return false;\r\n }\r\n *output_pointer++ = \']\';\r\n *output_pointer = \'\\0\';\r\n output_buffer->depth--;\r\n\r\n return true;\r\n}\r\n\r\n/* Build an object from the text. */\r\nstatic cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer)\r\n{\r\n cJSON *head = NULL; /* linked list head */\r\n cJSON *current_item = NULL;\r\n\r\n if (input_buffer->depth >= CJSON_NESTING_LIMIT)\r\n {\r\n return false; /* to deeply nested */\r\n }\r\n input_buffer->depth++;\r\n\r\n if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != \'{\'))\r\n {\r\n goto fail; /* not an object */\r\n }\r\n\r\n input_buffer->offset++;\r\n buffer_skip_whitespace(input_buffer);\r\n if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == \'}\'))\r\n {\r\n goto success; /* empty object */\r\n }\r\n\r\n /* check if we skipped to the end of the buffer */\r\n if (cannot_access_at_index(input_buffer, 0))\r\n {\r\n input_buffer->offset--;\r\n goto fail;\r\n }\r\n\r\n /* step back to character in front of the first element */\r\n input_buffer->offset--;\r\n /* loop through the comma separated array elements */\r\n do\r\n {\r\n /* allocate next item */\r\n cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks));\r\n if (new_item == NULL)\r\n {\r\n goto fail; /* allocation failure */\r\n }\r\n\r\n /* attach next item to list */\r\n if (head == NULL)\r\n {\r\n /* start the linked list */\r\n current_item = head = new_item;\r\n }\r\n else\r\n {\r\n /* add to the end and advance */\r\n current_item->next = new_item;\r\n new_item->prev = current_item;\r\n current_item = new_item;\r\n }\r\n\r\n /* parse the name of the child */\r\n input_buffer->offset++;\r\n buffer_skip_whitespace(input_buffer);\r\n if (!parse_string(current_item, input_buffer))\r\n {\r\n goto fail; /* failed to parse name */\r\n }\r\n buffer_skip_whitespace(input_buffer);\r\n\r\n /* swap valuestring and string, because we parsed the name */\r\n current_item->string = current_item->valuestring;\r\n current_item->valuestring = NULL;\r\n\r\n if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != \':\'))\r\n {\r\n goto fail; /* invalid object */\r\n }\r\n\r\n /* parse the value */\r\n input_buffer->offset++;\r\n buffer_skip_whitespace(input_buffer);\r\n if (!parse_value(current_item, input_buffer))\r\n {\r\n goto fail; /* failed to parse value */\r\n }\r\n buffer_skip_whitespace(input_buffer);\r\n }\r\n while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == \',\'));\r\n\r\n if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != \'}\'))\r\n {\r\n goto fail; /* expected end of object */\r\n }\r\n\r\nsuccess:\r\n input_buffer->depth--;\r\n\r\n if (head != NULL) {\r\n head->prev = current_item;\r\n }\r\n\r\n item->type = cJSON_Object;\r\n item->child = head;\r\n\r\n input_buffer->offset++;\r\n return true;\r\n\r\nfail:\r\n if (head != NULL)\r\n {\r\n cJSON_Delete(head);\r\n }\r\n\r\n return false;\r\n}\r\n\r\n/* Render an object to text. */\r\nstatic cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer)\r\n{\r\n unsigned char *output_pointer = NULL;\r\n size_t length = 0;\r\n cJSON *current_item = item->child;\r\n\r\n if (output_buffer == NULL)\r\n {\r\n return false;\r\n }\r\n\r\n /* Compose the output: */\r\n length = (size_t) (output_buffer->format ? 2 : 1); /* fmt: {\\n */\r\n output_pointer = ensure(output_buffer, length + 1);\r\n if (output_pointer == NULL)\r\n {\r\n return false;\r\n }\r\n\r\n *output_pointer++ = \'{\';\r\n output_buffer->depth++;\r\n if (output_buffer->format)\r\n {\r\n *output_pointer++ = \'\\n\';\r\n }\r\n output_buffer->offset += length;\r\n\r\n while (current_item)\r\n {\r\n if (output_buffer->format)\r\n {\r\n size_t i;\r\n output_pointer = ensure(output_buffer, output_buffer->depth);\r\n if (output_pointer == NULL)\r\n {\r\n return false;\r\n }\r\n for (i = 0; i < output_buffer->depth; i++)\r\n {\r\n *output_pointer++ = \'\\t\';\r\n }\r\n output_buffer->offset += output_buffer->depth;\r\n }\r\n\r\n /* print key */\r\n if (!print_string_ptr((unsigned char*)current_item->string, output_buffer))\r\n {\r\n return false;\r\n }\r\n update_offset(output_buffer);\r\n\r\n length = (size_t) (output_buffer->format ? 2 : 1);\r\n output_pointer = ensure(output_buffer, length);\r\n if (output_pointer == NULL)\r\n {\r\n return false;\r\n }\r\n *output_pointer++ = \':\';\r\n if (output_buffer->format)\r\n {\r\n *output_pointer++ = \'\\t\';\r\n }\r\n output_buffer->offset += length;\r\n\r\n /* print value */\r\n if (!print_value(current_item, output_buffer))\r\n {\r\n return false;\r\n }\r\n update_offset(output_buffer);\r\n\r\n /* print comma if not last */\r\n length = ((size_t)(output_buffer->format ? 1 : 0) + (size_t)(current_item->next ? 1 : 0));\r\n output_pointer = ensure(output_buffer, length + 1);\r\n if (output_pointer == NULL)\r\n {\r\n return false;\r\n }\r\n if (current_item->next)\r\n {\r\n *output_pointer++ = \',\';\r\n }\r\n\r\n if (output_buffer->format)\r\n {\r\n *output_pointer++ = \'\\n\';\r\n }\r\n *output_pointer = \'\\0\';\r\n output_buffer->offset += length;\r\n\r\n current_item = current_item->next;\r\n }\r\n\r\n output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2);\r\n if (output_pointer == NULL)\r\n {\r\n return false;\r\n }\r\n if (output_buffer->format)\r\n {\r\n size_t i;\r\n for (i = 0; i < (output_buffer->depth - 1); i++)\r\n {\r\n *output_pointer++ = \'\\t\';\r\n }\r\n }\r\n *output_pointer++ = \'}\';\r\n *output_pointer = \'\\0\';\r\n output_buffer->depth--;\r\n\r\n return true;\r\n}\r\n\r\n/* Get Array size/item / object item. */\r\nCJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array)\r\n{\r\n cJSON *child = NULL;\r\n size_t size = 0;\r\n\r\n if (array == NULL)\r\n {\r\n return 0;\r\n }\r\n\r\n child = array->child;\r\n\r\n while(child != NULL)\r\n {\r\n size++;\r\n child = child->next;\r\n }\r\n\r\n /* FIXME: Can overflow here. Cannot be fixed without breaking the API */\r\n\r\n return (int)size;\r\n}\r\n\r\nstatic cJSON* get_array_item(const cJSON *array, size_t index)\r\n{\r\n cJSON *current_child = NULL;\r\n\r\n if (array == NULL)\r\n {\r\n return NULL;\r\n }\r\n\r\n current_child = array->child;\r\n while ((current_child != NULL) && (index > 0))\r\n {\r\n index--;\r\n current_child = current_child->next;\r\n }\r\n\r\n return current_child;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index)\r\n{\r\n if (index < 0)\r\n {\r\n return NULL;\r\n }\r\n\r\n return get_array_item(array, (size_t)index);\r\n}\r\n\r\nstatic cJSON *get_object_item(const cJSON * const object, const char * const name, const cJSON_bool case_sensitive)\r\n{\r\n cJSON *current_element = NULL;\r\n\r\n if ((object == NULL) || (name == NULL))\r\n {\r\n return NULL;\r\n }\r\n\r\n current_element = object->child;\r\n if (case_sensitive)\r\n {\r\n while ((current_element != NULL) && (current_element->string != NULL) && (strcmp(name, current_element->string) != 0))\r\n {\r\n current_element = current_element->next;\r\n }\r\n }\r\n else\r\n {\r\n while ((current_element != NULL) && (case_insensitive_strcmp((const unsigned char*)name, (const unsigned char*)(current_element->string)) != 0))\r\n {\r\n current_element = current_element->next;\r\n }\r\n }\r\n\r\n if ((current_element == NULL) || (current_element->string == NULL)) {\r\n return NULL;\r\n }\r\n\r\n return current_element;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string)\r\n{\r\n return get_object_item(object, string, false);\r\n}\r\n\r\nCJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string)\r\n{\r\n return get_object_item(object, string, true);\r\n}\r\n\r\nCJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string)\r\n{\r\n return cJSON_GetObjectItem(object, string) ? 1 : 0;\r\n}\r\n\r\n/* Utility for array list handling. */\r\nstatic void suffix_object(cJSON *prev, cJSON *item)\r\n{\r\n prev->next = item;\r\n item->prev = prev;\r\n}\r\n\r\n/* Utility for handling references. */\r\nstatic cJSON *create_reference(const cJSON *item, const internal_hooks * const hooks)\r\n{\r\n cJSON *reference = NULL;\r\n if (item == NULL)\r\n {\r\n return NULL;\r\n }\r\n\r\n reference = cJSON_New_Item(hooks);\r\n if (reference == NULL)\r\n {\r\n return NULL;\r\n }\r\n\r\n memcpy(reference, item, sizeof(cJSON));\r\n reference->string = NULL;\r\n reference->type |= cJSON_IsReference;\r\n reference->next = reference->prev = NULL;\r\n return reference;\r\n}\r\n\r\nstatic cJSON_bool add_item_to_array(cJSON *array, cJSON *item)\r\n{\r\n cJSON *child = NULL;\r\n\r\n if ((item == NULL) || (array == NULL) || (array == item))\r\n {\r\n return false;\r\n }\r\n\r\n child = array->child;\r\n /*\r\n * To find the last item in array quickly, we use prev in array\r\n */\r\n if (child == NULL)\r\n {\r\n /* list is empty, start new one */\r\n array->child = item;\r\n item->prev = item;\r\n item->next = NULL;\r\n }\r\n else\r\n {\r\n /* append to the end */\r\n if (child->prev)\r\n {\r\n suffix_object(child->prev, item);\r\n array->child->prev = item;\r\n }\r\n }\r\n\r\n return true;\r\n}\r\n\r\n/* Add item to array/object. */\r\nCJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item)\r\n{\r\n return add_item_to_array(array, item);\r\n}\r\n\r\n#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5))))\r\n #pragma GCC diagnostic push\r\n#endif\r\n#ifdef __GNUC__\r\n#pragma GCC diagnostic ignored \"-Wcast-qual\"\r\n#endif\r\n/* helper function to cast away const */\r\nstatic void* cast_away_const(const void* string)\r\n{\r\n return (void*)string;\r\n}\r\n#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5))))\r\n #pragma GCC diagnostic pop\r\n#endif\r\n\r\n\r\nstatic cJSON_bool add_item_to_object(cJSON * const object, const char * const string, cJSON * const item, const internal_hooks * const hooks, const cJSON_bool constant_key)\r\n{\r\n char *new_key = NULL;\r\n int new_type = cJSON_Invalid;\r\n\r\n if ((object == NULL) || (string == NULL) || (item == NULL) || (object == item))\r\n {\r\n return false;\r\n }\r\n\r\n if (constant_key)\r\n {\r\n new_key = (char*)cast_away_const(string);\r\n new_type = item->type | cJSON_StringIsConst;\r\n }\r\n else\r\n {\r\n new_key = (char*)cJSON_strdup((const unsigned char*)string, hooks);\r\n if (new_key == NULL)\r\n {\r\n return false;\r\n }\r\n\r\n new_type = item->type & ~cJSON_StringIsConst;\r\n }\r\n\r\n if (!(item->type & cJSON_StringIsConst) && (item->string != NULL))\r\n {\r\n hooks->deallocate(item->string);\r\n }\r\n\r\n item->string = new_key;\r\n item->type = new_type;\r\n\r\n return add_item_to_array(object, item);\r\n}\r\n\r\nCJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item)\r\n{\r\n return add_item_to_object(object, string, item, &global_hooks, false);\r\n}\r\n\r\n/* Add an item to an object with constant string as key */\r\nCJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item)\r\n{\r\n return add_item_to_object(object, string, item, &global_hooks, true);\r\n}\r\n\r\nCJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item)\r\n{\r\n if (array == NULL)\r\n {\r\n return false;\r\n }\r\n\r\n return add_item_to_array(array, create_reference(item, &global_hooks));\r\n}\r\n\r\nCJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item)\r\n{\r\n if ((object == NULL) || (string == NULL))\r\n {\r\n return false;\r\n }\r\n\r\n return add_item_to_object(object, string, create_reference(item, &global_hooks), &global_hooks, false);\r\n}\r\n\r\nCJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name)\r\n{\r\n cJSON *null = cJSON_CreateNull();\r\n if (add_item_to_object(object, name, null, &global_hooks, false))\r\n {\r\n return null;\r\n }\r\n\r\n cJSON_Delete(null);\r\n return NULL;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name)\r\n{\r\n cJSON *true_item = cJSON_CreateTrue();\r\n if (add_item_to_object(object, name, true_item, &global_hooks, false))\r\n {\r\n return true_item;\r\n }\r\n\r\n cJSON_Delete(true_item);\r\n return NULL;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name)\r\n{\r\n cJSON *false_item = cJSON_CreateFalse();\r\n if (add_item_to_object(object, name, false_item, &global_hooks, false))\r\n {\r\n return false_item;\r\n }\r\n\r\n cJSON_Delete(false_item);\r\n return NULL;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean)\r\n{\r\n cJSON *bool_item = cJSON_CreateBool(boolean);\r\n if (add_item_to_object(object, name, bool_item, &global_hooks, false))\r\n {\r\n return bool_item;\r\n }\r\n\r\n cJSON_Delete(bool_item);\r\n return NULL;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number)\r\n{\r\n cJSON *number_item = cJSON_CreateNumber(number);\r\n if (add_item_to_object(object, name, number_item, &global_hooks, false))\r\n {\r\n return number_item;\r\n }\r\n\r\n cJSON_Delete(number_item);\r\n return NULL;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string)\r\n{\r\n cJSON *string_item = cJSON_CreateString(string);\r\n if (add_item_to_object(object, name, string_item, &global_hooks, false))\r\n {\r\n return string_item;\r\n }\r\n\r\n cJSON_Delete(string_item);\r\n return NULL;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw)\r\n{\r\n cJSON *raw_item = cJSON_CreateRaw(raw);\r\n if (add_item_to_object(object, name, raw_item, &global_hooks, false))\r\n {\r\n return raw_item;\r\n }\r\n\r\n cJSON_Delete(raw_item);\r\n return NULL;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name)\r\n{\r\n cJSON *object_item = cJSON_CreateObject();\r\n if (add_item_to_object(object, name, object_item, &global_hooks, false))\r\n {\r\n return object_item;\r\n }\r\n\r\n cJSON_Delete(object_item);\r\n return NULL;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name)\r\n{\r\n cJSON *array = cJSON_CreateArray();\r\n if (add_item_to_object(object, name, array, &global_hooks, false))\r\n {\r\n return array;\r\n }\r\n\r\n cJSON_Delete(array);\r\n return NULL;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item)\r\n{\r\n if ((parent == NULL) || (item == NULL))\r\n {\r\n return NULL;\r\n }\r\n\r\n if (item != parent->child)\r\n {\r\n /* not the first element */\r\n item->prev->next = item->next;\r\n }\r\n if (item->next != NULL)\r\n {\r\n /* not the last element */\r\n item->next->prev = item->prev;\r\n }\r\n\r\n if (item == parent->child)\r\n {\r\n /* first element */\r\n parent->child = item->next;\r\n }\r\n else if (item->next == NULL)\r\n {\r\n /* last element */\r\n parent->child->prev = item->prev;\r\n }\r\n\r\n /* make sure the detached item doesn\'t point anywhere anymore */\r\n item->prev = NULL;\r\n item->next = NULL;\r\n\r\n return item;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which)\r\n{\r\n if (which < 0)\r\n {\r\n return NULL;\r\n }\r\n\r\n return cJSON_DetachItemViaPointer(array, get_array_item(array, (size_t)which));\r\n}\r\n\r\nCJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which)\r\n{\r\n cJSON_Delete(cJSON_DetachItemFromArray(array, which));\r\n}\r\n\r\nCJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string)\r\n{\r\n cJSON *to_detach = cJSON_GetObjectItem(object, string);\r\n\r\n return cJSON_DetachItemViaPointer(object, to_detach);\r\n}\r\n\r\nCJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string)\r\n{\r\n cJSON *to_detach = cJSON_GetObjectItemCaseSensitive(object, string);\r\n\r\n return cJSON_DetachItemViaPointer(object, to_detach);\r\n}\r\n\r\nCJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string)\r\n{\r\n cJSON_Delete(cJSON_DetachItemFromObject(object, string));\r\n}\r\n\r\nCJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string)\r\n{\r\n cJSON_Delete(cJSON_DetachItemFromObjectCaseSensitive(object, string));\r\n}\r\n\r\n/* Replace array/object items with new ones. */\r\nCJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem)\r\n{\r\n cJSON *after_inserted = NULL;\r\n\r\n if (which < 0)\r\n {\r\n return false;\r\n }\r\n\r\n after_inserted = get_array_item(array, (size_t)which);\r\n if (after_inserted == NULL)\r\n {\r\n return add_item_to_array(array, newitem);\r\n }\r\n\r\n newitem->next = after_inserted;\r\n newitem->prev = after_inserted->prev;\r\n after_inserted->prev = newitem;\r\n if (after_inserted == array->child)\r\n {\r\n array->child = newitem;\r\n }\r\n else\r\n {\r\n newitem->prev->next = newitem;\r\n }\r\n return true;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement)\r\n{\r\n if ((parent == NULL) || (replacement == NULL) || (item == NULL))\r\n {\r\n return false;\r\n }\r\n\r\n if (replacement == item)\r\n {\r\n return true;\r\n }\r\n\r\n replacement->next = item->next;\r\n replacement->prev = item->prev;\r\n\r\n if (replacement->next != NULL)\r\n {\r\n replacement->next->prev = replacement;\r\n }\r\n if (parent->child == item)\r\n {\r\n if (parent->child->prev == parent->child)\r\n {\r\n replacement->prev = replacement;\r\n }\r\n parent->child = replacement;\r\n }\r\n else\r\n { /*\r\n * To find the last item in array quickly, we use prev in array.\r\n * We can\'t modify the last item\'s next pointer where this item was the parent\'s child\r\n */\r\n if (replacement->prev != NULL)\r\n {\r\n replacement->prev->next = replacement;\r\n }\r\n if (replacement->next == NULL)\r\n {\r\n parent->child->prev = replacement;\r\n }\r\n }\r\n\r\n item->next = NULL;\r\n item->prev = NULL;\r\n cJSON_Delete(item);\r\n\r\n return true;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem)\r\n{\r\n if (which < 0)\r\n {\r\n return false;\r\n }\r\n\r\n return cJSON_ReplaceItemViaPointer(array, get_array_item(array, (size_t)which), newitem);\r\n}\r\n\r\nstatic cJSON_bool replace_item_in_object(cJSON *object, const char *string, cJSON *replacement, cJSON_bool case_sensitive)\r\n{\r\n if ((replacement == NULL) || (string == NULL))\r\n {\r\n return false;\r\n }\r\n\r\n /* replace the name in the replacement */\r\n if (!(replacement->type & cJSON_StringIsConst) && (replacement->string != NULL))\r\n {\r\n cJSON_free(replacement->string);\r\n }\r\n replacement->string = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks);\r\n if (replacement->string == NULL)\r\n {\r\n return false;\r\n }\r\n\r\n replacement->type &= ~cJSON_StringIsConst;\r\n\r\n return cJSON_ReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement);\r\n}\r\n\r\nCJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem)\r\n{\r\n return replace_item_in_object(object, string, newitem, false);\r\n}\r\n\r\nCJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem)\r\n{\r\n return replace_item_in_object(object, string, newitem, true);\r\n}\r\n\r\n/* Create basic types: */\r\nCJSON_PUBLIC(cJSON *) cJSON_CreateNull(void)\r\n{\r\n cJSON *item = cJSON_New_Item(&global_hooks);\r\n if(item)\r\n {\r\n item->type = cJSON_NULL;\r\n }\r\n\r\n return item;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void)\r\n{\r\n cJSON *item = cJSON_New_Item(&global_hooks);\r\n if(item)\r\n {\r\n item->type = cJSON_True;\r\n }\r\n\r\n return item;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void)\r\n{\r\n cJSON *item = cJSON_New_Item(&global_hooks);\r\n if(item)\r\n {\r\n item->type = cJSON_False;\r\n }\r\n\r\n return item;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean)\r\n{\r\n cJSON *item = cJSON_New_Item(&global_hooks);\r\n if(item)\r\n {\r\n item->type = boolean ? cJSON_True : cJSON_False;\r\n }\r\n\r\n return item;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num)\r\n{\r\n cJSON *item = cJSON_New_Item(&global_hooks);\r\n if(item)\r\n {\r\n item->type = cJSON_Number;\r\n item->valuedouble = num;\r\n\r\n /* use saturation in case of overflow */\r\n if (num >= INT_MAX)\r\n {\r\n item->valueint = INT_MAX;\r\n }\r\n else if (num <= (double)INT_MIN)\r\n {\r\n item->valueint = INT_MIN;\r\n }\r\n else\r\n {\r\n item->valueint = (int)num;\r\n }\r\n }\r\n\r\n return item;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string)\r\n{\r\n cJSON *item = cJSON_New_Item(&global_hooks);\r\n if(item)\r\n {\r\n item->type = cJSON_String;\r\n item->valuestring = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks);\r\n if(!item->valuestring)\r\n {\r\n cJSON_Delete(item);\r\n return NULL;\r\n }\r\n }\r\n\r\n return item;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string)\r\n{\r\n cJSON *item = cJSON_New_Item(&global_hooks);\r\n if (item != NULL)\r\n {\r\n item->type = cJSON_String | cJSON_IsReference;\r\n item->valuestring = (char*)cast_away_const(string);\r\n }\r\n\r\n return item;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child)\r\n{\r\n cJSON *item = cJSON_New_Item(&global_hooks);\r\n if (item != NULL) {\r\n item->type = cJSON_Object | cJSON_IsReference;\r\n item->child = (cJSON*)cast_away_const(child);\r\n }\r\n\r\n return item;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child) {\r\n cJSON *item = cJSON_New_Item(&global_hooks);\r\n if (item != NULL) {\r\n item->type = cJSON_Array | cJSON_IsReference;\r\n item->child = (cJSON*)cast_away_const(child);\r\n }\r\n\r\n return item;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw)\r\n{\r\n cJSON *item = cJSON_New_Item(&global_hooks);\r\n if(item)\r\n {\r\n item->type = cJSON_Raw;\r\n item->valuestring = (char*)cJSON_strdup((const unsigned char*)raw, &global_hooks);\r\n if(!item->valuestring)\r\n {\r\n cJSON_Delete(item);\r\n return NULL;\r\n }\r\n }\r\n\r\n return item;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON *) cJSON_CreateArray(void)\r\n{\r\n cJSON *item = cJSON_New_Item(&global_hooks);\r\n if(item)\r\n {\r\n item->type=cJSON_Array;\r\n }\r\n\r\n return item;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON *) cJSON_CreateObject(void)\r\n{\r\n cJSON *item = cJSON_New_Item(&global_hooks);\r\n if (item)\r\n {\r\n item->type = cJSON_Object;\r\n }\r\n\r\n return item;\r\n}\r\n\r\n/* Create Arrays: */\r\nCJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count)\r\n{\r\n size_t i = 0;\r\n cJSON *n = NULL;\r\n cJSON *p = NULL;\r\n cJSON *a = NULL;\r\n\r\n if ((count < 0) || (numbers == NULL))\r\n {\r\n return NULL;\r\n }\r\n\r\n a = cJSON_CreateArray();\r\n\r\n for(i = 0; a && (i < (size_t)count); i++)\r\n {\r\n n = cJSON_CreateNumber(numbers[i]);\r\n if (!n)\r\n {\r\n cJSON_Delete(a);\r\n return NULL;\r\n }\r\n if(!i)\r\n {\r\n a->child = n;\r\n }\r\n else\r\n {\r\n suffix_object(p, n);\r\n }\r\n p = n;\r\n }\r\n\r\n if (a && a->child) {\r\n a->child->prev = n;\r\n }\r\n\r\n return a;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count)\r\n{\r\n size_t i = 0;\r\n cJSON *n = NULL;\r\n cJSON *p = NULL;\r\n cJSON *a = NULL;\r\n\r\n if ((count < 0) || (numbers == NULL))\r\n {\r\n return NULL;\r\n }\r\n\r\n a = cJSON_CreateArray();\r\n\r\n for(i = 0; a && (i < (size_t)count); i++)\r\n {\r\n n = cJSON_CreateNumber((double)numbers[i]);\r\n if(!n)\r\n {\r\n cJSON_Delete(a);\r\n return NULL;\r\n }\r\n if(!i)\r\n {\r\n a->child = n;\r\n }\r\n else\r\n {\r\n suffix_object(p, n);\r\n }\r\n p = n;\r\n }\r\n\r\n if (a && a->child) {\r\n a->child->prev = n;\r\n }\r\n\r\n return a;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count)\r\n{\r\n size_t i = 0;\r\n cJSON *n = NULL;\r\n cJSON *p = NULL;\r\n cJSON *a = NULL;\r\n\r\n if ((count < 0) || (numbers == NULL))\r\n {\r\n return NULL;\r\n }\r\n\r\n a = cJSON_CreateArray();\r\n\r\n for(i = 0; a && (i < (size_t)count); i++)\r\n {\r\n n = cJSON_CreateNumber(numbers[i]);\r\n if(!n)\r\n {\r\n cJSON_Delete(a);\r\n return NULL;\r\n }\r\n if(!i)\r\n {\r\n a->child = n;\r\n }\r\n else\r\n {\r\n suffix_object(p, n);\r\n }\r\n p = n;\r\n }\r\n\r\n if (a && a->child) {\r\n a->child->prev = n;\r\n }\r\n\r\n return a;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count)\r\n{\r\n size_t i = 0;\r\n cJSON *n = NULL;\r\n cJSON *p = NULL;\r\n cJSON *a = NULL;\r\n\r\n if ((count < 0) || (strings == NULL))\r\n {\r\n return NULL;\r\n }\r\n\r\n a = cJSON_CreateArray();\r\n\r\n for (i = 0; a && (i < (size_t)count); i++)\r\n {\r\n n = cJSON_CreateString(strings[i]);\r\n if(!n)\r\n {\r\n cJSON_Delete(a);\r\n return NULL;\r\n }\r\n if(!i)\r\n {\r\n a->child = n;\r\n }\r\n else\r\n {\r\n suffix_object(p,n);\r\n }\r\n p = n;\r\n }\r\n\r\n if (a && a->child) {\r\n a->child->prev = n;\r\n }\r\n\r\n return a;\r\n}\r\n\r\n/* Duplication */\r\nCJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse)\r\n{\r\n cJSON *newitem = NULL;\r\n cJSON *child = NULL;\r\n cJSON *next = NULL;\r\n cJSON *newchild = NULL;\r\n\r\n /* Bail on bad ptr */\r\n if (!item)\r\n {\r\n goto fail;\r\n }\r\n /* Create new item */\r\n newitem = cJSON_New_Item(&global_hooks);\r\n if (!newitem)\r\n {\r\n goto fail;\r\n }\r\n /* Copy over all vars */\r\n newitem->type = item->type & (~cJSON_IsReference);\r\n newitem->valueint = item->valueint;\r\n newitem->valuedouble = item->valuedouble;\r\n if (item->valuestring)\r\n {\r\n newitem->valuestring = (char*)cJSON_strdup((unsigned char*)item->valuestring, &global_hooks);\r\n if (!newitem->valuestring)\r\n {\r\n goto fail;\r\n }\r\n }\r\n if (item->string)\r\n {\r\n newitem->string = (item->type&cJSON_StringIsConst) ? item->string : (char*)cJSON_strdup((unsigned char*)item->string, &global_hooks);\r\n if (!newitem->string)\r\n {\r\n goto fail;\r\n }\r\n }\r\n /* If non-recursive, then we\'re done! */\r\n if (!recurse)\r\n {\r\n return newitem;\r\n }\r\n /* Walk the ->next chain for the child. */\r\n child = item->child;\r\n while (child != NULL)\r\n {\r\n newchild = cJSON_Duplicate(child, true); /* Duplicate (with recurse) each item in the ->next chain */\r\n if (!newchild)\r\n {\r\n goto fail;\r\n }\r\n if (next != NULL)\r\n {\r\n /* If newitem->child already set, then crosswire ->prev and ->next and move on */\r\n next->next = newchild;\r\n newchild->prev = next;\r\n next = newchild;\r\n }\r\n else\r\n {\r\n /* Set newitem->child and move to it */\r\n newitem->child = newchild;\r\n next = newchild;\r\n }\r\n child = child->next;\r\n }\r\n if (newitem && newitem->child)\r\n {\r\n newitem->child->prev = newchild;\r\n }\r\n\r\n return newitem;\r\n\r\nfail:\r\n if (newitem != NULL)\r\n {\r\n cJSON_Delete(newitem);\r\n }\r\n\r\n return NULL;\r\n}\r\n\r\nstatic void skip_oneline_comment(char **input)\r\n{\r\n *input += static_strlen(\"//\");\r\n\r\n for (; (*input)[0] != \'\\0\'; ++(*input))\r\n {\r\n if ((*input)[0] == \'\\n\') {\r\n *input += static_strlen(\"\\n\");\r\n return;\r\n }\r\n }\r\n}\r\n\r\nstatic void skip_multiline_comment(char **input)\r\n{\r\n *input += static_strlen(\"/*\");\r\n\r\n for (; (*input)[0] != \'\\0\'; ++(*input))\r\n {\r\n if (((*input)[0] == \'*\') && ((*input)[1] == \'/\'))\r\n {\r\n *input += static_strlen(\"*/\");\r\n return;\r\n }\r\n }\r\n}\r\n\r\nstatic void minify_string(char **input, char **output) {\r\n (*output)[0] = (*input)[0];\r\n *input += static_strlen(\"\\\"\");\r\n *output += static_strlen(\"\\\"\");\r\n\r\n\r\n for (; (*input)[0] != \'\\0\'; (void)++(*input), ++(*output)) {\r\n (*output)[0] = (*input)[0];\r\n\r\n if ((*input)[0] == \'\\\"\') {\r\n (*output)[0] = \'\\\"\';\r\n *input += static_strlen(\"\\\"\");\r\n *output += static_strlen(\"\\\"\");\r\n return;\r\n } else if (((*input)[0] == \'\\\\\') && ((*input)[1] == \'\\\"\')) {\r\n (*output)[1] = (*input)[1];\r\n *input += static_strlen(\"\\\"\");\r\n *output += static_strlen(\"\\\"\");\r\n }\r\n }\r\n}\r\n\r\nCJSON_PUBLIC(void) cJSON_Minify(char *json)\r\n{\r\n char *into = json;\r\n\r\n if (json == NULL)\r\n {\r\n return;\r\n }\r\n\r\n while (json[0] != \'\\0\')\r\n {\r\n switch (json[0])\r\n {\r\n case \' \':\r\n case \'\\t\':\r\n case \'\\r\':\r\n case \'\\n\':\r\n json++;\r\n break;\r\n\r\n case \'/\':\r\n if (json[1] == \'/\')\r\n {\r\n skip_oneline_comment(&json);\r\n }\r\n else if (json[1] == \'*\')\r\n {\r\n skip_multiline_comment(&json);\r\n } else {\r\n json++;\r\n }\r\n break;\r\n\r\n case \'\\\"\':\r\n minify_string(&json, (char**)&into);\r\n break;\r\n\r\n default:\r\n into[0] = json[0];\r\n json++;\r\n into++;\r\n }\r\n }\r\n\r\n /* and null-terminate. */\r\n *into = \'\\0\';\r\n}\r\n\r\nCJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item)\r\n{\r\n if (item == NULL)\r\n {\r\n return false;\r\n }\r\n\r\n return (item->type & 0xFF) == cJSON_Invalid;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item)\r\n{\r\n if (item == NULL)\r\n {\r\n return false;\r\n }\r\n\r\n return (item->type & 0xFF) == cJSON_False;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item)\r\n{\r\n if (item == NULL)\r\n {\r\n return false;\r\n }\r\n\r\n return (item->type & 0xff) == cJSON_True;\r\n}\r\n\r\n\r\nCJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item)\r\n{\r\n if (item == NULL)\r\n {\r\n return false;\r\n }\r\n\r\n return (item->type & (cJSON_True | cJSON_False)) != 0;\r\n}\r\nCJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item)\r\n{\r\n if (item == NULL)\r\n {\r\n return false;\r\n }\r\n\r\n return (item->type & 0xFF) == cJSON_NULL;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item)\r\n{\r\n if (item == NULL)\r\n {\r\n return false;\r\n }\r\n\r\n return (item->type & 0xFF) == cJSON_Number;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item)\r\n{\r\n if (item == NULL)\r\n {\r\n return false;\r\n }\r\n\r\n return (item->type & 0xFF) == cJSON_String;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item)\r\n{\r\n if (item == NULL)\r\n {\r\n return false;\r\n }\r\n\r\n return (item->type & 0xFF) == cJSON_Array;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item)\r\n{\r\n if (item == NULL)\r\n {\r\n return false;\r\n }\r\n\r\n return (item->type & 0xFF) == cJSON_Object;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item)\r\n{\r\n if (item == NULL)\r\n {\r\n return false;\r\n }\r\n\r\n return (item->type & 0xFF) == cJSON_Raw;\r\n}\r\n\r\nCJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive)\r\n{\r\n if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF)))\r\n {\r\n return false;\r\n }\r\n\r\n /* check if type is valid */\r\n switch (a->type & 0xFF)\r\n {\r\n case cJSON_False:\r\n case cJSON_True:\r\n case cJSON_NULL:\r\n case cJSON_Number:\r\n case cJSON_String:\r\n case cJSON_Raw:\r\n case cJSON_Array:\r\n case cJSON_Object:\r\n break;\r\n\r\n default:\r\n return false;\r\n }\r\n\r\n /* identical objects are equal */\r\n if (a == b)\r\n {\r\n return true;\r\n }\r\n\r\n switch (a->type & 0xFF)\r\n {\r\n /* in these cases and equal type is enough */\r\n case cJSON_False:\r\n case cJSON_True:\r\n case cJSON_NULL:\r\n return true;\r\n\r\n case cJSON_Number:\r\n if (compare_double(a->valuedouble, b->valuedouble))\r\n {\r\n return true;\r\n }\r\n return false;\r\n\r\n case cJSON_String:\r\n case cJSON_Raw:\r\n if ((a->valuestring == NULL) || (b->valuestring == NULL))\r\n {\r\n return false;\r\n }\r\n if (strcmp(a->valuestring, b->valuestring) == 0)\r\n {\r\n return true;\r\n }\r\n\r\n return false;\r\n\r\n case cJSON_Array:\r\n {\r\n cJSON *a_element = a->child;\r\n cJSON *b_element = b->child;\r\n\r\n for (; (a_element != NULL) && (b_element != NULL);)\r\n {\r\n if (!cJSON_Compare(a_element, b_element, case_sensitive))\r\n {\r\n return false;\r\n }\r\n\r\n a_element = a_element->next;\r\n b_element = b_element->next;\r\n }\r\n\r\n /* one of the arrays is longer than the other */\r\n if (a_element != b_element) {\r\n return false;\r\n }\r\n\r\n return true;\r\n }\r\n\r\n case cJSON_Object:\r\n {\r\n cJSON *a_element = NULL;\r\n cJSON *b_element = NULL;\r\n cJSON_ArrayForEach(a_element, a)\r\n {\r\n /* TODO This has O(n^2) runtime, which is horrible! */\r\n b_element = get_object_item(b, a_element->string, case_sensitive);\r\n if (b_element == NULL)\r\n {\r\n return false;\r\n }\r\n\r\n if (!cJSON_Compare(a_element, b_element, case_sensitive))\r\n {\r\n return false;\r\n }\r\n }\r\n\r\n /* doing this twice, once on a and b to prevent true comparison if a subset of b\r\n * TODO: Do this the proper way, this is just a fix for now */\r\n cJSON_ArrayForEach(b_element, b)\r\n {\r\n a_element = get_object_item(a, b_element->string, case_sensitive);\r\n if (a_element == NULL)\r\n {\r\n return false;\r\n }\r\n\r\n if (!cJSON_Compare(b_element, a_element, case_sensitive))\r\n {\r\n return false;\r\n }\r\n }\r\n\r\n return true;\r\n }\r\n\r\n default:\r\n return false;\r\n }\r\n}\r\n\r\nCJSON_PUBLIC(void *) cJSON_malloc(size_t size)\r\n{\r\n return global_hooks.allocate(size);\r\n}\r\n\r\nCJSON_PUBLIC(void) cJSON_free(void *object)\r\n{\r\n global_hooks.deallocate(object);\r\n}', 1); +INSERT INTO `onefile` VALUES (5, 'filedb', 'C', '基于B-tree 的文件数据库', 12, 'https://github.com/LiuYuguang/supersimplefiledatabase', 202, '/*\r\n * Copyright (C) 2022, LiuYuguang \r\n */\r\n/**\r\n * @brief 超简单的文件数据库\r\n */\r\n\r\n#include // for size_t\r\n#include // for malloc(), free()\r\n#include // for memcpy(), memmove(), strcpy(), strlen()\r\n#include // for open(), O_CREAT, O_RDWR, O_TRUNC\r\n#include // for struct stat, fstat()\r\n#include // for pread(), pwrite(), access(), ftruncate(), close()\r\n#include // for int32_t\r\n#include // for E2BIG\r\n\r\n#define DB_HEAD_SIZE (4096UL) // head size must be pow of 2! 文件数据库的头大小\r\n#define DB_BLOCK_SIZE (8192UL) // block size must be pow of 2! 文件数据库的数据块大小\r\n\r\n#define BTREE_NON_LEAF 0 /** 非叶子节点 */\r\n#define BTREE_LEAF 1 /** 叶子节点 */\r\n\r\n/**\r\n * @brief 存储数据对齐方式\r\n */\r\n#define DB_ALIGNMENT 16\r\n#define db_align(d, a) (((d) + (a - 1)) & ~(a - 1))\r\n\r\n#define ceil(M) (((M)-1)/2)\r\n\r\n/**\r\n * @brief 数据块类型\r\n */\r\n#define TYPE_KEY 0\r\n#define TYPE_VALUE 1\r\n\r\n/**\r\n * @brief 创建数据库时,指定的key类型\r\n */\r\n#define DB_STRINGKEY 0 /** 4 <= max_key_size <= 128, include \'\\0\', 包含\'\\0\'在内 */\r\n#define DB_BYTESKEY 1 /** 4 <= max_key_size <= 128 */\r\n#define DB_INT32KEY 2 /** max_key_size = sizeof(int32_t) */\r\n#define DB_INT64KEY 3 /** max_key_size = sizeof(int64_t) */\r\n\r\ntypedef struct{\r\n off_t value;\r\n off_t child;\r\n unsigned char key[0];\r\n}btree_key;\r\n\r\ntypedef struct{\r\n size_t size;\r\n unsigned char value[0];\r\n}btree_value;\r\n\r\n/**\r\n * @brief 文件数据库的数据块的头\r\n */\r\ntypedef struct{\r\n off_t self; /** 数据块位置 */\r\n size_t num; /** 当前数据块作为btree_key时,表示btree节点的关键字数;当前数据块作为btree_value时,表示块的引用数 */\r\n off_t free; /** 空闲链表节点 */\r\n uint32_t use:1; /** 当前数据块是否被使用 */\r\n uint32_t type:1; /** 当前数据块作为btree_key或btree_value */\r\n uint32_t leaf:1; /** 当前数据块作为btree_key时,表示节点为叶子节点或非叶子节点 */\r\n uint32_t last:29; /** 当前数据块作为btree_value时,表示数据块未分配的空间 */\r\n}btree_node;\r\n\r\n#define btree_key_ptr(db,node,n) ((btree_key*)((char *)(node) + sizeof(*node) + (db->key_align) * (n)))\r\n#define btree_value_ptr(node,n) ((btree_value*)((char *)(node) + (n)))\r\n\r\n/**\r\n * @brief 文件数据库的头,即是句柄\r\n */\r\ntypedef struct db_s{\r\n int fd; /** 文件句柄 */\r\n int key_type; /** key类型,必须在创建文件数据库时指定 */\r\n size_t key_size; /** key的最大长度 */\r\n size_t key_align; /** 对齐,值 = db_align(sizeof(btree_key) + key_size, DB_ALIGNMENT) */\r\n size_t M; /** Btree 节点child的最大值 */\r\n size_t key_total; /** 已存储的key总数 */\r\n size_t key_use_block; /** 数据块为btree_key类型的总数 */\r\n size_t value_use_block; /** 数据块为btree_value类型的总数 */ \r\n off_t free; /** 空闲链表的头 */\r\n off_t current; /** 当前作为btree_value的数据块,未用完分配空间 */\r\n int (*key_cmp)(void*,void*,size_t); /** key比较方式 */\r\n}db_t;\r\n\r\nstatic int cmp_string(void *a, void *b, size_t n){\r\n return strncmp((char*)a, (char*)b, n);\r\n}\r\n\r\nstatic int cmp_bytes(void *a, void *b, size_t n){\r\n return memcmp(a, b, n);\r\n}\r\n\r\nstatic int cmp_int32(void *a, void *b, size_t n){\r\n return *(int32_t*)a - *(int32_t*)b;\r\n}\r\n\r\nstatic int cmp_int64(void *a, void *b, size_t n){\r\n return *(int64_t*)a - *(int64_t*)b;\r\n}\r\n\r\n/** \r\n * @brief 读出文件数据库的头,即是数据库句柄\r\n*/\r\ninline static ssize_t head_seek(db_t *db){\r\n int fd = db->fd;\r\n int (*key_cmp)(void*,void*,size_t) = db->key_cmp;\r\n ssize_t rc = pread(fd,db,DB_HEAD_SIZE,0);\r\n db->fd = fd;\r\n db->key_cmp = key_cmp;\r\n return rc;\r\n}\r\n\r\n/** \r\n * @brief 写入文件数据库的头,即是数据库句柄\r\n*/\r\ninline static ssize_t head_flush(db_t *db){\r\n return pwrite(db->fd,db,DB_HEAD_SIZE,0);\r\n}\r\n\r\n/** \r\n * @brief 读出文件数据库的数据块\r\n*/\r\ninline static ssize_t node_seek(db_t *db, btree_node* node, off_t offset){\r\n return pread(db->fd,node,DB_BLOCK_SIZE,offset);\r\n}\r\n\r\n/** \r\n * @brief 写入文件数据库的数据块\r\n*/\r\ninline static ssize_t node_flush(db_t *db, btree_node *node){\r\n return pwrite(db->fd,node,DB_BLOCK_SIZE,node->self);\r\n}\r\n\r\n/** \r\n * @brief 分配文件数据库的数据块\r\n*/\r\ninline static int node_create(db_t *db, btree_node* node, int leaf, int type){\r\n if(db->free != 0L){\r\n // 空闲链表不为空\r\n node_seek(db,node,db->free);\r\n db->free = node->free;\r\n }else{\r\n // 空闲链表为空,在文件尾追加\r\n struct stat stat;\r\n fstat(db->fd,&stat);\r\n memset(node,0,DB_BLOCK_SIZE);\r\n node->self = stat.st_size;\r\n if(node_flush(db,node) != DB_BLOCK_SIZE){\r\n // 存储空间不够时,只会写入部分数据\r\n ftruncate(db->fd, stat.st_size);\r\n errno = ENOMEM;\r\n return -1;\r\n }\r\n }\r\n if(type == TYPE_KEY){\r\n db->key_use_block++;\r\n }else{\r\n db->value_use_block++;\r\n db->current = node->self;\r\n }\r\n head_flush(db);\r\n\r\n node->num = 0;\r\n node->free = 0;\r\n node->leaf = leaf;\r\n node->use = 1;\r\n node->type = type;\r\n node->last = sizeof(btree_node);\r\n memset((char*)node + sizeof(btree_node), 0, DB_BLOCK_SIZE-sizeof(btree_node));\r\n return node_flush(db,node);\r\n}\r\n\r\n/** \r\n * @brief 释放文件数据库的数据块\r\n*/\r\ninline static void node_destroy(db_t *db, btree_node *node){\r\n node->free = db->free;\r\n db->free = node->self;// 加入空闲链表中\r\n node->num = 0;\r\n node->use = 0;\r\n if(node->type == TYPE_KEY){\r\n db->key_use_block--;\r\n }else{\r\n db->value_use_block--;\r\n }\r\n head_flush(db);\r\n node_flush(db,node);\r\n return;\r\n}\r\n\r\n/**\r\n * @brief create dateabase file, mode default 0664 创建数据库\r\n * @param[in] path 数据库文件路径\r\n * @param[in] key_type DB_STRINGKEY, DB_BYTESKEY, DB_INT32KEY, DB_INT64KEY\r\n * @param[in] max_key_size key长度最大值\r\n * @return ==0 if successful, ==-1 error\r\n*/\r\nint db_create(char *path, int key_type, size_t max_key_size){\r\n switch (key_type)\r\n {\r\n case DB_STRINGKEY:\r\n case DB_BYTESKEY:\r\n if(max_key_size < 4UL || max_key_size > 128UL){\r\n errno = EINVAL;\r\n return -1;\r\n }\r\n break;\r\n case DB_INT32KEY:\r\n if(max_key_size != sizeof(int32_t)){\r\n errno = EINVAL;\r\n return -1;\r\n }\r\n break;\r\n case DB_INT64KEY:\r\n if(max_key_size != sizeof(int64_t)){\r\n errno = EINVAL;\r\n return -1;\r\n }\r\n break;\r\n default:\r\n errno = EINVAL;\r\n return -1;\r\n break;\r\n }\r\n\r\n size_t key_align = db_align(sizeof(btree_key) + max_key_size,DB_ALIGNMENT);\r\n\r\n if(DB_BLOCK_SIZE < sizeof(btree_node) + key_align){\r\n errno = EINVAL;\r\n return -1;\r\n }\r\n\r\n // 需要预留一个位置,比如M=5(关键字个数是4),分裂后变成左2右1,右插入变成左2右2,合并后变成M=6(关键字个数变成了5)\r\n size_t M = (DB_BLOCK_SIZE - sizeof(btree_node))/key_align - 1;\r\n if(M < 3){\r\n errno = EINVAL;\r\n return -1;\r\n }\r\n\r\n if(access(path,F_OK) == 0){\r\n errno = EEXIST;\r\n return -1;\r\n }\r\n\r\n int fd = open(path,O_CREAT|O_RDWR|O_TRUNC,0664);\r\n if(fd == -1){\r\n return -1;\r\n }\r\n\r\n char *buf = calloc(DB_HEAD_SIZE + DB_BLOCK_SIZE, sizeof(char));\r\n if(buf == NULL){\r\n close(fd);\r\n return -1;\r\n }\r\n\r\n db_t *db = (db_t *)buf;\r\n db->fd = fd;\r\n db->key_type = key_type;\r\n db->key_size = max_key_size;\r\n db->key_align = key_align;\r\n db->M = M;\r\n db->key_total = 0;\r\n db->key_use_block = 1;// Btree根的数据块,绝对不会被释放\r\n db->value_use_block = 0;\r\n db->free = 0;\r\n db->current = 0;\r\n\r\n if(head_flush(db) != DB_HEAD_SIZE){\r\n close(fd);\r\n free(buf);\r\n return -1;\r\n }\r\n\r\n btree_node *root = (btree_node *)(buf + DB_HEAD_SIZE);\r\n root->self = DB_HEAD_SIZE;\r\n root->leaf = BTREE_LEAF;\r\n root->use = 1;// Btree根的数据块,绝对不会被释放\r\n if(node_flush(db, root) != DB_BLOCK_SIZE){\r\n close(fd);\r\n free(buf);\r\n return -1;\r\n }\r\n\r\n close(fd);\r\n free(buf);\r\n return 0;\r\n}\r\n\r\n/**\r\n * @brief 校验数据库的一致性\r\n * @param[in] db 数据库句柄\r\n * @return ==0 if successful, ==-1 error\r\n*/\r\nint db_checker(db_t *db){\r\n struct stat stat;\r\n if(fstat(db->fd, &stat) == -1){\r\n return -1;\r\n }\r\n\r\n if(stat.st_size < DB_HEAD_SIZE + DB_BLOCK_SIZE || (stat.st_size-DB_HEAD_SIZE)%DB_BLOCK_SIZE != 0){\r\n return -1;\r\n }\r\n \r\n switch (db->key_type)\r\n {\r\n case DB_STRINGKEY:\r\n case DB_BYTESKEY:\r\n if(db->key_size < 4UL || db->key_size > 128UL){\r\n return -1;\r\n }\r\n break;\r\n case DB_INT32KEY:\r\n if(db->key_size != sizeof(int32_t)){\r\n return -1;\r\n }\r\n break;\r\n case DB_INT64KEY:\r\n if(db->key_size != sizeof(int64_t)){\r\n return -1;\r\n }\r\n break;\r\n default:\r\n return -1;\r\n break;\r\n }\r\n\r\n if(db->key_align != db_align(sizeof(btree_key) + db->key_size,DB_ALIGNMENT)){\r\n return -1;\r\n }\r\n\r\n if(db->M != (DB_BLOCK_SIZE - sizeof(btree_node))/db->key_align - 1){\r\n return -1;\r\n }\r\n\r\n // 校验每个数据块的数目是否一致\r\n btree_node *node = (btree_node *)((char*)db + DB_HEAD_SIZE + DB_BLOCK_SIZE * 0);\r\n off_t i;\r\n size_t key_total=0,value_total=0,key_use_block=0,value_use_block=0;\r\n for(i=DB_HEAD_SIZE;iself != i){\r\n return -1;\r\n }\r\n if(node->use){\r\n if(node->type == TYPE_KEY){\r\n key_total += node->num;\r\n key_use_block++;\r\n }else{\r\n value_total += node->num;\r\n value_use_block++;\r\n }\r\n }\r\n }\r\n if(key_total != value_total \r\n || key_total != db->key_total\r\n || key_use_block != db->key_use_block\r\n || value_use_block != db->value_use_block\r\n ){\r\n return -1;\r\n }\r\n return 0;\r\n}\r\n\r\n/**\r\n * @brief open dateabase file 打开数据库\r\n * @param[out] db 数据库句柄\r\n * @param[in] path 数据库文件路径\r\n * @return ==0 if success, ==-1 error\r\n*/\r\nint db_open(db_t **db, char *path){\r\n int fd = open(path, O_RDWR);\r\n if(fd == -1){\r\n return -1;\r\n }\r\n\r\n *db = malloc(DB_HEAD_SIZE + DB_BLOCK_SIZE * 5);\r\n if(*db == NULL){\r\n close(fd);\r\n return -1;\r\n }\r\n \r\n (*db)->fd = fd;\r\n\r\n head_seek(*db);\r\n // 校验数据库\r\n if(db_checker(*db) == -1){\r\n close(fd);\r\n free(*db);\r\n return -1;\r\n }\r\n\r\n switch ((*db)->key_type)\r\n {\r\n case DB_STRINGKEY:\r\n (*db)->key_cmp = cmp_string;\r\n break;\r\n case DB_BYTESKEY:\r\n (*db)->key_cmp = cmp_bytes;\r\n break;\r\n case DB_INT32KEY:\r\n (*db)->key_cmp = cmp_int32;\r\n break;\r\n case DB_INT64KEY:\r\n (*db)->key_cmp = cmp_int64;\r\n break;\r\n default:\r\n (*db)->key_cmp = NULL;\r\n break;\r\n }\r\n\r\n return 0;\r\n}\r\n\r\n/**\r\n * @brief close dateabase file 关闭数据库\r\n * @param[in] db 数据库句柄\r\n*/\r\nvoid db_close(db_t *db){\r\n close(db->fd);\r\n free(db);\r\n}\r\n\r\ninline static int key_binary_search(db_t *db, btree_node *node, void* target)\r\n{\r\n int low = 0, high = node->num - 1, mid, rc;\r\n while (low <= high) {\r\n mid = low + (high - low) / 2;\r\n rc = db->key_cmp(target, btree_key_ptr(db,node,mid)->key, db->key_size);\r\n if(rc == 0){\r\n return mid;\r\n }else if(rc > 0){\r\n low = mid + 1;\r\n }else{\r\n high = mid - 1;\r\n }\r\n }\r\n return -low-1;\r\n}\r\n\r\n#define keycpy(db,dest,src,n) memmove(dest,src,(db)->key_align * ((n)+1));// 需要包括 src[n]->child\r\n\r\n/**\r\n * @brief 分裂Btree节点\r\n * 将sub_x分裂,一半给sub_y,中间上升到node[position]\r\n * @param db \r\n * @param node \r\n * @param position \r\n * @param sub_x node->child[position] = sub_x\r\n * @param sub_y node->child[position+1] = sub_y\r\n */\r\ninline static void btree_split_child(db_t* db, btree_node *node, int position, btree_node *sub_x, btree_node *sub_y){\r\n size_t n = ceil(db->M);\r\n\r\n keycpy(db, btree_key_ptr(db, sub_y, 0), btree_key_ptr(db, sub_x, n+1), sub_x->num-n-1);\r\n sub_y->num = sub_x->num - n - 1;\r\n sub_x->num = n;\r\n\r\n keycpy(db, btree_key_ptr(db, node, position+1), btree_key_ptr(db, node, position), node->num - position);\r\n memcpy(btree_key_ptr(db, node, position), btree_key_ptr(db, sub_x, n), db->key_align);\r\n btree_key_ptr(db, node, position)->child = sub_x->self;\r\n btree_key_ptr(db, node, position+1)->child = sub_y->self;\r\n node->num++;\r\n\r\n node_flush(db, node);\r\n node_flush(db, sub_x);\r\n node_flush(db, sub_y);\r\n}\r\n\r\n/**\r\n * @brief 合并Btree节点\r\n * 将sub_x和sub_y和node[position]合并\r\n * @param db \r\n * @param node \r\n * @param position \r\n * @param sub_x node->child[position] = sub_x\r\n * @param sub_y node->child[position+1] = sub_y\r\n */\r\ninline static int btree_merge(db_t* db, btree_node *node, int position, btree_node *sub_x, btree_node *sub_y){\r\n memcpy(btree_key_ptr(db, sub_x, sub_x->num)->key, btree_key_ptr(db, node, position)->key, db->key_align - sizeof(btree_key));\r\n btree_key_ptr(db, sub_x, sub_x->num)->value = btree_key_ptr(db, node, position)->value;\r\n\r\n keycpy(db, btree_key_ptr(db, sub_x, sub_x->num+1), btree_key_ptr(db, sub_y, 0), sub_y->num); \r\n sub_x->num += ( 1 + sub_y->num );\r\n\r\n keycpy(db, btree_key_ptr(db, node, position), btree_key_ptr(db, node, position+1), node->num - position - 1);\r\n btree_key_ptr(db, node, position)->child = sub_x->self;\r\n node->num--;\r\n\r\n node_destroy(db, sub_y);\r\n\r\n if(node->num != 0){\r\n node_flush(db, sub_x);\r\n node_flush(db, node);\r\n return 0;\r\n }else{\r\n // must be root\r\n node->num = sub_x->num;\r\n node->leaf = sub_x->leaf;\r\n memcpy((char*)node + sizeof(btree_node),(char*)sub_x + sizeof(btree_node),DB_BLOCK_SIZE - sizeof(btree_node));\r\n node_destroy(db, sub_x);\r\n node_flush(db, node);\r\n return 1;\r\n }\r\n}\r\n\r\n/**\r\n * @brief insert key 插入值\r\n * @param[in] db 数据库句柄\r\n * @param[in] key key_size can\'t exceed max_key_size 需要保证key类型和创建数据库时一致\r\n * @param[in] value\r\n * @param[in] value_size value_size can\'t too large 不能太大\r\n * @return ==1 if success, ==0 if key repeat, ==-1 error\r\n*/\r\nint db_insert(db_t* db, void* key, void *value, size_t value_size){\r\n if(db->key_type == DB_STRINGKEY && strlen((char*)key) >= db->key_size){\r\n errno = EINVAL;\r\n return -1;\r\n }\r\n\r\n if(sizeof(btree_node) + db_align(sizeof(btree_value) + value_size, DB_ALIGNMENT) > DB_BLOCK_SIZE){\r\n errno = E2BIG;\r\n return -1;\r\n }\r\n\r\n int i,rc;\r\n btree_node *node = (btree_node *)((char*)db + DB_HEAD_SIZE + DB_BLOCK_SIZE * 0);\r\n btree_node *sub_x = (btree_node *)((char*)db + DB_HEAD_SIZE + DB_BLOCK_SIZE * 1);\r\n btree_node *sub_y = (btree_node *)((char*)db + DB_HEAD_SIZE + DB_BLOCK_SIZE * 2);\r\n btree_node *valnode = (btree_node *)((char*)db + DB_HEAD_SIZE + DB_BLOCK_SIZE * 3);\r\n // 关键字只会插入到叶子节点,在由上往下的遍历中,需要将已满的节点分裂\r\n /* node */\r\n /* / \\ */\r\n /* sub_x sub_y */\r\n \r\n node_seek(db, node, DB_HEAD_SIZE);// root 读取根节点\r\n\r\n if(node->num >= db->M-1){\r\n // root is full 根节点已满\r\n \r\n if(node_create(db, sub_x, node->leaf, TYPE_KEY) == -1 || node_create(db, sub_y, node->leaf, TYPE_KEY) == -1){\r\n return -1;\r\n }\r\n\r\n sub_x->num = node->num;\r\n memcpy((char*)sub_x + sizeof(btree_node), (char*)node + sizeof(btree_node), DB_BLOCK_SIZE - sizeof(btree_node));\r\n\r\n node->num = 0;\r\n node->leaf = BTREE_NON_LEAF;\r\n btree_key_ptr(db,node,0)->child = sub_x->self;\r\n\r\n btree_split_child(db, node, 0, sub_x, sub_y);\r\n }\r\n \r\n while(node->leaf == BTREE_NON_LEAF){\r\n i = key_binary_search(db, node, key);\r\n if(i >= 0){\r\n // 关键字已存在\r\n return 0;\r\n }\r\n\r\n i = -(i+1);\r\n \r\n // 需要判断子节点是否已满\r\n node_seek(db, sub_x, btree_key_ptr(db,node,i)->child);\r\n\r\n if(sub_x->num < db->M-1){\r\n // child is no full 子节点未满\r\n memcpy(node, sub_x, DB_BLOCK_SIZE);\r\n continue;\r\n }\r\n\r\n // child is full 子节点已满,开始分裂\r\n if(node_create(db, sub_y, sub_x->leaf, TYPE_KEY) == -1){\r\n return -1;\r\n }\r\n btree_split_child(db, node, i, sub_x, sub_y);\r\n\r\n // 判断上升的关键字\r\n rc = db->key_cmp(key, btree_key_ptr(db,node,i)->key, db->key_size);\r\n if(rc == 0){\r\n // 上升的关键字相同\r\n return 0;\r\n }else if(rc > 0){\r\n // 上升的关键字更大\r\n memcpy(node, sub_y, DB_BLOCK_SIZE);\r\n }else{\r\n // 上升的关键字更小\r\n memcpy(node, sub_x, DB_BLOCK_SIZE);\r\n }\r\n }\r\n\r\n i = key_binary_search(db, node, key);\r\n if(i >= 0){\r\n // 关键字已存在\r\n return 0;\r\n }\r\n\r\n i = -(i+1);\r\n\r\n // 寻找合适的btree_value数据块\r\n if(db->current != 0L){\r\n node_seek(db, valnode, db->current);\r\n if(valnode->last + db_align(sizeof(btree_value) + value_size, DB_ALIGNMENT) > DB_BLOCK_SIZE){\r\n // 当前的btree_value数据块不够空间分配\r\n db->current = 0L;\r\n head_flush(db);\r\n }\r\n }\r\n if(db->current == 0L && node_create(db, valnode, 0, TYPE_VALUE) == -1){\r\n return -1;\r\n }\r\n\r\n // 先存储值\r\n btree_value_ptr(valnode,valnode->last)->size = value_size;\r\n memcpy(btree_value_ptr(valnode,valnode->last)->value, value, value_size);\r\n size_t last = valnode->last;\r\n valnode->last += db_align(sizeof(btree_value) + value_size, DB_ALIGNMENT);\r\n valnode->num++;\r\n node_flush(db, valnode);\r\n\r\n // 再存储关键字\r\n // leaf node right shift one position 叶子节点右移腾出一个空位\r\n keycpy(db, btree_key_ptr(db,node,i+1), btree_key_ptr(db,node,i), node->num-i);\r\n switch (db->key_type)\r\n {\r\n case DB_STRINGKEY:\r\n strcpy((char*)(btree_key_ptr(db,node,i)->key), key);\r\n break;\r\n case DB_BYTESKEY:\r\n case DB_INT32KEY:\r\n case DB_INT64KEY:\r\n memcpy(btree_key_ptr(db,node,i)->key, key, db->key_size);\r\n break;\r\n }\r\n btree_key_ptr(db, node, i)->value = valnode->self + last; // 记录value所在数据块的位置 + 偏移\r\n node->num++;\r\n node_flush(db, node);\r\n db->key_total++;\r\n head_flush(db);\r\n return 1;\r\n}\r\n\r\n/**\r\n * @brief delete key 删除值\r\n * @param[in] db 数据库句柄\r\n * @param[in] key key_size can\'t exceed max_key_size 需要保证key类型和创建数据库时一致\r\n * @return ==1 if success, ==0 if key no found, ==-1 error\r\n*/\r\nint db_delete(db_t* db, void* key){\r\n if(db->key_type == DB_STRINGKEY && strlen((char*)key) >= db->key_size){\r\n errno = EINVAL;\r\n return -1;\r\n }\r\n\r\n #define LESS 1\r\n #define MORE 2\r\n int i,i_match=-1,flag = 0;\r\n btree_node *node = (btree_node *)((char*)db + DB_HEAD_SIZE + DB_BLOCK_SIZE * 0);\r\n btree_node *node_match = (btree_node *)((char*)db + DB_HEAD_SIZE + DB_BLOCK_SIZE * 1);\r\n btree_node *sub_x = (btree_node *)((char*)db + DB_HEAD_SIZE + DB_BLOCK_SIZE * 2);\r\n btree_node *sub_y = (btree_node *)((char*)db + DB_HEAD_SIZE + DB_BLOCK_SIZE * 3);\r\n btree_node *sub_w = (btree_node *)((char*)db + DB_HEAD_SIZE + DB_BLOCK_SIZE * 4);\r\n /* 删除只会发生在叶子节点,在由上往下的遍历中,需要保证叶子节点有足够的关键字数(大于ceil(M)) */\r\n /* __ node */\r\n /* / / \\ */\r\n /* sub_w sub_x sub_y */\r\n\r\n node_seek(db, node, DB_HEAD_SIZE);// root 读取根节点\r\n\r\n while(node->leaf == BTREE_NON_LEAF){\r\n switch (flag)\r\n {\r\n case LESS:\r\n i = -0-1;\r\n break;\r\n case MORE:\r\n i = -node->num-1;\r\n break;\r\n default:\r\n i = key_binary_search(db, node, key);\r\n break;\r\n }\r\n\r\n // match when in internal 在非叶子节点中匹配到,需要找到在前缀或后缀关键字(该关键字只会在叶子结点中)\r\n if(i >= 0){\r\n // 判断左子树是否方便删除前缀关键字(左子树关键字个数大于ceil(M))\r\n node_seek(db, sub_x, btree_key_ptr(db,node,i)->child);\r\n if(sub_x->num > ceil(db->M)){\r\n // 寻找前缀关键字,即是寻找左子树的最大关键字\r\n flag = MORE;\r\n i_match = i;\r\n memcpy(node_match, node, DB_BLOCK_SIZE);\r\n memcpy(node, sub_x, DB_BLOCK_SIZE);\r\n }else{\r\n // 判断右子树是否方便删除后缀关键字(右子树关键字个数大于ceil(M))\r\n node_seek(db,sub_y,btree_key_ptr(db,node,i+1)->child);\r\n if(sub_y->num > ceil(db->M)){\r\n // 寻找后缀关键字,即是寻找右子树的最小关键字\r\n flag = LESS;\r\n i_match = i;\r\n memcpy(node_match, node, DB_BLOCK_SIZE);\r\n memcpy(node, sub_y, DB_BLOCK_SIZE);\r\n }else{\r\n // 左右子树都不方便,则合并\r\n if(!btree_merge(db, node, i, sub_x, sub_y)){\r\n memcpy(node, sub_x, DB_BLOCK_SIZE);\r\n }\r\n }\r\n }\r\n continue;\r\n }\r\n \r\n i = -(i+1);\r\n\r\n // need prepare , make sure child have enough key 自上而下调整子树,保证最后在叶子节点处方便删除关键字(自上而下,确保子树关键字个数大于ceil(M))\r\n node_seek(db, sub_x, btree_key_ptr(db,node,i)->child);\r\n\r\n if(sub_x->num > ceil(db->M)){\r\n // already enough\r\n memcpy(node, sub_x, DB_BLOCK_SIZE);\r\n continue;\r\n }\r\n\r\n if(i+1<=node->num){\r\n node_seek(db, sub_y, btree_key_ptr(db,node,i+1)->child);\r\n }\r\n\r\n if(i-1>=0 && ((i+1>node->num) || sub_y->num<=ceil(db->M))){\r\n node_seek(db, sub_w, btree_key_ptr(db,node,i-1)->child);\r\n }\r\n \r\n if(i+1<=node->num && sub_y->num>ceil(db->M)){\r\n // borrow from right 从子树的右兄弟借\r\n memcpy(btree_key_ptr(db,sub_x,sub_x->num)->key, btree_key_ptr(db,node,i)->key, db->key_align - sizeof(btree_key));\r\n btree_key_ptr(db,sub_x,sub_x->num)->value = btree_key_ptr(db,node,i)->value;\r\n btree_key_ptr(db,sub_x,sub_x->num+1)->child = btree_key_ptr(db,sub_y,0)->child;\r\n sub_x->num++;\r\n\r\n memcpy(btree_key_ptr(db,node,i)->key, btree_key_ptr(db,sub_y,0)->key, db->key_align - sizeof(btree_key));\r\n btree_key_ptr(db,node,i)->value = btree_key_ptr(db,sub_y,0)->value;\r\n keycpy(db, btree_key_ptr(db,sub_y,0),btree_key_ptr(db,sub_y,1), sub_y->num-1);\r\n sub_y->num--;\r\n\r\n node_flush(db,node);\r\n node_flush(db,sub_x);\r\n node_flush(db,sub_y);\r\n memcpy(node,sub_x,DB_BLOCK_SIZE);\r\n }else if(i-1>=0 && sub_w->num>ceil(db->M)){\r\n // borrow from left 从子树的左兄弟借\r\n keycpy(db, btree_key_ptr(db,sub_x,1),btree_key_ptr(db,sub_x,0), sub_x->num);\r\n memcpy(btree_key_ptr(db,sub_x,0)->key, btree_key_ptr(db,node,i-1)->key, db->key_align - sizeof(btree_key));\r\n btree_key_ptr(db,sub_x,0)->value = btree_key_ptr(db,node,i-1)->value;\r\n btree_key_ptr(db,sub_x,0)->child = btree_key_ptr(db,sub_w,sub_w->num)->child;\r\n sub_x->num++;\r\n \r\n memcpy(btree_key_ptr(db,node,i-1)->key, btree_key_ptr(db,sub_w,sub_w->num-1)->key, db->key_align - sizeof(btree_key));\r\n btree_key_ptr(db,node,i-1)->value = btree_key_ptr(db,sub_w,sub_w->num-1)->value;\r\n sub_w->num--;\r\n\r\n node_flush(db,node);\r\n node_flush(db,sub_x);\r\n node_flush(db,sub_w);\r\n memcpy(node,sub_x,DB_BLOCK_SIZE);\r\n }else{\r\n if(i+1<=node->num){\r\n // merge with right\r\n if(!btree_merge(db,node,i,sub_x,sub_y)){\r\n memcpy(node,sub_x,DB_BLOCK_SIZE);\r\n }\r\n }else{\r\n // merge with left\r\n if(!btree_merge(db,node,i-1,sub_w,sub_x)){\r\n memcpy(node,sub_w,DB_BLOCK_SIZE);\r\n }\r\n }\r\n }\r\n }\r\n\r\n off_t offset = 0;\r\n if(flag == LESS){\r\n // 找到后缀关键字,即是右子树的最小关键字\r\n offset = btree_key_ptr(db,node_match,i_match)->value;\r\n\r\n memcpy(btree_key_ptr(db,node_match,i_match)->key, btree_key_ptr(db,node,0)->key, db->key_align - sizeof(btree_key));\r\n btree_key_ptr(db,node_match,i_match)->value = btree_key_ptr(db,node,0)->value;\r\n keycpy(db, btree_key_ptr(db,node,0), btree_key_ptr(db,node,1), node->num-1);\r\n node->num--;\r\n node_flush(db,node_match);\r\n node_flush(db,node);\r\n }else if(flag == MORE){\r\n // 找到前缀关键字,即是左子树的最大关键字\r\n offset = btree_key_ptr(db,node_match,i_match)->value;\r\n\r\n memcpy(btree_key_ptr(db,node_match,i_match)->key, btree_key_ptr(db,node,node->num-1)->key, db->key_align - sizeof(btree_key));\r\n btree_key_ptr(db,node_match,i_match)->value = btree_key_ptr(db,node,node->num-1)->value;\r\n node->num--;\r\n node_flush(db,node_match);\r\n node_flush(db,node);\r\n }else{\r\n i = key_binary_search(db,node,key);\r\n if(i < 0){\r\n return 0;\r\n }\r\n\r\n // 关键字刚好在叶子节点\r\n offset = btree_key_ptr(db,node,i)->value;\r\n keycpy(db, btree_key_ptr(db,node,i),btree_key_ptr(db,node,i+1), node->num - i - 1);\r\n node->num--;\r\n node_flush(db,node);\r\n }\r\n\r\n // release value block 释放关键字对应的value\r\n node_seek(db, node, DB_HEAD_SIZE + ((offset - DB_HEAD_SIZE)& ~(DB_BLOCK_SIZE-1)));\r\n node->num--;\r\n // btree_value的数据块,引用数为0时,释放该数据块\r\n if(node->num == 0){\r\n if(node->self == db->current){\r\n db->current = 0;\r\n // head_flush(d);// node_destroy will flush again\r\n }\r\n node_destroy(db, node);\r\n }else{\r\n node_flush(db, node);\r\n }\r\n db->key_total--;\r\n head_flush(db);\r\n return 1;\r\n}\r\n\r\n/**\r\n * @brief search key 查询值\r\n * @param[in] db 数据库句柄\r\n * @param[in] key key_size can\'t exceed max_key_size 需要保证key类型和创建数据库时一致\r\n * @param[out] value\r\n * @param[in] value_size 需要保证空间足够大\r\n * @return >=0 if success, ==-1 error\r\n*/\r\nint db_search(db_t* db, void* key, void *value, size_t value_size){\r\n if(db->key_type == DB_STRINGKEY && strlen((char*)key) >= db->key_size){\r\n errno = EINVAL;\r\n return -1;\r\n }\r\n\r\n btree_node *node = (btree_node *)((char*)db + DB_HEAD_SIZE + DB_BLOCK_SIZE * 0);\r\n int i;\r\n off_t offset = DB_HEAD_SIZE;\r\n\r\n do{\r\n node_seek(db, node, offset);\r\n i = key_binary_search(db, node, key);\r\n if(i >= 0){\r\n offset = btree_key_ptr(db, node, i)->value;\r\n node_seek(db, node, DB_HEAD_SIZE + ((offset - DB_HEAD_SIZE)& ~(DB_BLOCK_SIZE-1)));\r\n btree_value *pval = btree_value_ptr(node, offset-node->self);\r\n if(pval->size > value_size){\r\n errno = E2BIG;\r\n return -1;\r\n }\r\n memcpy(value,pval->value, pval->size);\r\n return pval->size;\r\n }\r\n i = -(i+1);\r\n offset = btree_key_ptr(db, node, i)->child;\r\n }while(offset != 0);\r\n\r\n errno = ENOMSG;\r\n return -1;\r\n}\r\n\r\n/***************************************/\r\n#include \r\n#include \r\n#include \r\n#include \r\n\r\n#define COUNT 100000\r\n#define PATH \"./test.db\"\r\n\r\nint main(){\r\n db_t* db;\r\n int i,rc;\r\n char value[128];\r\n\r\n // 创建数据库\r\n unlink(PATH);\r\n assert(db_create(PATH,DB_INT32KEY,sizeof(int)) == 0);\r\n\r\n // 打开数据库\r\n assert(db_open(&db,PATH) == 0);\r\n\r\n // 插入操作\r\n for(i=0;i= 0);\r\n printf(\"search key: %d value: %.*s\\n\",i,rc,value);\r\n\r\n // 删除操作\r\n for(i=0;i\r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n\r\nclass ThreadPool {\r\npublic:\r\n ThreadPool(size_t);\r\n template\r\n auto enqueue(F&& f, Args&&... args) \r\n -> std::future::type>;\r\n ~ThreadPool();\r\nprivate:\r\n // need to keep track of threads so we can join them\r\n std::vector< std::thread > workers;\r\n // the task queue\r\n std::queue< std::function > tasks;\r\n \r\n // synchronization\r\n std::mutex queue_mutex;\r\n std::condition_variable condition;\r\n bool stop;\r\n};\r\n \r\n// the constructor just launches some amount of workers\r\ninline ThreadPool::ThreadPool(size_t threads)\r\n : stop(false)\r\n{\r\n for(size_t i = 0;i task;\r\n\r\n {\r\n std::unique_lock lock(this->queue_mutex);\r\n this->condition.wait(lock,\r\n [this]{ return this->stop || !this->tasks.empty(); });\r\n if(this->stop && this->tasks.empty())\r\n return;\r\n task = std::move(this->tasks.front());\r\n this->tasks.pop();\r\n }\r\n\r\n task();\r\n }\r\n }\r\n );\r\n}\r\n\r\n// add new work item to the pool\r\ntemplate\r\nauto ThreadPool::enqueue(F&& f, Args&&... args) \r\n -> std::future::type>\r\n{\r\n using return_type = typename std::result_of::type;\r\n\r\n auto task = std::make_shared< std::packaged_task >(\r\n std::bind(std::forward(f), std::forward(args)...)\r\n );\r\n \r\n std::future res = task->get_future();\r\n {\r\n std::unique_lock lock(queue_mutex);\r\n\r\n // don\'t allow enqueueing after stopping the pool\r\n if(stop)\r\n throw std::runtime_error(\"enqueue on stopped ThreadPool\");\r\n\r\n tasks.emplace([task](){ (*task)(); });\r\n }\r\n condition.notify_one();\r\n return res;\r\n}\r\n\r\n// the destructor joins all threads\r\ninline ThreadPool::~ThreadPool()\r\n{\r\n {\r\n std::unique_lock lock(queue_mutex);\r\n stop = true;\r\n }\r\n condition.notify_all();\r\n for(std::thread &worker: workers)\r\n worker.join();\r\n}\r\n\r\n#endif', 1); +INSERT INTO `onefile` VALUES (7, 'looptap', 'HTML', '消磨时间的小游戏,把球停在有颜色区域', 12, 'https://github.com/vasanthv/looptap', 5200, '\r\n\r\n\r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n Looptap - 消磨时间的小游戏\r\n \r\n\r\n \r\n\r\n\r\n\r\n\r\n

\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n 点击 ▶️ 开始 / 点击任何地方停止\r\n \r\n \r\n 让球停在有颜色的区域就行!\r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n \r\n 分享你的分数\r\n \r\n
\r\n 源码:\r\n Vasanth.V
\r\n 灵感来自 iOS 游戏 Coogyloop\r\n
\r\n
\r\n\r\n\r\n\r\n', 1); +INSERT INTO `onefile` VALUES (8, 'minesweeper', 'HTML', '扫雷游戏', 12, 'https://github.com/521xueweihan/OneFile/blob/main/src/html/minesweeper.html', 3200, '\r\n\r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n 扫雷\r\n \r\n \r\n\r\n \r\n\r\n\r\n\r\n
\r\n
\r\n Mode:\r\n  \r\n Row x Col: x
\r\n Mines:  \r\n \r\n
\r\n
\r\n
\r\n
\r\n

Web Minesweeper

\r\n

Beginner Mode

\r\n
\r\n
\r\n
\r\n \r\n \r\n \r\n \r\n \r\n \r\n
Mines:
10
🙂Time:
000
\r\n\r\n
\r\n
\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
 \r\n  \r\n  \r\n  \r\n  \r\n  \r\n  \r\n  \r\n  \r\n
 \r\n  \r\n  \r\n  \r\n  \r\n  \r\n  \r\n  \r\n  \r\n
 \r\n  \r\n  \r\n  \r\n  \r\n  \r\n  \r\n  \r\n  \r\n
 \r\n  \r\n  \r\n  \r\n  \r\n  \r\n  \r\n  \r\n  \r\n
 \r\n  \r\n  \r\n  \r\n  \r\n  \r\n  \r\n  \r\n  \r\n
 \r\n  \r\n  \r\n  \r\n  \r\n  \r\n  \r\n  \r\n  \r\n
 \r\n  \r\n  \r\n  \r\n  \r\n  \r\n  \r\n  \r\n  \r\n
 \r\n  \r\n  \r\n  \r\n  \r\n  \r\n  \r\n  \r\n  \r\n
 \r\n  \r\n  \r\n  \r\n  \r\n  \r\n  \r\n  \r\n  \r\n
\r\n
\r\n
\r\n\r\n\r\n', 1); +INSERT INTO `onefile` VALUES (9, '2048', 'HTML', '2048 游戏', 12, 'https://github.com/521xueweihan/OneFile/blob/main/src/html/2048.html', 3100, '\r\n\r\n\r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n 2048-game \r\n \r\n \r\n\r\n\r\n\r\n
\r\n
\r\n
\r\n
\r\n Score:\r\n \r\n
\r\n
\r\n 3X3\r\n 4X4\r\n 5X5\r\n 6X6\r\n \r\n \r\n
\r\n
\r\n
\r\n
\r\n
\r\n

\r\n Try again\r\n
\r\n \r\n\r\n\r\n\r\n', 1); +INSERT INTO `onefile` VALUES (10, 'ascii-cam', 'HTML', '把视频图像转成 ascii \r\n', 12, 'https://github.com/521xueweihan/OneFile/blob/main/src/html/ascii-cam.html', 2600, '\r\n\r\n\r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n A S C I I C A M\r\n \r\n\r\n\r\n\r\n
\r\n\r\n   _n_|_|_,_\r\n  |===.-.===|\r\n  |  ((_))  |\r\n  \'===\'-\'===\'\r\nA S C I I C A M\r\n点击“允许”使用摄像头\r\n    
\r\n\r\n
Allow access to camera to use
\r\n\r\n
\r\n
\r\n
\r\n            
\r\n            
\r\n        
\r\n
\r\n\r\n \r\n \r\n \r\n\r\n\r\n', 1); +INSERT INTO `onefile` VALUES (11, 'the-super-tiny-compiler', 'JavaScript', '人人都能看懂的微型编译器', 12, 'https://github.com/jamiebuilds/the-super-tiny-compiler', 9100, '\'use strict\';\r\n\r\n/**\r\n * TTTTTTTTTTTTTTTTTTTTTTTHHHHHHHHH HHHHHHHHHEEEEEEEEEEEEEEEEEEEEEE\r\n * T:::::::::::::::::::::TH:::::::H H:::::::HE::::::::::::::::::::E\r\n * T:::::::::::::::::::::TH:::::::H H:::::::HE::::::::::::::::::::E\r\n * T:::::TT:::::::TT:::::THH::::::H H::::::HHEE::::::EEEEEEEEE::::E\r\n * TTTTTT T:::::T TTTTTT H:::::H H:::::H E:::::E EEEEEE\r\n * T:::::T H:::::H H:::::H E:::::E\r\n * T:::::T H::::::HHHHH::::::H E::::::EEEEEEEEEE\r\n * T:::::T H:::::::::::::::::H E:::::::::::::::E\r\n * T:::::T H:::::::::::::::::H E:::::::::::::::E\r\n * T:::::T H::::::HHHHH::::::H E::::::EEEEEEEEEE\r\n * T:::::T H:::::H H:::::H E:::::E\r\n * T:::::T H:::::H H:::::H E:::::E EEEEEE\r\n * TT:::::::TT HH::::::H H::::::HHEE::::::EEEEEEEE:::::E\r\n * T:::::::::T H:::::::H H:::::::HE::::::::::::::::::::E\r\n * T:::::::::T H:::::::H H:::::::HE::::::::::::::::::::E\r\n * TTTTTTTTTTT HHHHHHHHH HHHHHHHHHEEEEEEEEEEEEEEEEEEEEEE\r\n *\r\n * SSSSSSSSSSSSSSS UUUUUUUU UUUUUUUUPPPPPPPPPPPPPPPPP EEEEEEEEEEEEEEEEEEEEEERRRRRRRRRRRRRRRRR\r\n * SS:::::::::::::::SU::::::U U::::::UP::::::::::::::::P E::::::::::::::::::::ER::::::::::::::::R\r\n * S:::::SSSSSS::::::SU::::::U U::::::UP::::::PPPPPP:::::P E::::::::::::::::::::ER::::::RRRRRR:::::R\r\n * S:::::S SSSSSSSUU:::::U U:::::UUPP:::::P P:::::PEE::::::EEEEEEEEE::::ERR:::::R R:::::R\r\n * S:::::S U:::::U U:::::U P::::P P:::::P E:::::E EEEEEE R::::R R:::::R\r\n * S:::::S U:::::U U:::::U P::::P P:::::P E:::::E R::::R R:::::R\r\n * S::::SSSS U:::::U U:::::U P::::PPPPPP:::::P E::::::EEEEEEEEEE R::::RRRRRR:::::R\r\n * SS::::::SSSSS U:::::U U:::::U P:::::::::::::PP E:::::::::::::::E R:::::::::::::RR\r\n * SSS::::::::SS U:::::U U:::::U P::::PPPPPPPPP E:::::::::::::::E R::::RRRRRR:::::R\r\n * SSSSSS::::S U:::::U U:::::U P::::P E::::::EEEEEEEEEE R::::R R:::::R\r\n * S:::::S U:::::U U:::::U P::::P E:::::E R::::R R:::::R\r\n * S:::::S U::::::U U::::::U P::::P E:::::E EEEEEE R::::R R:::::R\r\n * SSSSSSS S:::::S U:::::::UUU:::::::U PP::::::PP EE::::::EEEEEEEE:::::ERR:::::R R:::::R\r\n * S::::::SSSSSS:::::S UU:::::::::::::UU P::::::::P E::::::::::::::::::::ER::::::R R:::::R\r\n * S:::::::::::::::SS UU:::::::::UU P::::::::P E::::::::::::::::::::ER::::::R R:::::R\r\n * SSSSSSSSSSSSSSS UUUUUUUUU PPPPPPPPPP EEEEEEEEEEEEEEEEEEEEEERRRRRRRR RRRRRRR\r\n *\r\n * TTTTTTTTTTTTTTTTTTTTTTTIIIIIIIIIINNNNNNNN NNNNNNNNYYYYYYY YYYYYYY\r\n * T:::::::::::::::::::::TI::::::::IN:::::::N N::::::NY:::::Y Y:::::Y\r\n * T:::::::::::::::::::::TI::::::::IN::::::::N N::::::NY:::::Y Y:::::Y\r\n * T:::::TT:::::::TT:::::TII::::::IIN:::::::::N N::::::NY::::::Y Y::::::Y\r\n * TTTTTT T:::::T TTTTTT I::::I N::::::::::N N::::::NYYY:::::Y Y:::::YYY\r\n * T:::::T I::::I N:::::::::::N N::::::N Y:::::Y Y:::::Y\r\n * T:::::T I::::I N:::::::N::::N N::::::N Y:::::Y:::::Y\r\n * T:::::T I::::I N::::::N N::::N N::::::N Y:::::::::Y\r\n * T:::::T I::::I N::::::N N::::N:::::::N Y:::::::Y\r\n * T:::::T I::::I N::::::N N:::::::::::N Y:::::Y\r\n * T:::::T I::::I N::::::N N::::::::::N Y:::::Y\r\n * T:::::T I::::I N::::::N N:::::::::N Y:::::Y\r\n * TT:::::::TT II::::::IIN::::::N N::::::::N Y:::::Y\r\n * T:::::::::T I::::::::IN::::::N N:::::::N YYYY:::::YYYY\r\n * T:::::::::T I::::::::IN::::::N N::::::N Y:::::::::::Y\r\n * TTTTTTTTTTT IIIIIIIIIINNNNNNNN NNNNNNN YYYYYYYYYYYYY\r\n *\r\n * CCCCCCCCCCCCC OOOOOOOOO MMMMMMMM MMMMMMMMPPPPPPPPPPPPPPPPP IIIIIIIIIILLLLLLLLLLL EEEEEEEEEEEEEEEEEEEEEERRRRRRRRRRRRRRRRR\r\n * CCC::::::::::::C OO:::::::::OO M:::::::M M:::::::MP::::::::::::::::P I::::::::IL:::::::::L E::::::::::::::::::::ER::::::::::::::::R\r\n * CC:::::::::::::::C OO:::::::::::::OO M::::::::M M::::::::MP::::::PPPPPP:::::P I::::::::IL:::::::::L E::::::::::::::::::::ER::::::RRRRRR:::::R\r\n * C:::::CCCCCCCC::::CO:::::::OOO:::::::OM:::::::::M M:::::::::MPP:::::P P:::::PII::::::IILL:::::::LL EE::::::EEEEEEEEE::::ERR:::::R R:::::R\r\n * C:::::C CCCCCCO::::::O O::::::OM::::::::::M M::::::::::M P::::P P:::::P I::::I L:::::L E:::::E EEEEEE R::::R R:::::R\r\n * C:::::C O:::::O O:::::OM:::::::::::M M:::::::::::M P::::P P:::::P I::::I L:::::L E:::::E R::::R R:::::R\r\n * C:::::C O:::::O O:::::OM:::::::M::::M M::::M:::::::M P::::PPPPPP:::::P I::::I L:::::L E::::::EEEEEEEEEE R::::RRRRRR:::::R\r\n * C:::::C O:::::O O:::::OM::::::M M::::M M::::M M::::::M P:::::::::::::PP I::::I L:::::L E:::::::::::::::E R:::::::::::::RR\r\n * C:::::C O:::::O O:::::OM::::::M M::::M::::M M::::::M P::::PPPPPPPPP I::::I L:::::L E:::::::::::::::E R::::RRRRRR:::::R\r\n * C:::::C O:::::O O:::::OM::::::M M:::::::M M::::::M P::::P I::::I L:::::L E::::::EEEEEEEEEE R::::R R:::::R\r\n * C:::::C O:::::O O:::::OM::::::M M:::::M M::::::M P::::P I::::I L:::::L E:::::E R::::R R:::::R\r\n * C:::::C CCCCCCO::::::O O::::::OM::::::M MMMMM M::::::M P::::P I::::I L:::::L LLLLLL E:::::E EEEEEE R::::R R:::::R\r\n * C:::::CCCCCCCC::::CO:::::::OOO:::::::OM::::::M M::::::MPP::::::PP II::::::IILL:::::::LLLLLLLLL:::::LEE::::::EEEEEEEE:::::ERR:::::R R:::::R\r\n * CC:::::::::::::::C OO:::::::::::::OO M::::::M M::::::MP::::::::P I::::::::IL::::::::::::::::::::::LE::::::::::::::::::::ER::::::R R:::::R\r\n * CCC::::::::::::C OO:::::::::OO M::::::M M::::::MP::::::::P I::::::::IL::::::::::::::::::::::LE::::::::::::::::::::ER::::::R R:::::R\r\n * CCCCCCCCCCCCC OOOOOOOOO MMMMMMMM MMMMMMMMPPPPPPPPPP IIIIIIIIIILLLLLLLLLLLLLLLLLLLLLLLLEEEEEEEEEEEEEEEEEEEEEERRRRRRRR RRRRRRR\r\n *\r\n * =======================================================================================================================================================================\r\n * =======================================================================================================================================================================\r\n * =======================================================================================================================================================================\r\n * =======================================================================================================================================================================\r\n */\r\n\r\n/**\r\n * 今天我们要一起写一个编译器。 可能和之前的编译器不太一样,因为这个编译器实在是太 mini 了!迷你到除去注视意外,整个文件大约只有 200 行实际代码。\r\n * 首先我们将把一些类似 lisp 的函数调用编译成类似 C 的函数调用。如果您不熟悉其中之一,那就先来一个简单快速的介绍:\r\n * 如果我们有两个函数 `add` 和 `subtract` 他们会写成这样:\r\n *\r\n * LISP C\r\n *\r\n * 2 + 2 (add 2 2) add(2, 2)\r\n * 4 - 2 (subtract 4 2) subtract(4, 2)\r\n * 2 + (4 - 2) (add 2 (subtract 4 2)) add(2, subtract(4, 2))\r\n *\r\n * 这个对比容易理解吧?这正是我们要编译的。 虽然这不是完整的 LISP 或 C 语法,但它足以展示现代编译器的许多主要部分。\r\n */\r\n\r\n/**\r\n * 大多数编译器分为三个主要阶段:解析、转换和代码生成:\r\n *\r\n * 1. 解析:将原始代码转化为更抽象的代码表示\r\n *\r\n * 2. 转换:采用抽象表示并进行操作来执行编译器希望它执行的任何操作\r\n *\r\n * 3. 代码生成:把经过转换表示的代码转换为新代码\r\n\r\n/**\r\n * 解析\r\n * -------\r\n *\r\n * 解析通常分为两个阶段:词法分析和句法分析\r\n *\r\n * 1. 词法分析:获取原始代码并通过一种称为标记器(或词法分析器 Tokenizer)的东西将其拆分为一种称为标记(Token)的东西。\r\n * 标记是一个数组,它描述了一个独立的语法片段。这些片段可以是数字、标签、标点符号、运算符等等。\r\n *\r\n * 2. 语法分析:获取之前生成的标记(Token),并把它们转换成一种抽象的表示,这种抽象的表示描述了代码语句中的每一个片段以及它们之间的关系。\r\n * 这被称为中间表示(intermediate representation)或抽象语法树(Abstract Syntax Tree, 缩写为AST)。\r\n * AST是一个深度嵌套的对象,用一种更容易处理的方式代表了代码本身,也能给我们更多信息。\r\n *\r\n * 对于下面这个句法:\r\n *\r\n * (add 2 (subtract 4 2))\r\n *\r\n * 标记可能看起来像这样:\r\n *\r\n * [\r\n * { type: \'paren\', value: \'(\' },\r\n * { type: \'name\', value: \'add\' },\r\n * { type: \'number\', value: \'2\' },\r\n * { type: \'paren\', value: \'(\' },\r\n * { type: \'name\', value: \'subtract\' },\r\n * { type: \'number\', value: \'4\' },\r\n * { type: \'number\', value: \'2\' },\r\n * { type: \'paren\', value: \')\' },\r\n * { type: \'paren\', value: \')\' },\r\n * ]\r\n *\r\n * 然后一个抽象语法树(AST)可能看起来像这样:\r\n *\r\n * {\r\n * type: \'Program\',\r\n * body: [{\r\n * type: \'CallExpression\',\r\n * name: \'add\',\r\n * params: [{\r\n * type: \'NumberLiteral\',\r\n * value: \'2\',\r\n * }, {\r\n * type: \'CallExpression\',\r\n * name: \'subtract\',\r\n * params: [{\r\n * type: \'NumberLiteral\',\r\n * value: \'4\',\r\n * }, {\r\n * type: \'NumberLiteral\',\r\n * value: \'2\',\r\n * }]\r\n * }]\r\n * }]\r\n * }\r\n */\r\n\r\n/**\r\n * 转换\r\n * --------------\r\n *\r\n * 编译器的下一个阶段是转换。同样的,获取了 AST 之后再对其进行更改。\r\n * 它可以用相同的语言操作 AST,也可以将其翻译成一种全新的语言。\r\n *\r\n * 让我们来看看如何转换 AST。\r\n *\r\n * 你可能会注意到我们的 AST 中的元素看起来非常相似。\r\n * 这些对象都有具有类型属性。这些中的每一个都称为 AST 的节点。\r\n * 这些节点在树上定义了属性并描述了树上的每一个独立的部分。\r\n *\r\n * 我们可以有一个“NumberLiteral”的节点:\r\n *\r\n * {\r\n * type: \'NumberLiteral\',\r\n * value: \'2\',\r\n * }\r\n *\r\n * 或者可能是“CallExpression”的一个节点:\r\n *\r\n * {\r\n * type: \'CallExpression\',\r\n * name: \'subtract\',\r\n * params: [...嵌套节点放在这里...],\r\n * }\r\n *\r\n * 在转换 AST 的时候,我们可以通过新增、删除、替换属性来操作节点,我们可以新增节点、删除节点,\r\n * 或者我们可以在原有的 AST 结构保持不变的状态下创建一个基于它的新的 AST。\r\n *\r\n * 由于我们的目标是一种新的语言,所以我们将要专注于创造一个完全新的 AST 来配合这个特定的语言。\r\n *\r\n * 遍历\r\n * ---------\r\n *\r\n * 为了浏览所有的这些节点,我们需要遍历它们。\r\n * 这个遍历过程首先是深度遍历。\r\n *\r\n * {\r\n * type: \'Program\',\r\n * body: [{\r\n * type: \'CallExpression\',\r\n * name: \'add\',\r\n * params: [{\r\n * type: \'NumberLiteral\',\r\n * value: \'2\'\r\n * }, {\r\n * type: \'CallExpression\',\r\n * name: \'subtract\',\r\n * params: [{\r\n * type: \'NumberLiteral\',\r\n * value: \'4\'\r\n * }, {\r\n * type: \'NumberLiteral\',\r\n * value: \'2\'\r\n * }]\r\n * }]\r\n * }]\r\n * }\r\n *\r\n * 对于上面那个 AST,我们会:\r\n *\r\n * 1.Program-首先从 AST 的顶层开始\r\n * 2. CallExpression(add)-移动到程序主体的第一个元素\r\n * 3. NumberLiteral(2)-移动到 NumberLiteral 参数的第一个元素\r\n * 4. CallExpression (subtract) - 移动到 CallExpression 参数的第二个元素\r\n * 5. NumberLiteral (4) - 移动到 CallExpression 参数的第一个元素\r\n * 6. NumberLiteral (2) - 移动到 CallExpression 参数的第二个元素\r\n *\r\n * 如果我们直接操作这个 AST,而不是创造一个单独的 AST,我们很有可能会在这里引入各种抽象。\r\n * 但是仅仅访问树中的每个节点对于我们来说想做和能做的事情已经很多了。\r\n *\r\n * 使用访问(visiting)这个词是因为这是一种模式,代表在对象结构内对元素进行操作。\r\n *\r\n * 访问者(Visitors)\r\n * --------\r\n *\r\n * 这里的基本思想是我们将创建一个“访问者”对象,该对象具有接受不同节点类型的方法。\r\n *\r\n * var visitor = {\r\n * NumberLiteral() {},\r\n * CallExpression() {},\r\n * };\r\n *\r\n * 当我们遍历我们的 AST 时,只要我们“进入”一个匹配类型的节点,我们就会调用这个“访问者”的方法。\r\n * 为了使他能够有效,我们还将传递这个节点和它对父节点的引用。\r\n *\r\n * var visitor = {\r\n * NumberLiteral(node, parent) {},\r\n * CallExpression(node, parent) {},\r\n * };\r\n *\r\n * 但是,也存在在“退出”时调用事物的可能性。想象一下我们之前列表形式的树结构:\r\n *\r\n * - Program\r\n * - CallExpression\r\n * - NumberLiteral\r\n * - CallExpression\r\n * - NumberLiteral\r\n * - NumberLiteral\r\n *\r\n * 当我们向下遍历时,我们将到达有死胡同的分支。当我们完成树的每个分支时就“退出”它。\r\n * \r\n * 所以顺着树往下走时,我们可以“进入”每个节点,而当往上走时就“退出”。\r\n *\r\n * -> Program (enter)\r\n * -> CallExpression (enter)\r\n * -> Number Literal (enter)\r\n * <- Number Literal (exit)\r\n * -> Call Expression (enter)\r\n * -> Number Literal (enter)\r\n * <- Number Literal (exit)\r\n * -> Number Literal (enter)\r\n * <- Number Literal (exit)\r\n * <- CallExpression (exit)\r\n * <- CallExpression (exit)\r\n * <- Program (exit)\r\n *\r\n * 为了支持这点,我们的“访问者”的最终形式应该是这样:\r\n *\r\n * var visitor = {\r\n * NumberLiteral: {\r\n * enter(node, parent) {},\r\n * exit(node, parent) {},\r\n * }\r\n * };\r\n */\r\n\r\n/**\r\n * 代码生成\r\n * ---------------\r\n *\r\n * 编译器的最后一步是代码生成。有时候编译器会做一些和转换重叠的事情,但是大部分情况下,\r\n * 代码生成只是意味着将我们的 AST 取出并进行字符串化。\r\n *\r\n * 代码生成器有几种不同的工作方式,一些编译器直接将之前的标记重复使用,\r\n * 另一些会创建代码的单独表示,这样他们就可以线性的打印节点,\r\n * 但是据我所知大部分编译器将会使用我们刚刚创建的相同的 AST,这是我们需要重点关注的。\r\n *\r\n * 实际上,我们的代码生成器知道如何“打印” AST 的所有不同节点类型,\r\n * 并且它将递归调用自身来打印嵌套节点,直到所有内容都打印成一长串代码。\r\n */\r\n\r\n/**\r\n * 就是这样!这就是编译器中所有不同的部分。\r\n * 现在这并不是说每个编译器看起来都和我在这里描述的完全一样。\r\n * 编译器有许多不同的用途,它们可能比我说的这些步骤更加的详细。\r\n *\r\n * 但是现在你应该对大多数编译器有了一个大致的了解。\r\n * 现在我已经解释了所有这些,你们都可以编写自己的编译器了,对吧?\r\n * \r\n * 好了开个玩笑,让我来帮助你从这里慢慢编写一个简单的编译器吧!\r\n */\r\n\r\n/**\r\n * ============================================================================\r\n * (/^▽^)/\r\n * THE TOKENIZER!\r\n * ============================================================================\r\n */\r\n\r\n/**\r\n * 我们将从解析的第一阶段开始:词法分析和标记器。\r\n * 我们只要将我们的代码字符串分解成一个标记数组:\r\n *\r\n * (add 2 (subtract 4 2)) => [{ type: \'paren\', value: \'(\' }, ...]\r\n */\r\n\r\n// 我们从接收一个代码输入字符串(input)开始,并且我们还要做两件事...\r\nfunction tokenizer(input) {\r\n \r\n // `current` 变量就像一个指光标一样让我们可以在代码中追踪我们的位置\r\n let current = 0;\r\n \r\n // `tokens` 数组用来存我们的标记\r\n let tokens = [];\r\n \r\n // 首先创建一个`while`循环,在循环里面我们可以将`current`变量增加为我们想要的值\r\n // 做这一步的原因是因为我们想要在一个循环里多次增加`current`变量的值,因为我们的标记的长度不确定\r\n while (current < input.length) {\r\n \r\n // 我们还将在 `input` 中存储 `current` 字符\r\n let char = input[current];\r\n \r\n // 我们要检查的第一件事是一个左括号。 这稍后将用于 `CallExpression` 但现在我们只关心字符。\r\n // 检查是否有一个左括号:\r\n if (char === \'(\') {\r\n \r\n // 如果有,我们会存一个类型为 `paren` 的新标记到数组,并将值设置为一个左括号。\r\n tokens.push({\r\n type: \'paren\',\r\n value: \'(\',\r\n });\r\n \r\n // `current`自增\r\n current++;\r\n \r\n // 然后继续进入下一次循环。\r\n continue;\r\n }\r\n \r\n // 接下去检查右括号, 像上面一样\r\n if (char === \')\') {\r\n tokens.push({\r\n type: \'paren\',\r\n value: \')\',\r\n });\r\n current++;\r\n continue;\r\n }\r\n \r\n // 接下去我们检查空格,这对于我们来说就是为了知道字符的分割,但是并不需要存储为标记。\r\n \r\n // 所以我们来检查是否有空格的存在,如果存在,就继续下一次循环,做除了存储到标记数组之外的其他操作即可\r\n let WHITESPACE = /\\s/;\r\n if (WHITESPACE.test(char)) {\r\n current++;\r\n continue;\r\n }\r\n \r\n // **首先我们从表达式的值中来入手,这里简单举例子就只检查了两种类型的值\r\n // **一种是数字类型,一种是字符串类型\r\n \r\n // 接下去的标记类型是数字. 这和我们之前看到的不太一样\r\n // 因为数字可以是任何数量的字符,我们希望将整个字符序列捕获称为一个标记\r\n //\r\n // (add 123 456)\r\n // ^^^ ^^^\r\n // 只有两个单独的标记\r\n //\r\n // 因此,当我们遇到序列中的第一个数字时,我们就开始了\r\n let NUMBERS = /[0-9]/;\r\n if (NUMBERS.test(char)) {\r\n \r\n // 我们将创建一个`value`字符串,并把字符推送给他\r\n let value = \'\';\r\n \r\n // 然后我们将遍历序列中的每个字符,直到遇到一个不是数字的字符\r\n // 将每个作为数字的字符推到我们的 `value` 并随着我们去增加 `current`\r\n // 这样我们就能拿到一个完整的数字字符串,例如上面的 123 和 456,而不是单独的 1 2 3 4 5 6\r\n while (NUMBERS.test(char)) {\r\n value += char;\r\n char = input[++current];\r\n }\r\n \r\n // 接着我们把数字放到标记数组中,用数字类型来描述区分它\r\n tokens.push({ type: \'number\', value });\r\n \r\n // 继续外层的下一次循环\r\n continue;\r\n }\r\n \r\n // 我们还将在我们的语言中添加对字符串的支持,可以是任何\r\n // 用双引号 (\") 括起来的文本。\r\n //\r\n // (concat \"foo\" \"bar\")\r\n // ^^^ ^^^ 字符串标记\r\n //\r\n // 从检查开头的双引号开始:\r\n if (char === \'\"\') {\r\n // 保留一个 `value` 变量来构建我们的字符串标记。\r\n let value = \'\';\r\n \r\n // 我们将跳过编辑中开头的双引号\r\n char = input[++current];\r\n \r\n // 然后我们将遍历每个字符,直到我们到达另一个双引号\r\n while (char !== \'\"\') {\r\n value += char;\r\n char = input[++current];\r\n }\r\n \r\n // 跳过相对应闭合的双引号.\r\n char = input[++current];\r\n \r\n // 把我们的字符串标记添加到标记数组中\r\n tokens.push({ type: \'string\', value });\r\n \r\n continue;\r\n }\r\n \r\n // 最后一种标记的类型是名称。这是一个字母序列而不是数字,这是我们 lisp 语法中的函数名称\r\n //\r\n // (add 2 4)\r\n // ^^^\r\n // 名称标记\r\n //\r\n let LETTERS = /[a-z]/i;\r\n if (LETTERS.test(char)) {\r\n let value = \'\';\r\n \r\n // 同样,我们遍历所有,并将它们完整的存到`value`变量中\r\n while (LETTERS.test(char)) {\r\n value += char;\r\n char = input[++current];\r\n }\r\n \r\n // 并把这种名称类型的标记存到标记数组中,继续循环\r\n tokens.push({ type: \'name\', value });\r\n \r\n continue;\r\n }\r\n \r\n // 如果到最后我们还没有匹配到一个字符,那就抛出一个异常错误并完全退出\r\n throw new TypeError(\'I dont know what this character is: \' + char);\r\n }\r\n \r\n // 在标记器执行结束后,直接返回标记数组即可\r\n return tokens;\r\n}\r\n\r\n/**\r\n * ============================================================================\r\n * ヽ/❀o ل͜ o\\ノ\r\n * THE PARSER!!!\r\n * ============================================================================\r\n */\r\n\r\n/**\r\n * 对于解析器来说,我们要把我们的标记数组变成一个 AST:\r\n *\r\n * [{ type: \'paren\', value: \'(\' }, ...] => { type: \'Program\', body: [...] }\r\n */\r\n\r\n// 因此我们先定义一个`parser`函数用来接收我们的`tokens`数组\r\nfunction parser(tokens) {\r\n\r\n // 同样的我们保持一个`current`变量当作光标来供我们使用\r\n let current = 0;\r\n\r\n // 但是这一次,我们我们将使用递归来替代之前使用的 whie 循环\r\n // 先定义一个`walk`函数\r\n function walk() {\r\n\r\n // 在 walk 函数里面,我们首先获取`current`标记\r\n let token = tokens[current];\r\n\r\n // 我们将把每种类型的标记分成不同的代码路径\r\n // 先从`number`类型的标记开始.\r\n\r\n // 首先先检查一下是否有`number`标签.\r\n if (token.type === \'number\') {\r\n\r\n // 如果找到一个,就增加`current`.\r\n current++;\r\n\r\n // 然后我们就能返回一个新的叫做`NumberLiteral`的 AST 节点,并赋值\r\n return {\r\n type: \'NumberLiteral\',\r\n value: token.value,\r\n };\r\n }\r\n\r\n // 对于字符串来说,也是和上面数字一样的操作。新增一个`StringLiteral`节点\r\n if (token.type === \'string\') {\r\n current++;\r\n\r\n return {\r\n type: \'StringLiteral\',\r\n value: token.value,\r\n };\r\n }\r\n\r\n // 接下去我们要寻找调用的表达式(CallExpressions)\r\n // 从找到第一个左括号开始\r\n if (\r\n token.type === \'paren\' &&\r\n token.value === \'(\'\r\n ) {\r\n\r\n // 我们将增加`current`来跳过这个插入语,因为在 AST 树中我们并不关心这个括号\r\n token = tokens[++current];\r\n\r\n // 我们创建一个类型为“CallExpression”的基本节点,并把当前标记的值设置到 name 字段上\r\n // 因为左括号的下一个标记就是这个函数的名字\r\n let node = {\r\n type: \'CallExpression\',\r\n name: token.value,\r\n params: [],\r\n };\r\n\r\n // 继续增加`current`来跳过这个名称标记\r\n token = tokens[++current];\r\n\r\n // 现在我们要遍历每一个标记,找出其中是`CallExpression`的`params`,直到遇到右括号\r\n\r\n // 现在这就是递归发挥作用的地方。我们将使用递归来解决事情而不是尝试去解析可能无限嵌套的节点集\r\n //\r\n // 为了解释这个,我们先用 lisp 代码做演示\r\n // \r\n // 可以看到`add` 的参数是一个数字,还有一个包含自己数字的嵌套`CallExpression`\r\n // (add 2 (subtract 4 2))\r\n //\r\n // 你也将会注意到,在我们的标记数组里,我们有很多右括号\r\n //\r\n // [\r\n // { type: \'paren\', value: \'(\' },\r\n // { type: \'name\', value: \'add\' },\r\n // { type: \'number\', value: \'2\' },\r\n // { type: \'paren\', value: \'(\' },\r\n // { type: \'name\', value: \'subtract\' },\r\n // { type: \'number\', value: \'4\' },\r\n // { type: \'number\', value: \'2\' },\r\n // { type: \'paren\', value: \')\' }, <<< 右括号\r\n // { type: \'paren\', value: \')\' }, <<< 右括号\r\n // ]\r\n //\r\n // 我们将依赖嵌套的`walk`函数来增加我们的`current`变量来超过任何嵌套的`CallExpression`\r\n // 所以我们创建一个`while`循环持续到遇到一个`type`是\'paren\'并且`value`是右括号的标记\r\n while (\r\n (token.type !== \'paren\') ||\r\n (token.type === \'paren\' && token.value !== \')\')\r\n ) {\r\n // 我们调用`walk`方法,这个方法会返回一个`node`节点\r\n // 我们把这个节点存到我们的`node.params`中去\r\n node.params.push(walk());\r\n token = tokens[current];\r\n }\r\n\r\n // 我们最后一次增加`current`变量来跳过右括号\r\n current++;\r\n\r\n // 返回node节点\r\n return node;\r\n }\r\n\r\n // 同样的,如果我们到这里都没有辨认出标记的类型,就抛出一个异常\r\n throw new TypeError(token.type);\r\n }\r\n\r\n // 现在,我们将要创建我们的 AST 树,他有一个根节点叫做`Program`节点\r\n let ast = {\r\n type: \'Program\',\r\n body: [],\r\n };\r\n\r\n // 并且启动我们的`walk`函数,把节点推到我们的`ast.body`数组\r\n // 我们之所以在一个循环里做这些事是因为我们的程序可以有一个接一个的`CallExpression`而不是嵌套的\r\n //\r\n // (add 2 2)\r\n // (subtract 4 2)\r\n //\r\n while (current < tokens.length) {\r\n ast.body.push(walk());\r\n }\r\n\r\n // 最后返回我们得到的 AST 结构\r\n return ast;\r\n}\r\n\r\n/**\r\n * ============================================================================\r\n * ⌒(❀>◞౪◟<❀)⌒\r\n * THE TRAVERSER!!!\r\n * ============================================================================\r\n */\r\n\r\n/**\r\n * 现在我们已经有了一个 AST 树。然后我们想要通过访问者(visitor)来访问不同的节点。\r\n * 无论什么时候只要遇到能匹配类型的节点,就需要调用访问者的方法,像这样:\r\n *\r\n * traverse(ast, {\r\n * Program: {\r\n * enter(node, parent) {\r\n * // ...\r\n * },\r\n * exit(node, parent) {\r\n * // ...\r\n * },\r\n * },\r\n *\r\n * CallExpression: {\r\n * enter(node, parent) {\r\n * // ...\r\n * },\r\n * exit(node, parent) {\r\n * // ...\r\n * },\r\n * },\r\n *\r\n * NumberLiteral: {\r\n * enter(node, parent) {\r\n * // ...\r\n * },\r\n * exit(node, parent) {\r\n * // ...\r\n * },\r\n * },\r\n * });\r\n */\r\n\r\n// 所以我们定义了一个接收一个 AST 和一个访问者的遍历器函数\r\n// 在里面我们还将定义两个函数...\r\nfunction traverser(ast, visitor) {\r\n\r\n // 一个`traverseArray`函数,一个`traverseNode`函数\r\n // traverseArray 函数来迭代数组,并且调用 traverseNode 函数\r\n function traverseArray(array, parent) {\r\n array.forEach(child => {\r\n traverseNode(child, parent);\r\n });\r\n }\r\n\r\n // traverseNode 函数将接受两个参数:node 节点和他的 parent 节点\r\n // 这样他就可以将两者都传递给我们的访问者方法(visitor)\r\n function traverseNode(node, parent) {\r\n\r\n // 我们首先从匹配`type`开始,来检测访问者方法是否存在。访问者方法就是(enter 和 exit)\r\n let methods = visitor[node.type];\r\n\r\n // 如果这个节点类型有`enter`方法,我们将调用这个函数,并且传入当前节点和他的父节点\r\n if (methods && methods.enter) {\r\n methods.enter(node, parent);\r\n }\r\n\r\n // 接下去我们将按当前节点类型来进行拆分,以便于对子节点数组进行遍历,处理到每一个子节点\r\n switch (node.type) {\r\n\r\n // 首先从最高的`Program`层开始。因为 Program 节点有一个名叫 body 的属性,里面包含了节点数组\r\n // 我们调用`traverseArray`来向下遍历它们\r\n //\r\n // 请记住,`traverseArray`会依次调用`traverseNode`,所以这棵树将会被递归遍历\r\n case \'Program\':\r\n traverseArray(node.body, node);\r\n break;\r\n\r\n // 接下去我们对`CallExpression`做相同的事情,然后遍历`params`属性\r\n case \'CallExpression\':\r\n traverseArray(node.params, node);\r\n break;\r\n\r\n // 对于`NumberLiteral`和`StringLiteral`的情况,由于没有子节点,所以直接 break 即可\r\n case \'NumberLiteral\':\r\n case \'StringLiteral\':\r\n break;\r\n\r\n // 接着,如果我们没有匹配到上面的节点类型,就抛出一个异常错误\r\n default:\r\n throw new TypeError(node.type);\r\n }\r\n\r\n // 如果这个节点类型里面有一个`exit`方法,我们就调用它,并且传入当前节点和他的父节点\r\n if (methods && methods.exit) {\r\n methods.exit(node, parent);\r\n }\r\n }\r\n\r\n // 代码最后,我们调用`traverseNode`来启动遍历,传入之前的 AST 树,由于 AST 树最开始\r\n // 的点没有父节点,所以我们直接传入 null 就好\r\n traverseNode(ast, null);\r\n}\r\n\r\n/**\r\n * ============================================================================\r\n * ⁽(◍˃̵͈̑ᴗ˂̵͈̑)⁽\r\n * THE TRANSFORMER!!!\r\n * ============================================================================\r\n */\r\n\r\n/**\r\n * 下一步就是转换器了。我们的转换器将获取我们构建的 AST 并将其传递给我们的遍历器函数,并创建一个新的 AST:\r\n *\r\n * ----------------------------------------------------------------------------\r\n * Original AST | Transformed AST\r\n * ----------------------------------------------------------------------------\r\n * { | {\r\n * type: \'Program\', | type: \'Program\',\r\n * body: [{ | body: [{\r\n * type: \'CallExpression\', | type: \'ExpressionStatement\',\r\n * name: \'add\', | expression: {\r\n * params: [{ | type: \'CallExpression\',\r\n * type: \'NumberLiteral\', | callee: {\r\n * value: \'2\' | type: \'Identifier\',\r\n * }, { | name: \'add\'\r\n * type: \'CallExpression\', | },\r\n * name: \'subtract\', | arguments: [{\r\n * params: [{ | type: \'NumberLiteral\',\r\n * type: \'NumberLiteral\', | value: \'2\'\r\n * value: \'4\' | }, {\r\n * }, { | type: \'CallExpression\',\r\n * type: \'NumberLiteral\', | callee: {\r\n * value: \'2\' | type: \'Identifier\',\r\n * }] | name: \'subtract\'\r\n * }] | },\r\n * }] | arguments: [{\r\n * } | type: \'NumberLiteral\',\r\n * | value: \'4\'\r\n * ---------------------------------- | }, {\r\n * | type: \'NumberLiteral\',\r\n * | value: \'2\'\r\n * | }]\r\n * (sorry the other one is longer.) | }\r\n * | }\r\n * | }]\r\n * | }\r\n * ----------------------------------------------------------------------------\r\n */\r\n\r\n// 这就是我们的转换器函数,他接受一个 lisp 格式的 ast 树。\r\nfunction transformer(ast) {\r\n\r\n // 我们创建一个像之前的 AST 树一样的新的 AST 树,也有一个 program 节点。\r\n let newAst = {\r\n type: \'Program\',\r\n body: [],\r\n };\r\n\r\n // 接下去我们要使用一些 hack 的小技巧\r\n // 首先我们在父节点上使用`context`的属性,这样我们就可以把节点放到其父节点的`context`上\r\n // 通常你会有比这个更好的抽象方法,但是为了我们的目标能实现,这个方法相对简单些\r\n //\r\n // 需要注意的是,这个上下文(context)属性只是用来对比新旧 ast 的\r\n ast._context = newAst.body;\r\n\r\n // 在这里调用遍历器函数并传入我们的旧的 AST 树和访问者方法\r\n traverser(ast, {\r\n\r\n // 第一个访问者方法是 NumberLiteral \r\n NumberLiteral: {\r\n // 我们将在 enter 方法里面访问他们\r\n enter(node, parent) {\r\n // 我们将创建一个也叫做`NumberLiteral`的新节点,并放到父节点的上下文中去\r\n parent._context.push({\r\n type: \'NumberLiteral\',\r\n value: node.value,\r\n });\r\n },\r\n },\r\n\r\n // 接下去是 `StringLiteral`\r\n StringLiteral: {\r\n enter(node, parent) {\r\n parent._context.push({\r\n type: \'StringLiteral\',\r\n value: node.value,\r\n });\r\n },\r\n },\r\n\r\n // 然后, `CallExpression`,这里相对比较复杂一点\r\n CallExpression: {\r\n enter(node, parent) {\r\n\r\n // 首先我们创建一个叫`CallExpression`的节点,它带有表示嵌套的标识符“Identifier”\r\n let expression = {\r\n type: \'CallExpression\',\r\n callee: {\r\n type: \'Identifier\',\r\n name: node.name,\r\n },\r\n arguments: [],\r\n };\r\n\r\n // 接下去我们在原有的`CallExpression`节点(node)上定义一个新的上下文\r\n // 他将引用 `expression`的参数(arguments),这样就可以利用这个数组了\r\n node._context = expression.arguments;\r\n\r\n // 之后我们将检查父节点是否是`CallExpression`类型\r\n // 如果不是的话\r\n if (parent.type !== \'CallExpression\') {\r\n\r\n // 我们将用`ExpressionStatement`来包裹`CallExpression`节点\r\n // 我们这样做的原因是因为单独存在的`CallExpression`在 js 中也可以被当作是声明语句\r\n expression = {\r\n type: \'ExpressionStatement\',\r\n expression: expression,\r\n };\r\n }\r\n\r\n // 最后我们把我们的`CallExpression`(可能是被包起来的)放到父节点的上下文中去\r\n parent._context.push(expression);\r\n },\r\n }\r\n });\r\n\r\n // 在转换器方法的最后,我们就能返回我们刚创建的新的 AST 树了\r\n return newAst;\r\n}\r\n\r\n/**\r\n * ============================================================================\r\n * ヾ(〃^∇^)ノ♪\r\n * THE CODE GENERATOR!!!!\r\n * ============================================================================\r\n */\r\n\r\n/**\r\n * 现在让我们来到我们最后的一个阶段:代码生成。\r\n * 代码生成器将递归的调用自己来打印树的每一个节点,最后输出一个巨大的字符串。\r\n */\r\n\r\n function codeGenerator(node) {\r\n\r\n // 还是按照节点的类型来进行分解操作\r\n switch (node.type) {\r\n\r\n // 如果我们有`Program`节点,我们将映射`body`中的每个节点\r\n // 并且通过代码生成器来运行他们,用换行符将他们连接起来\r\n case \'Program\':\r\n return node.body.map(codeGenerator)\r\n .join(\'\\n\');\r\n\r\n // 对于`ExpressionStatement`,我们将在嵌套表达式上调用代码生成器,并添加一个分号\r\n case \'ExpressionStatement\':\r\n return (\r\n codeGenerator(node.expression) +\r\n \';\' // << 这是因为保持代码的统一性(用正确的方式编写代码)\r\n );\r\n\r\n // 对于`CallExpression`我们将打印`callee`, 新增一个左括号\r\n // 然后映射每一个`arguments`数组的节点,并用代码生成器执行,每一个节点运行完之后加上逗号\r\n // 最后增加一个右括号\r\n case \'CallExpression\':\r\n return (\r\n codeGenerator(node.callee) +\r\n \'(\' +\r\n node.arguments.map(codeGenerator)\r\n .join(\', \') +\r\n \')\'\r\n );\r\n\r\n // 对于`Identifier`直接返回`node`的名字就好.\r\n case \'Identifier\':\r\n return node.name;\r\n\r\n // 对于`NumberLiteral`直接返回`node`的值就好.\r\n case \'NumberLiteral\':\r\n return node.value;\r\n\r\n // 对于`StringLiteral`,在`node`的值的周围添加双引号.\r\n case \'StringLiteral\':\r\n return \'\"\' + node.value + \'\"\';\r\n\r\n // 如果没有匹配到节点的类型,就抛出异常\r\n default:\r\n throw new TypeError(node.type);\r\n }\r\n}\r\n\r\n/**\r\n * ============================================================================\r\n * (۶* ‘ヮ’)۶”\r\n * !!!!!!!!THE COMPILER!!!!!!!!\r\n * ============================================================================\r\n */\r\n\r\n/**\r\n * 最后! 我们创建了 `compiler` 函数。 在这里,我们将把管道的每个部分连接在一起。\r\n *\r\n * 1. input => tokenizer => tokens\r\n * 2. tokens => parser => ast\r\n * 3. ast => transformer => newAst\r\n * 4. newAst => generator => output\r\n */\r\n\r\nfunction compiler(input) {\r\n let tokens = tokenizer(input);\r\n let ast = parser(tokens);\r\n let newAst = transformer(ast);\r\n let output = codeGenerator(newAst);\r\n\r\n // 简单的返回输出\r\n return output;\r\n}\r\n\r\n/**\r\n * ============================================================================\r\n * (๑˃̵ᴗ˂̵)و\r\n * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!YOU MADE IT!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\r\n * ============================================================================\r\n */\r\n\r\n// 这里把所有的东西都导出...\r\nmodule.exports = {\r\n tokenizer,\r\n parser,\r\n traverser,\r\n transformer,\r\n codeGenerator,\r\n compiler,\r\n};', 1); +INSERT INTO `onefile` VALUES (12, 'pico', 'JavaScript', '200 行实现的面部识别库', 12, 'https://github.com/nenadmarkus/picojs', 1100, '/* This library is released under the MIT license, see https://github.com/nenadmarkus/picojs */\r\npico = {}\r\n\r\npico.unpack_cascade = function(bytes)\r\n{\r\n //\r\n const dview = new DataView(new ArrayBuffer(4));\r\n /*\r\n we skip the first 8 bytes of the cascade file\r\n (cascade version number and some data used during the learning process)\r\n */\r\n let p = 8;\r\n /*\r\n read the depth (size) of each tree first: a 32-bit signed integer\r\n */\r\n dview.setUint8(0, bytes[p+0]), dview.setUint8(1, bytes[p+1]), dview.setUint8(2, bytes[p+2]), dview.setUint8(3, bytes[p+3]);\r\n const tdepth = dview.getInt32(0, true);\r\n p = p + 4\r\n /*\r\n next, read the number of trees in the cascade: another 32-bit signed integer\r\n */\r\n dview.setUint8(0, bytes[p+0]), dview.setUint8(1, bytes[p+1]), dview.setUint8(2, bytes[p+2]), dview.setUint8(3, bytes[p+3]);\r\n const ntrees = dview.getInt32(0, true);\r\n p = p + 4\r\n /*\r\n read the actual trees and cascade thresholds\r\n */\r\n const tcodes_ls = [];\r\n const tpreds_ls = [];\r\n const thresh_ls = [];\r\n for(let t=0; t> 0; // \'>>0\' transforms this number to int\r\n\r\n for(let i=0; i> 8\' here to perform an integer division: this seems important for performance\r\n idx = 2*idx + (pixels[((r+tcodes[root + 4*idx + 0]*s) >> 8)*ldim+((c+tcodes[root + 4*idx + 1]*s) >> 8)]<=pixels[((r+tcodes[root + 4*idx + 2]*s) >> 8)*ldim+((c+tcodes[root + 4*idx + 3]*s) >> 8)]);\r\n\r\n o = o + tpreds[pow2tdepth*i + idx-pow2tdepth];\r\n\r\n if(o<=thresh[i])\r\n return -1;\r\n\r\n root += 4*pow2tdepth;\r\n }\r\n return o - thresh[ntrees-1];\r\n }\r\n /*\r\n we\'re done\r\n */\r\n return classify_region;\r\n}\r\n\r\npico.run_cascade = function(image, classify_region, params)\r\n{\r\n const pixels = image.pixels;\r\n const nrows = image.nrows;\r\n const ncols = image.ncols;\r\n const ldim = image.ldim;\r\n\r\n const shiftfactor = params.shiftfactor;\r\n const minsize = params.minsize;\r\n const maxsize = params.maxsize;\r\n const scalefactor = params.scalefactor;\r\n\r\n let scale = minsize;\r\n const detections = [];\r\n\r\n while(scale<=maxsize)\r\n {\r\n const step = Math.max(shiftfactor*scale, 1) >> 0; // \'>>0\' transforms this number to int\r\n const offset = (scale/2 + 1) >> 0;\r\n\r\n for(let r=offset; r<=nrows-offset; r+=step)\r\n for(let c=offset; c<=ncols-offset; c+=step)\r\n {\r\n const q = classify_region(r, c, scale, pixels, ldim);\r\n if (q > 0.0)\r\n detections.push([r, c, scale, q]);\r\n }\r\n \r\n scale = scale*scalefactor;\r\n }\r\n\r\n return detections;\r\n}\r\n\r\npico.cluster_detections = function(dets, iouthreshold)\r\n{\r\n /*\r\n sort detections by their score\r\n */\r\n dets = dets.sort(function(a, b) {\r\n return b[3] - a[3];\r\n });\r\n /*\r\n this helper function calculates the intersection over union for two detections\r\n */\r\n function calculate_iou(det1, det2)\r\n {\r\n // unpack the position and size of each detection\r\n const r1=det1[0], c1=det1[1], s1=det1[2];\r\n const r2=det2[0], c2=det2[1], s2=det2[2];\r\n // calculate detection overlap in each dimension\r\n const overr = Math.max(0, Math.min(r1+s1/2, r2+s2/2) - Math.max(r1-s1/2, r2-s2/2));\r\n const overc = Math.max(0, Math.min(c1+s1/2, c2+s2/2) - Math.max(c1-s1/2, c2-s2/2));\r\n // calculate and return IoU\r\n return overr*overc/(s1*s1+s2*s2-overr*overc);\r\n }\r\n /*\r\n do clustering through non-maximum suppression\r\n */\r\n const assignments = new Array(dets.length).fill(0);\r\n const clusters = [];\r\n for(let i=0; iiouthreshold)\r\n {\r\n assignments[j] = 1;\r\n r = r + dets[j][0];\r\n c = c + dets[j][1];\r\n s = s + dets[j][2];\r\n q = q + dets[j][3];\r\n n = n + 1;\r\n }\r\n // make a cluster representative\r\n clusters.push([r/n, c/n, s/n, q]);\r\n }\r\n }\r\n\r\n return clusters;\r\n}\r\n\r\npico.instantiate_detection_memory = function(size)\r\n{\r\n /*\r\n initialize a circular buffer of `size` elements\r\n */\r\n let n = 0;\r\n const memory = [];\r\n for(let i=0; itextElements($text);\r\n\r\n # convert to markup\r\n $markup = $this->elements($Elements);\r\n\r\n # trim line breaks\r\n $markup = trim($markup, \"\\n\");\r\n\r\n return $markup;\r\n }\r\n\r\n protected function textElements($text)\r\n {\r\n # make sure no definitions are set\r\n $this->DefinitionData = array();\r\n\r\n # standardize line breaks\r\n $text = str_replace(array(\"\\r\\n\", \"\\r\"), \"\\n\", $text);\r\n\r\n # remove surrounding line breaks\r\n $text = trim($text, \"\\n\");\r\n\r\n # split text into lines\r\n $lines = explode(\"\\n\", $text);\r\n\r\n # iterate through lines to identify blocks\r\n return $this->linesElements($lines);\r\n }\r\n\r\n #\r\n # Setters\r\n #\r\n\r\n function setBreaksEnabled($breaksEnabled)\r\n {\r\n $this->breaksEnabled = $breaksEnabled;\r\n\r\n return $this;\r\n }\r\n\r\n protected $breaksEnabled;\r\n\r\n function setMarkupEscaped($markupEscaped)\r\n {\r\n $this->markupEscaped = $markupEscaped;\r\n\r\n return $this;\r\n }\r\n\r\n protected $markupEscaped;\r\n\r\n function setUrlsLinked($urlsLinked)\r\n {\r\n $this->urlsLinked = $urlsLinked;\r\n\r\n return $this;\r\n }\r\n\r\n protected $urlsLinked = true;\r\n\r\n function setSafeMode($safeMode)\r\n {\r\n $this->safeMode = (bool) $safeMode;\r\n\r\n return $this;\r\n }\r\n\r\n protected $safeMode;\r\n\r\n function setStrictMode($strictMode)\r\n {\r\n $this->strictMode = (bool) $strictMode;\r\n\r\n return $this;\r\n }\r\n\r\n protected $strictMode;\r\n\r\n protected $safeLinksWhitelist = array(\r\n \'http://\',\r\n \'https://\',\r\n \'ftp://\',\r\n \'ftps://\',\r\n \'mailto:\',\r\n \'tel:\',\r\n \'data:image/png;base64,\',\r\n \'data:image/gif;base64,\',\r\n \'data:image/jpeg;base64,\',\r\n \'irc:\',\r\n \'ircs:\',\r\n \'git:\',\r\n \'ssh:\',\r\n \'news:\',\r\n \'steam:\',\r\n );\r\n\r\n #\r\n # Lines\r\n #\r\n\r\n protected $BlockTypes = array(\r\n \'#\' => array(\'Header\'),\r\n \'*\' => array(\'Rule\', \'List\'),\r\n \'+\' => array(\'List\'),\r\n \'-\' => array(\'SetextHeader\', \'Table\', \'Rule\', \'List\'),\r\n \'0\' => array(\'List\'),\r\n \'1\' => array(\'List\'),\r\n \'2\' => array(\'List\'),\r\n \'3\' => array(\'List\'),\r\n \'4\' => array(\'List\'),\r\n \'5\' => array(\'List\'),\r\n \'6\' => array(\'List\'),\r\n \'7\' => array(\'List\'),\r\n \'8\' => array(\'List\'),\r\n \'9\' => array(\'List\'),\r\n \':\' => array(\'Table\'),\r\n \'<\' => array(\'Comment\', \'Markup\'),\r\n \'=\' => array(\'SetextHeader\'),\r\n \'>\' => array(\'Quote\'),\r\n \'[\' => array(\'Reference\'),\r\n \'_\' => array(\'Rule\'),\r\n \'`\' => array(\'FencedCode\'),\r\n \'|\' => array(\'Table\'),\r\n \'~\' => array(\'FencedCode\'),\r\n );\r\n\r\n # ~\r\n\r\n protected $unmarkedBlockTypes = array(\r\n \'Code\',\r\n );\r\n\r\n #\r\n # Blocks\r\n #\r\n\r\n protected function lines(array $lines)\r\n {\r\n return $this->elements($this->linesElements($lines));\r\n }\r\n\r\n protected function linesElements(array $lines)\r\n {\r\n $Elements = array();\r\n $CurrentBlock = null;\r\n\r\n foreach ($lines as $line)\r\n {\r\n if (chop($line) === \'\')\r\n {\r\n if (isset($CurrentBlock))\r\n {\r\n $CurrentBlock[\'interrupted\'] = (isset($CurrentBlock[\'interrupted\'])\r\n ? $CurrentBlock[\'interrupted\'] + 1 : 1\r\n );\r\n }\r\n\r\n continue;\r\n }\r\n\r\n while (($beforeTab = strstr($line, \"\\t\", true)) !== false)\r\n {\r\n $shortage = 4 - mb_strlen($beforeTab, \'utf-8\') % 4;\r\n\r\n $line = $beforeTab\r\n . str_repeat(\' \', $shortage)\r\n . substr($line, strlen($beforeTab) + 1)\r\n ;\r\n }\r\n\r\n $indent = strspn($line, \' \');\r\n\r\n $text = $indent > 0 ? substr($line, $indent) : $line;\r\n\r\n # ~\r\n\r\n $Line = array(\'body\' => $line, \'indent\' => $indent, \'text\' => $text);\r\n\r\n # ~\r\n\r\n if (isset($CurrentBlock[\'continuable\']))\r\n {\r\n $methodName = \'block\' . $CurrentBlock[\'type\'] . \'Continue\';\r\n $Block = $this->$methodName($Line, $CurrentBlock);\r\n\r\n if (isset($Block))\r\n {\r\n $CurrentBlock = $Block;\r\n\r\n continue;\r\n }\r\n else\r\n {\r\n if ($this->isBlockCompletable($CurrentBlock[\'type\']))\r\n {\r\n $methodName = \'block\' . $CurrentBlock[\'type\'] . \'Complete\';\r\n $CurrentBlock = $this->$methodName($CurrentBlock);\r\n }\r\n }\r\n }\r\n\r\n # ~\r\n\r\n $marker = $text[0];\r\n\r\n # ~\r\n\r\n $blockTypes = $this->unmarkedBlockTypes;\r\n\r\n if (isset($this->BlockTypes[$marker]))\r\n {\r\n foreach ($this->BlockTypes[$marker] as $blockType)\r\n {\r\n $blockTypes []= $blockType;\r\n }\r\n }\r\n\r\n #\r\n # ~\r\n\r\n foreach ($blockTypes as $blockType)\r\n {\r\n $Block = $this->{\"block$blockType\"}($Line, $CurrentBlock);\r\n\r\n if (isset($Block))\r\n {\r\n $Block[\'type\'] = $blockType;\r\n\r\n if ( ! isset($Block[\'identified\']))\r\n {\r\n if (isset($CurrentBlock))\r\n {\r\n $Elements[] = $this->extractElement($CurrentBlock);\r\n }\r\n\r\n $Block[\'identified\'] = true;\r\n }\r\n\r\n if ($this->isBlockContinuable($blockType))\r\n {\r\n $Block[\'continuable\'] = true;\r\n }\r\n\r\n $CurrentBlock = $Block;\r\n\r\n continue 2;\r\n }\r\n }\r\n\r\n # ~\r\n\r\n if (isset($CurrentBlock) and $CurrentBlock[\'type\'] === \'Paragraph\')\r\n {\r\n $Block = $this->paragraphContinue($Line, $CurrentBlock);\r\n }\r\n\r\n if (isset($Block))\r\n {\r\n $CurrentBlock = $Block;\r\n }\r\n else\r\n {\r\n if (isset($CurrentBlock))\r\n {\r\n $Elements[] = $this->extractElement($CurrentBlock);\r\n }\r\n\r\n $CurrentBlock = $this->paragraph($Line);\r\n\r\n $CurrentBlock[\'identified\'] = true;\r\n }\r\n }\r\n\r\n # ~\r\n\r\n if (isset($CurrentBlock[\'continuable\']) and $this->isBlockCompletable($CurrentBlock[\'type\']))\r\n {\r\n $methodName = \'block\' . $CurrentBlock[\'type\'] . \'Complete\';\r\n $CurrentBlock = $this->$methodName($CurrentBlock);\r\n }\r\n\r\n # ~\r\n\r\n if (isset($CurrentBlock))\r\n {\r\n $Elements[] = $this->extractElement($CurrentBlock);\r\n }\r\n\r\n # ~\r\n\r\n return $Elements;\r\n }\r\n\r\n protected function extractElement(array $Component)\r\n {\r\n if ( ! isset($Component[\'element\']))\r\n {\r\n if (isset($Component[\'markup\']))\r\n {\r\n $Component[\'element\'] = array(\'rawHtml\' => $Component[\'markup\']);\r\n }\r\n elseif (isset($Component[\'hidden\']))\r\n {\r\n $Component[\'element\'] = array();\r\n }\r\n }\r\n\r\n return $Component[\'element\'];\r\n }\r\n\r\n protected function isBlockContinuable($Type)\r\n {\r\n return method_exists($this, \'block\' . $Type . \'Continue\');\r\n }\r\n\r\n protected function isBlockCompletable($Type)\r\n {\r\n return method_exists($this, \'block\' . $Type . \'Complete\');\r\n }\r\n\r\n #\r\n # Code\r\n\r\n protected function blockCode($Line, $Block = null)\r\n {\r\n if (isset($Block) and $Block[\'type\'] === \'Paragraph\' and ! isset($Block[\'interrupted\']))\r\n {\r\n return;\r\n }\r\n\r\n if ($Line[\'indent\'] >= 4)\r\n {\r\n $text = substr($Line[\'body\'], 4);\r\n\r\n $Block = array(\r\n \'element\' => array(\r\n \'name\' => \'pre\',\r\n \'element\' => array(\r\n \'name\' => \'code\',\r\n \'text\' => $text,\r\n ),\r\n ),\r\n );\r\n\r\n return $Block;\r\n }\r\n }\r\n\r\n protected function blockCodeContinue($Line, $Block)\r\n {\r\n if ($Line[\'indent\'] >= 4)\r\n {\r\n if (isset($Block[\'interrupted\']))\r\n {\r\n $Block[\'element\'][\'element\'][\'text\'] .= str_repeat(\"\\n\", $Block[\'interrupted\']);\r\n\r\n unset($Block[\'interrupted\']);\r\n }\r\n\r\n $Block[\'element\'][\'element\'][\'text\'] .= \"\\n\";\r\n\r\n $text = substr($Line[\'body\'], 4);\r\n\r\n $Block[\'element\'][\'element\'][\'text\'] .= $text;\r\n\r\n return $Block;\r\n }\r\n }\r\n\r\n protected function blockCodeComplete($Block)\r\n {\r\n return $Block;\r\n }\r\n\r\n #\r\n # Comment\r\n\r\n protected function blockComment($Line)\r\n {\r\n if ($this->markupEscaped or $this->safeMode)\r\n {\r\n return;\r\n }\r\n\r\n if (strpos($Line[\'text\'], \'\') !== false)\r\n {\r\n $Block[\'closed\'] = true;\r\n }\r\n\r\n return $Block;\r\n }\r\n }\r\n\r\n protected function blockCommentContinue($Line, array $Block)\r\n {\r\n if (isset($Block[\'closed\']))\r\n {\r\n return;\r\n }\r\n\r\n $Block[\'element\'][\'rawHtml\'] .= \"\\n\" . $Line[\'body\'];\r\n\r\n if (strpos($Line[\'text\'], \'-->\') !== false)\r\n {\r\n $Block[\'closed\'] = true;\r\n }\r\n\r\n return $Block;\r\n }\r\n\r\n #\r\n # Fenced Code\r\n\r\n protected function blockFencedCode($Line)\r\n {\r\n $marker = $Line[\'text\'][0];\r\n\r\n $openerLength = strspn($Line[\'text\'], $marker);\r\n\r\n if ($openerLength < 3)\r\n {\r\n return;\r\n }\r\n\r\n $infostring = trim(substr($Line[\'text\'], $openerLength), \"\\t \");\r\n\r\n if (strpos($infostring, \'`\') !== false)\r\n {\r\n return;\r\n }\r\n\r\n $Element = array(\r\n \'name\' => \'code\',\r\n \'text\' => \'\',\r\n );\r\n\r\n if ($infostring !== \'\')\r\n {\r\n /**\r\n * https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes\r\n * Every HTML element may have a class attribute specified.\r\n * The attribute, if specified, must have a value that is a set\r\n * of space-separated tokens representing the various classes\r\n * that the element belongs to.\r\n * [...]\r\n * The space characters, for the purposes of this specification,\r\n * are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab),\r\n * U+000A LINE FEED (LF), U+000C FORM FEED (FF), and\r\n * U+000D CARRIAGE RETURN (CR).\r\n */\r\n $language = substr($infostring, 0, strcspn($infostring, \" \\t\\n\\f\\r\"));\r\n\r\n $Element[\'attributes\'] = array(\'class\' => \"language-$language\");\r\n }\r\n\r\n $Block = array(\r\n \'char\' => $marker,\r\n \'openerLength\' => $openerLength,\r\n \'element\' => array(\r\n \'name\' => \'pre\',\r\n \'element\' => $Element,\r\n ),\r\n );\r\n\r\n return $Block;\r\n }\r\n\r\n protected function blockFencedCodeContinue($Line, $Block)\r\n {\r\n if (isset($Block[\'complete\']))\r\n {\r\n return;\r\n }\r\n\r\n if (isset($Block[\'interrupted\']))\r\n {\r\n $Block[\'element\'][\'element\'][\'text\'] .= str_repeat(\"\\n\", $Block[\'interrupted\']);\r\n\r\n unset($Block[\'interrupted\']);\r\n }\r\n\r\n if (($len = strspn($Line[\'text\'], $Block[\'char\'])) >= $Block[\'openerLength\']\r\n and chop(substr($Line[\'text\'], $len), \' \') === \'\'\r\n ) {\r\n $Block[\'element\'][\'element\'][\'text\'] = substr($Block[\'element\'][\'element\'][\'text\'], 1);\r\n\r\n $Block[\'complete\'] = true;\r\n\r\n return $Block;\r\n }\r\n\r\n $Block[\'element\'][\'element\'][\'text\'] .= \"\\n\" . $Line[\'body\'];\r\n\r\n return $Block;\r\n }\r\n\r\n protected function blockFencedCodeComplete($Block)\r\n {\r\n return $Block;\r\n }\r\n\r\n #\r\n # Header\r\n\r\n protected function blockHeader($Line)\r\n {\r\n $level = strspn($Line[\'text\'], \'#\');\r\n\r\n if ($level > 6)\r\n {\r\n return;\r\n }\r\n\r\n $text = trim($Line[\'text\'], \'#\');\r\n\r\n if ($this->strictMode and isset($text[0]) and $text[0] !== \' \')\r\n {\r\n return;\r\n }\r\n\r\n $text = trim($text, \' \');\r\n\r\n $Block = array(\r\n \'element\' => array(\r\n \'name\' => \'h\' . $level,\r\n \'handler\' => array(\r\n \'function\' => \'lineElements\',\r\n \'argument\' => $text,\r\n \'destination\' => \'elements\',\r\n )\r\n ),\r\n );\r\n\r\n return $Block;\r\n }\r\n\r\n #\r\n # List\r\n\r\n protected function blockList($Line, array $CurrentBlock = null)\r\n {\r\n list($name, $pattern) = $Line[\'text\'][0] <= \'-\' ? array(\'ul\', \'[*+-]\') : array(\'ol\', \'[0-9]{1,9}+[.\\)]\');\r\n\r\n if (preg_match(\'/^(\'.$pattern.\'([ ]++|$))(.*+)/\', $Line[\'text\'], $matches))\r\n {\r\n $contentIndent = strlen($matches[2]);\r\n\r\n if ($contentIndent >= 5)\r\n {\r\n $contentIndent -= 1;\r\n $matches[1] = substr($matches[1], 0, -$contentIndent);\r\n $matches[3] = str_repeat(\' \', $contentIndent) . $matches[3];\r\n }\r\n elseif ($contentIndent === 0)\r\n {\r\n $matches[1] .= \' \';\r\n }\r\n\r\n $markerWithoutWhitespace = strstr($matches[1], \' \', true);\r\n\r\n $Block = array(\r\n \'indent\' => $Line[\'indent\'],\r\n \'pattern\' => $pattern,\r\n \'data\' => array(\r\n \'type\' => $name,\r\n \'marker\' => $matches[1],\r\n \'markerType\' => ($name === \'ul\' ? $markerWithoutWhitespace : substr($markerWithoutWhitespace, -1)),\r\n ),\r\n \'element\' => array(\r\n \'name\' => $name,\r\n \'elements\' => array(),\r\n ),\r\n );\r\n $Block[\'data\'][\'markerTypeRegex\'] = preg_quote($Block[\'data\'][\'markerType\'], \'/\');\r\n\r\n if ($name === \'ol\')\r\n {\r\n $listStart = ltrim(strstr($matches[1], $Block[\'data\'][\'markerType\'], true), \'0\') ?: \'0\';\r\n\r\n if ($listStart !== \'1\')\r\n {\r\n if (\r\n isset($CurrentBlock)\r\n and $CurrentBlock[\'type\'] === \'Paragraph\'\r\n and ! isset($CurrentBlock[\'interrupted\'])\r\n ) {\r\n return;\r\n }\r\n\r\n $Block[\'element\'][\'attributes\'] = array(\'start\' => $listStart);\r\n }\r\n }\r\n\r\n $Block[\'li\'] = array(\r\n \'name\' => \'li\',\r\n \'handler\' => array(\r\n \'function\' => \'li\',\r\n \'argument\' => !empty($matches[3]) ? array($matches[3]) : array(),\r\n \'destination\' => \'elements\'\r\n )\r\n );\r\n\r\n $Block[\'element\'][\'elements\'] []= & $Block[\'li\'];\r\n\r\n return $Block;\r\n }\r\n }\r\n\r\n protected function blockListContinue($Line, array $Block)\r\n {\r\n if (isset($Block[\'interrupted\']) and empty($Block[\'li\'][\'handler\'][\'argument\']))\r\n {\r\n return null;\r\n }\r\n\r\n $requiredIndent = ($Block[\'indent\'] + strlen($Block[\'data\'][\'marker\']));\r\n\r\n if ($Line[\'indent\'] < $requiredIndent\r\n and (\r\n (\r\n $Block[\'data\'][\'type\'] === \'ol\'\r\n and preg_match(\'/^[0-9]++\'.$Block[\'data\'][\'markerTypeRegex\'].\'(?:[ ]++(.*)|$)/\', $Line[\'text\'], $matches)\r\n ) or (\r\n $Block[\'data\'][\'type\'] === \'ul\'\r\n and preg_match(\'/^\'.$Block[\'data\'][\'markerTypeRegex\'].\'(?:[ ]++(.*)|$)/\', $Line[\'text\'], $matches)\r\n )\r\n )\r\n ) {\r\n if (isset($Block[\'interrupted\']))\r\n {\r\n $Block[\'li\'][\'handler\'][\'argument\'] []= \'\';\r\n\r\n $Block[\'loose\'] = true;\r\n\r\n unset($Block[\'interrupted\']);\r\n }\r\n\r\n unset($Block[\'li\']);\r\n\r\n $text = isset($matches[1]) ? $matches[1] : \'\';\r\n\r\n $Block[\'indent\'] = $Line[\'indent\'];\r\n\r\n $Block[\'li\'] = array(\r\n \'name\' => \'li\',\r\n \'handler\' => array(\r\n \'function\' => \'li\',\r\n \'argument\' => array($text),\r\n \'destination\' => \'elements\'\r\n )\r\n );\r\n\r\n $Block[\'element\'][\'elements\'] []= & $Block[\'li\'];\r\n\r\n return $Block;\r\n }\r\n elseif ($Line[\'indent\'] < $requiredIndent and $this->blockList($Line))\r\n {\r\n return null;\r\n }\r\n\r\n if ($Line[\'text\'][0] === \'[\' and $this->blockReference($Line))\r\n {\r\n return $Block;\r\n }\r\n\r\n if ($Line[\'indent\'] >= $requiredIndent)\r\n {\r\n if (isset($Block[\'interrupted\']))\r\n {\r\n $Block[\'li\'][\'handler\'][\'argument\'] []= \'\';\r\n\r\n $Block[\'loose\'] = true;\r\n\r\n unset($Block[\'interrupted\']);\r\n }\r\n\r\n $text = substr($Line[\'body\'], $requiredIndent);\r\n\r\n $Block[\'li\'][\'handler\'][\'argument\'] []= $text;\r\n\r\n return $Block;\r\n }\r\n\r\n if ( ! isset($Block[\'interrupted\']))\r\n {\r\n $text = preg_replace(\'/^[ ]{0,\'.$requiredIndent.\'}+/\', \'\', $Line[\'body\']);\r\n\r\n $Block[\'li\'][\'handler\'][\'argument\'] []= $text;\r\n\r\n return $Block;\r\n }\r\n }\r\n\r\n protected function blockListComplete(array $Block)\r\n {\r\n if (isset($Block[\'loose\']))\r\n {\r\n foreach ($Block[\'element\'][\'elements\'] as &$li)\r\n {\r\n if (end($li[\'handler\'][\'argument\']) !== \'\')\r\n {\r\n $li[\'handler\'][\'argument\'] []= \'\';\r\n }\r\n }\r\n }\r\n\r\n return $Block;\r\n }\r\n\r\n #\r\n # Quote\r\n\r\n protected function blockQuote($Line)\r\n {\r\n if (preg_match(\'/^>[ ]?+(.*+)/\', $Line[\'text\'], $matches))\r\n {\r\n $Block = array(\r\n \'element\' => array(\r\n \'name\' => \'blockquote\',\r\n \'handler\' => array(\r\n \'function\' => \'linesElements\',\r\n \'argument\' => (array) $matches[1],\r\n \'destination\' => \'elements\',\r\n )\r\n ),\r\n );\r\n\r\n return $Block;\r\n }\r\n }\r\n\r\n protected function blockQuoteContinue($Line, array $Block)\r\n {\r\n if (isset($Block[\'interrupted\']))\r\n {\r\n return;\r\n }\r\n\r\n if ($Line[\'text\'][0] === \'>\' and preg_match(\'/^>[ ]?+(.*+)/\', $Line[\'text\'], $matches))\r\n {\r\n $Block[\'element\'][\'handler\'][\'argument\'] []= $matches[1];\r\n\r\n return $Block;\r\n }\r\n\r\n if ( ! isset($Block[\'interrupted\']))\r\n {\r\n $Block[\'element\'][\'handler\'][\'argument\'] []= $Line[\'text\'];\r\n\r\n return $Block;\r\n }\r\n }\r\n\r\n #\r\n # Rule\r\n\r\n protected function blockRule($Line)\r\n {\r\n $marker = $Line[\'text\'][0];\r\n\r\n if (substr_count($Line[\'text\'], $marker) >= 3 and chop($Line[\'text\'], \" $marker\") === \'\')\r\n {\r\n $Block = array(\r\n \'element\' => array(\r\n \'name\' => \'hr\',\r\n ),\r\n );\r\n\r\n return $Block;\r\n }\r\n }\r\n\r\n #\r\n # Setext\r\n\r\n protected function blockSetextHeader($Line, array $Block = null)\r\n {\r\n if ( ! isset($Block) or $Block[\'type\'] !== \'Paragraph\' or isset($Block[\'interrupted\']))\r\n {\r\n return;\r\n }\r\n\r\n if ($Line[\'indent\'] < 4 and chop(chop($Line[\'text\'], \' \'), $Line[\'text\'][0]) === \'\')\r\n {\r\n $Block[\'element\'][\'name\'] = $Line[\'text\'][0] === \'=\' ? \'h1\' : \'h2\';\r\n\r\n return $Block;\r\n }\r\n }\r\n\r\n #\r\n # Markup\r\n\r\n protected function blockMarkup($Line)\r\n {\r\n if ($this->markupEscaped or $this->safeMode)\r\n {\r\n return;\r\n }\r\n\r\n if (preg_match(\'/^<[\\/]?+(\\w*)(?:[ ]*+\'.$this->regexHtmlAttribute.\')*+[ ]*+(\\/)?>/\', $Line[\'text\'], $matches))\r\n {\r\n $element = strtolower($matches[1]);\r\n\r\n if (in_array($element, $this->textLevelElements))\r\n {\r\n return;\r\n }\r\n\r\n $Block = array(\r\n \'name\' => $matches[1],\r\n \'element\' => array(\r\n \'rawHtml\' => $Line[\'text\'],\r\n \'autobreak\' => true,\r\n ),\r\n );\r\n\r\n return $Block;\r\n }\r\n }\r\n\r\n protected function blockMarkupContinue($Line, array $Block)\r\n {\r\n if (isset($Block[\'closed\']) or isset($Block[\'interrupted\']))\r\n {\r\n return;\r\n }\r\n\r\n $Block[\'element\'][\'rawHtml\'] .= \"\\n\" . $Line[\'body\'];\r\n\r\n return $Block;\r\n }\r\n\r\n #\r\n # Reference\r\n\r\n protected function blockReference($Line)\r\n {\r\n if (strpos($Line[\'text\'], \']\') !== false\r\n and preg_match(\'/^\\[(.+?)\\]:[ ]*+?(?:[ ]+[\"\\\'(](.+)[\"\\\')])?[ ]*+$/\', $Line[\'text\'], $matches)\r\n ) {\r\n $id = strtolower($matches[1]);\r\n\r\n $Data = array(\r\n \'url\' => $matches[2],\r\n \'title\' => isset($matches[3]) ? $matches[3] : null,\r\n );\r\n\r\n $this->DefinitionData[\'Reference\'][$id] = $Data;\r\n\r\n $Block = array(\r\n \'element\' => array(),\r\n );\r\n\r\n return $Block;\r\n }\r\n }\r\n\r\n #\r\n # Table\r\n\r\n protected function blockTable($Line, array $Block = null)\r\n {\r\n if ( ! isset($Block) or $Block[\'type\'] !== \'Paragraph\' or isset($Block[\'interrupted\']))\r\n {\r\n return;\r\n }\r\n\r\n if (\r\n strpos($Block[\'element\'][\'handler\'][\'argument\'], \'|\') === false\r\n and strpos($Line[\'text\'], \'|\') === false\r\n and strpos($Line[\'text\'], \':\') === false\r\n or strpos($Block[\'element\'][\'handler\'][\'argument\'], \"\\n\") !== false\r\n ) {\r\n return;\r\n }\r\n\r\n if (chop($Line[\'text\'], \' -:|\') !== \'\')\r\n {\r\n return;\r\n }\r\n\r\n $alignments = array();\r\n\r\n $divider = $Line[\'text\'];\r\n\r\n $divider = trim($divider);\r\n $divider = trim($divider, \'|\');\r\n\r\n $dividerCells = explode(\'|\', $divider);\r\n\r\n foreach ($dividerCells as $dividerCell)\r\n {\r\n $dividerCell = trim($dividerCell);\r\n\r\n if ($dividerCell === \'\')\r\n {\r\n return;\r\n }\r\n\r\n $alignment = null;\r\n\r\n if ($dividerCell[0] === \':\')\r\n {\r\n $alignment = \'left\';\r\n }\r\n\r\n if (substr($dividerCell, - 1) === \':\')\r\n {\r\n $alignment = $alignment === \'left\' ? \'center\' : \'right\';\r\n }\r\n\r\n $alignments []= $alignment;\r\n }\r\n\r\n # ~\r\n\r\n $HeaderElements = array();\r\n\r\n $header = $Block[\'element\'][\'handler\'][\'argument\'];\r\n\r\n $header = trim($header);\r\n $header = trim($header, \'|\');\r\n\r\n $headerCells = explode(\'|\', $header);\r\n\r\n if (count($headerCells) !== count($alignments))\r\n {\r\n return;\r\n }\r\n\r\n foreach ($headerCells as $index => $headerCell)\r\n {\r\n $headerCell = trim($headerCell);\r\n\r\n $HeaderElement = array(\r\n \'name\' => \'th\',\r\n \'handler\' => array(\r\n \'function\' => \'lineElements\',\r\n \'argument\' => $headerCell,\r\n \'destination\' => \'elements\',\r\n )\r\n );\r\n\r\n if (isset($alignments[$index]))\r\n {\r\n $alignment = $alignments[$index];\r\n\r\n $HeaderElement[\'attributes\'] = array(\r\n \'style\' => \"text-align: $alignment;\",\r\n );\r\n }\r\n\r\n $HeaderElements []= $HeaderElement;\r\n }\r\n\r\n # ~\r\n\r\n $Block = array(\r\n \'alignments\' => $alignments,\r\n \'identified\' => true,\r\n \'element\' => array(\r\n \'name\' => \'table\',\r\n \'elements\' => array(),\r\n ),\r\n );\r\n\r\n $Block[\'element\'][\'elements\'] []= array(\r\n \'name\' => \'thead\',\r\n );\r\n\r\n $Block[\'element\'][\'elements\'] []= array(\r\n \'name\' => \'tbody\',\r\n \'elements\' => array(),\r\n );\r\n\r\n $Block[\'element\'][\'elements\'][0][\'elements\'] []= array(\r\n \'name\' => \'tr\',\r\n \'elements\' => $HeaderElements,\r\n );\r\n\r\n return $Block;\r\n }\r\n\r\n protected function blockTableContinue($Line, array $Block)\r\n {\r\n if (isset($Block[\'interrupted\']))\r\n {\r\n return;\r\n }\r\n\r\n if (count($Block[\'alignments\']) === 1 or $Line[\'text\'][0] === \'|\' or strpos($Line[\'text\'], \'|\'))\r\n {\r\n $Elements = array();\r\n\r\n $row = $Line[\'text\'];\r\n\r\n $row = trim($row);\r\n $row = trim($row, \'|\');\r\n\r\n preg_match_all(\'/(?:(\\\\\\\\[|])|[^|`]|`[^`]++`|`)++/\', $row, $matches);\r\n\r\n $cells = array_slice($matches[0], 0, count($Block[\'alignments\']));\r\n\r\n foreach ($cells as $index => $cell)\r\n {\r\n $cell = trim($cell);\r\n\r\n $Element = array(\r\n \'name\' => \'td\',\r\n \'handler\' => array(\r\n \'function\' => \'lineElements\',\r\n \'argument\' => $cell,\r\n \'destination\' => \'elements\',\r\n )\r\n );\r\n\r\n if (isset($Block[\'alignments\'][$index]))\r\n {\r\n $Element[\'attributes\'] = array(\r\n \'style\' => \'text-align: \' . $Block[\'alignments\'][$index] . \';\',\r\n );\r\n }\r\n\r\n $Elements []= $Element;\r\n }\r\n\r\n $Element = array(\r\n \'name\' => \'tr\',\r\n \'elements\' => $Elements,\r\n );\r\n\r\n $Block[\'element\'][\'elements\'][1][\'elements\'] []= $Element;\r\n\r\n return $Block;\r\n }\r\n }\r\n\r\n #\r\n # ~\r\n #\r\n\r\n protected function paragraph($Line)\r\n {\r\n return array(\r\n \'type\' => \'Paragraph\',\r\n \'element\' => array(\r\n \'name\' => \'p\',\r\n \'handler\' => array(\r\n \'function\' => \'lineElements\',\r\n \'argument\' => $Line[\'text\'],\r\n \'destination\' => \'elements\',\r\n ),\r\n ),\r\n );\r\n }\r\n\r\n protected function paragraphContinue($Line, array $Block)\r\n {\r\n if (isset($Block[\'interrupted\']))\r\n {\r\n return;\r\n }\r\n\r\n $Block[\'element\'][\'handler\'][\'argument\'] .= \"\\n\".$Line[\'text\'];\r\n\r\n return $Block;\r\n }\r\n\r\n #\r\n # Inline Elements\r\n #\r\n\r\n protected $InlineTypes = array(\r\n \'!\' => array(\'Image\'),\r\n \'&\' => array(\'SpecialCharacter\'),\r\n \'*\' => array(\'Emphasis\'),\r\n \':\' => array(\'Url\'),\r\n \'<\' => array(\'UrlTag\', \'EmailTag\', \'Markup\'),\r\n \'[\' => array(\'Link\'),\r\n \'_\' => array(\'Emphasis\'),\r\n \'`\' => array(\'Code\'),\r\n \'~\' => array(\'Strikethrough\'),\r\n \'\\\\\' => array(\'EscapeSequence\'),\r\n );\r\n\r\n # ~\r\n\r\n protected $inlineMarkerList = \'!*_&[:<`~\\\\\';\r\n\r\n #\r\n # ~\r\n #\r\n\r\n public function line($text, $nonNestables = array())\r\n {\r\n return $this->elements($this->lineElements($text, $nonNestables));\r\n }\r\n\r\n protected function lineElements($text, $nonNestables = array())\r\n {\r\n # standardize line breaks\r\n $text = str_replace(array(\"\\r\\n\", \"\\r\"), \"\\n\", $text);\r\n\r\n $Elements = array();\r\n\r\n $nonNestables = (empty($nonNestables)\r\n ? array()\r\n : array_combine($nonNestables, $nonNestables)\r\n );\r\n\r\n # $excerpt is based on the first occurrence of a marker\r\n\r\n while ($excerpt = strpbrk($text, $this->inlineMarkerList))\r\n {\r\n $marker = $excerpt[0];\r\n\r\n $markerPosition = strlen($text) - strlen($excerpt);\r\n\r\n $Excerpt = array(\'text\' => $excerpt, \'context\' => $text);\r\n\r\n foreach ($this->InlineTypes[$marker] as $inlineType)\r\n {\r\n # check to see if the current inline type is nestable in the current context\r\n\r\n if (isset($nonNestables[$inlineType]))\r\n {\r\n continue;\r\n }\r\n\r\n $Inline = $this->{\"inline$inlineType\"}($Excerpt);\r\n\r\n if ( ! isset($Inline))\r\n {\r\n continue;\r\n }\r\n\r\n # makes sure that the inline belongs to \"our\" marker\r\n\r\n if (isset($Inline[\'position\']) and $Inline[\'position\'] > $markerPosition)\r\n {\r\n continue;\r\n }\r\n\r\n # sets a default inline position\r\n\r\n if ( ! isset($Inline[\'position\']))\r\n {\r\n $Inline[\'position\'] = $markerPosition;\r\n }\r\n\r\n # cause the new element to \'inherit\' our non nestables\r\n\r\n\r\n $Inline[\'element\'][\'nonNestables\'] = isset($Inline[\'element\'][\'nonNestables\'])\r\n ? array_merge($Inline[\'element\'][\'nonNestables\'], $nonNestables)\r\n : $nonNestables\r\n ;\r\n\r\n # the text that comes before the inline\r\n $unmarkedText = substr($text, 0, $Inline[\'position\']);\r\n\r\n # compile the unmarked text\r\n $InlineText = $this->inlineText($unmarkedText);\r\n $Elements[] = $InlineText[\'element\'];\r\n\r\n # compile the inline\r\n $Elements[] = $this->extractElement($Inline);\r\n\r\n # remove the examined text\r\n $text = substr($text, $Inline[\'position\'] + $Inline[\'extent\']);\r\n\r\n continue 2;\r\n }\r\n\r\n # the marker does not belong to an inline\r\n\r\n $unmarkedText = substr($text, 0, $markerPosition + 1);\r\n\r\n $InlineText = $this->inlineText($unmarkedText);\r\n $Elements[] = $InlineText[\'element\'];\r\n\r\n $text = substr($text, $markerPosition + 1);\r\n }\r\n\r\n $InlineText = $this->inlineText($text);\r\n $Elements[] = $InlineText[\'element\'];\r\n\r\n foreach ($Elements as &$Element)\r\n {\r\n if ( ! isset($Element[\'autobreak\']))\r\n {\r\n $Element[\'autobreak\'] = false;\r\n }\r\n }\r\n\r\n return $Elements;\r\n }\r\n\r\n #\r\n # ~\r\n #\r\n\r\n protected function inlineText($text)\r\n {\r\n $Inline = array(\r\n \'extent\' => strlen($text),\r\n \'element\' => array(),\r\n );\r\n\r\n $Inline[\'element\'][\'elements\'] = self::pregReplaceElements(\r\n $this->breaksEnabled ? \'/[ ]*+\\n/\' : \'/(?:[ ]*+\\\\\\\\|[ ]{2,}+)\\n/\',\r\n array(\r\n array(\'name\' => \'br\'),\r\n array(\'text\' => \"\\n\"),\r\n ),\r\n $text\r\n );\r\n\r\n return $Inline;\r\n }\r\n\r\n protected function inlineCode($Excerpt)\r\n {\r\n $marker = $Excerpt[\'text\'][0];\r\n\r\n if (preg_match(\'/^([\'.$marker.\']++)[ ]*+(.+?)[ ]*+(? strlen($matches[0]),\r\n \'element\' => array(\r\n \'name\' => \'code\',\r\n \'text\' => $text,\r\n ),\r\n );\r\n }\r\n }\r\n\r\n protected function inlineEmailTag($Excerpt)\r\n {\r\n $hostnameLabel = \'[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\';\r\n\r\n $commonMarkEmail = \'[a-zA-Z0-9.!#$%&\\\'*+\\/=?^_`{|}~-]++@\'\r\n . $hostnameLabel . \'(?:\\.\' . $hostnameLabel . \')*\';\r\n\r\n if (strpos($Excerpt[\'text\'], \'>\') !== false\r\n and preg_match(\"/^<((mailto:)?$commonMarkEmail)>/i\", $Excerpt[\'text\'], $matches)\r\n ){\r\n $url = $matches[1];\r\n\r\n if ( ! isset($matches[2]))\r\n {\r\n $url = \"mailto:$url\";\r\n }\r\n\r\n return array(\r\n \'extent\' => strlen($matches[0]),\r\n \'element\' => array(\r\n \'name\' => \'a\',\r\n \'text\' => $matches[1],\r\n \'attributes\' => array(\r\n \'href\' => $url,\r\n ),\r\n ),\r\n );\r\n }\r\n }\r\n\r\n protected function inlineEmphasis($Excerpt)\r\n {\r\n if ( ! isset($Excerpt[\'text\'][1]))\r\n {\r\n return;\r\n }\r\n\r\n $marker = $Excerpt[\'text\'][0];\r\n\r\n if ($Excerpt[\'text\'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt[\'text\'], $matches))\r\n {\r\n $emphasis = \'strong\';\r\n }\r\n elseif (preg_match($this->EmRegex[$marker], $Excerpt[\'text\'], $matches))\r\n {\r\n $emphasis = \'em\';\r\n }\r\n else\r\n {\r\n return;\r\n }\r\n\r\n return array(\r\n \'extent\' => strlen($matches[0]),\r\n \'element\' => array(\r\n \'name\' => $emphasis,\r\n \'handler\' => array(\r\n \'function\' => \'lineElements\',\r\n \'argument\' => $matches[1],\r\n \'destination\' => \'elements\',\r\n )\r\n ),\r\n );\r\n }\r\n\r\n protected function inlineEscapeSequence($Excerpt)\r\n {\r\n if (isset($Excerpt[\'text\'][1]) and in_array($Excerpt[\'text\'][1], $this->specialCharacters))\r\n {\r\n return array(\r\n \'element\' => array(\'rawHtml\' => $Excerpt[\'text\'][1]),\r\n \'extent\' => 2,\r\n );\r\n }\r\n }\r\n\r\n protected function inlineImage($Excerpt)\r\n {\r\n if ( ! isset($Excerpt[\'text\'][1]) or $Excerpt[\'text\'][1] !== \'[\')\r\n {\r\n return;\r\n }\r\n\r\n $Excerpt[\'text\']= substr($Excerpt[\'text\'], 1);\r\n\r\n $Link = $this->inlineLink($Excerpt);\r\n\r\n if ($Link === null)\r\n {\r\n return;\r\n }\r\n\r\n $Inline = array(\r\n \'extent\' => $Link[\'extent\'] + 1,\r\n \'element\' => array(\r\n \'name\' => \'img\',\r\n \'attributes\' => array(\r\n \'src\' => $Link[\'element\'][\'attributes\'][\'href\'],\r\n \'alt\' => $Link[\'element\'][\'handler\'][\'argument\'],\r\n ),\r\n \'autobreak\' => true,\r\n ),\r\n );\r\n\r\n $Inline[\'element\'][\'attributes\'] += $Link[\'element\'][\'attributes\'];\r\n\r\n unset($Inline[\'element\'][\'attributes\'][\'href\']);\r\n\r\n return $Inline;\r\n }\r\n\r\n protected function inlineLink($Excerpt)\r\n {\r\n $Element = array(\r\n \'name\' => \'a\',\r\n \'handler\' => array(\r\n \'function\' => \'lineElements\',\r\n \'argument\' => null,\r\n \'destination\' => \'elements\',\r\n ),\r\n \'nonNestables\' => array(\'Url\', \'Link\'),\r\n \'attributes\' => array(\r\n \'href\' => null,\r\n \'title\' => null,\r\n ),\r\n );\r\n\r\n $extent = 0;\r\n\r\n $remainder = $Excerpt[\'text\'];\r\n\r\n if (preg_match(\'/\\[((?:[^][]++|(?R))*+)\\]/\', $remainder, $matches))\r\n {\r\n $Element[\'handler\'][\'argument\'] = $matches[1];\r\n\r\n $extent += strlen($matches[0]);\r\n\r\n $remainder = substr($remainder, $extent);\r\n }\r\n else\r\n {\r\n return;\r\n }\r\n\r\n if (preg_match(\'/^[(]\\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+(\"[^\"]*+\"|\\\'[^\\\']*+\\\'))?\\s*+[)]/\', $remainder, $matches))\r\n {\r\n $Element[\'attributes\'][\'href\'] = $matches[1];\r\n\r\n if (isset($matches[2]))\r\n {\r\n $Element[\'attributes\'][\'title\'] = substr($matches[2], 1, - 1);\r\n }\r\n\r\n $extent += strlen($matches[0]);\r\n }\r\n else\r\n {\r\n if (preg_match(\'/^\\s*\\[(.*?)\\]/\', $remainder, $matches))\r\n {\r\n $definition = strlen($matches[1]) ? $matches[1] : $Element[\'handler\'][\'argument\'];\r\n $definition = strtolower($definition);\r\n\r\n $extent += strlen($matches[0]);\r\n }\r\n else\r\n {\r\n $definition = strtolower($Element[\'handler\'][\'argument\']);\r\n }\r\n\r\n if ( ! isset($this->DefinitionData[\'Reference\'][$definition]))\r\n {\r\n return;\r\n }\r\n\r\n $Definition = $this->DefinitionData[\'Reference\'][$definition];\r\n\r\n $Element[\'attributes\'][\'href\'] = $Definition[\'url\'];\r\n $Element[\'attributes\'][\'title\'] = $Definition[\'title\'];\r\n }\r\n\r\n return array(\r\n \'extent\' => $extent,\r\n \'element\' => $Element,\r\n );\r\n }\r\n\r\n protected function inlineMarkup($Excerpt)\r\n {\r\n if ($this->markupEscaped or $this->safeMode or strpos($Excerpt[\'text\'], \'>\') === false)\r\n {\r\n return;\r\n }\r\n\r\n if ($Excerpt[\'text\'][1] === \'/\' and preg_match(\'/^<\\/\\w[\\w-]*+[ ]*+>/s\', $Excerpt[\'text\'], $matches))\r\n {\r\n return array(\r\n \'element\' => array(\'rawHtml\' => $matches[0]),\r\n \'extent\' => strlen($matches[0]),\r\n );\r\n }\r\n\r\n if ($Excerpt[\'text\'][1] === \'!\' and preg_match(\'/^/s\', $Excerpt[\'text\'], $matches))\r\n {\r\n return array(\r\n \'element\' => array(\'rawHtml\' => $matches[0]),\r\n \'extent\' => strlen($matches[0]),\r\n );\r\n }\r\n\r\n if ($Excerpt[\'text\'][1] !== \' \' and preg_match(\'/^<\\w[\\w-]*+(?:[ ]*+\'.$this->regexHtmlAttribute.\')*+[ ]*+\\/?>/s\', $Excerpt[\'text\'], $matches))\r\n {\r\n return array(\r\n \'element\' => array(\'rawHtml\' => $matches[0]),\r\n \'extent\' => strlen($matches[0]),\r\n );\r\n }\r\n }\r\n\r\n protected function inlineSpecialCharacter($Excerpt)\r\n {\r\n if (substr($Excerpt[\'text\'], 1, 1) !== \' \' and strpos($Excerpt[\'text\'], \';\') !== false\r\n and preg_match(\'/^&(#?+[0-9a-zA-Z]++);/\', $Excerpt[\'text\'], $matches)\r\n ) {\r\n return array(\r\n \'element\' => array(\'rawHtml\' => \'&\' . $matches[1] . \';\'),\r\n \'extent\' => strlen($matches[0]),\r\n );\r\n }\r\n\r\n return;\r\n }\r\n\r\n protected function inlineStrikethrough($Excerpt)\r\n {\r\n if ( ! isset($Excerpt[\'text\'][1]))\r\n {\r\n return;\r\n }\r\n\r\n if ($Excerpt[\'text\'][1] === \'~\' and preg_match(\'/^~~(?=\\S)(.+?)(?<=\\S)~~/\', $Excerpt[\'text\'], $matches))\r\n {\r\n return array(\r\n \'extent\' => strlen($matches[0]),\r\n \'element\' => array(\r\n \'name\' => \'del\',\r\n \'handler\' => array(\r\n \'function\' => \'lineElements\',\r\n \'argument\' => $matches[1],\r\n \'destination\' => \'elements\',\r\n )\r\n ),\r\n );\r\n }\r\n }\r\n\r\n protected function inlineUrl($Excerpt)\r\n {\r\n if ($this->urlsLinked !== true or ! isset($Excerpt[\'text\'][2]) or $Excerpt[\'text\'][2] !== \'/\')\r\n {\r\n return;\r\n }\r\n\r\n if (strpos($Excerpt[\'context\'], \'http\') !== false\r\n and preg_match(\'/\\bhttps?+:[\\/]{2}[^\\s<]+\\b\\/*+/ui\', $Excerpt[\'context\'], $matches, PREG_OFFSET_CAPTURE)\r\n ) {\r\n $url = $matches[0][0];\r\n\r\n $Inline = array(\r\n \'extent\' => strlen($matches[0][0]),\r\n \'position\' => $matches[0][1],\r\n \'element\' => array(\r\n \'name\' => \'a\',\r\n \'text\' => $url,\r\n \'attributes\' => array(\r\n \'href\' => $url,\r\n ),\r\n ),\r\n );\r\n\r\n return $Inline;\r\n }\r\n }\r\n\r\n protected function inlineUrlTag($Excerpt)\r\n {\r\n if (strpos($Excerpt[\'text\'], \'>\') !== false and preg_match(\'/^<(\\w++:\\/{2}[^ >]++)>/i\', $Excerpt[\'text\'], $matches))\r\n {\r\n $url = $matches[1];\r\n\r\n return array(\r\n \'extent\' => strlen($matches[0]),\r\n \'element\' => array(\r\n \'name\' => \'a\',\r\n \'text\' => $url,\r\n \'attributes\' => array(\r\n \'href\' => $url,\r\n ),\r\n ),\r\n );\r\n }\r\n }\r\n\r\n # ~\r\n\r\n protected function unmarkedText($text)\r\n {\r\n $Inline = $this->inlineText($text);\r\n return $this->element($Inline[\'element\']);\r\n }\r\n\r\n #\r\n # Handlers\r\n #\r\n\r\n protected function handle(array $Element)\r\n {\r\n if (isset($Element[\'handler\']))\r\n {\r\n if (!isset($Element[\'nonNestables\']))\r\n {\r\n $Element[\'nonNestables\'] = array();\r\n }\r\n\r\n if (is_string($Element[\'handler\']))\r\n {\r\n $function = $Element[\'handler\'];\r\n $argument = $Element[\'text\'];\r\n unset($Element[\'text\']);\r\n $destination = \'rawHtml\';\r\n }\r\n else\r\n {\r\n $function = $Element[\'handler\'][\'function\'];\r\n $argument = $Element[\'handler\'][\'argument\'];\r\n $destination = $Element[\'handler\'][\'destination\'];\r\n }\r\n\r\n $Element[$destination] = $this->{$function}($argument, $Element[\'nonNestables\']);\r\n\r\n if ($destination === \'handler\')\r\n {\r\n $Element = $this->handle($Element);\r\n }\r\n\r\n unset($Element[\'handler\']);\r\n }\r\n\r\n return $Element;\r\n }\r\n\r\n protected function handleElementRecursive(array $Element)\r\n {\r\n return $this->elementApplyRecursive(array($this, \'handle\'), $Element);\r\n }\r\n\r\n protected function handleElementsRecursive(array $Elements)\r\n {\r\n return $this->elementsApplyRecursive(array($this, \'handle\'), $Elements);\r\n }\r\n\r\n protected function elementApplyRecursive($closure, array $Element)\r\n {\r\n $Element = call_user_func($closure, $Element);\r\n\r\n if (isset($Element[\'elements\']))\r\n {\r\n $Element[\'elements\'] = $this->elementsApplyRecursive($closure, $Element[\'elements\']);\r\n }\r\n elseif (isset($Element[\'element\']))\r\n {\r\n $Element[\'element\'] = $this->elementApplyRecursive($closure, $Element[\'element\']);\r\n }\r\n\r\n return $Element;\r\n }\r\n\r\n protected function elementApplyRecursiveDepthFirst($closure, array $Element)\r\n {\r\n if (isset($Element[\'elements\']))\r\n {\r\n $Element[\'elements\'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element[\'elements\']);\r\n }\r\n elseif (isset($Element[\'element\']))\r\n {\r\n $Element[\'element\'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element[\'element\']);\r\n }\r\n\r\n $Element = call_user_func($closure, $Element);\r\n\r\n return $Element;\r\n }\r\n\r\n protected function elementsApplyRecursive($closure, array $Elements)\r\n {\r\n foreach ($Elements as &$Element)\r\n {\r\n $Element = $this->elementApplyRecursive($closure, $Element);\r\n }\r\n\r\n return $Elements;\r\n }\r\n\r\n protected function elementsApplyRecursiveDepthFirst($closure, array $Elements)\r\n {\r\n foreach ($Elements as &$Element)\r\n {\r\n $Element = $this->elementApplyRecursiveDepthFirst($closure, $Element);\r\n }\r\n\r\n return $Elements;\r\n }\r\n\r\n protected function element(array $Element)\r\n {\r\n if ($this->safeMode)\r\n {\r\n $Element = $this->sanitiseElement($Element);\r\n }\r\n\r\n # identity map if element has no handler\r\n $Element = $this->handle($Element);\r\n\r\n $hasName = isset($Element[\'name\']);\r\n\r\n $markup = \'\';\r\n\r\n if ($hasName)\r\n {\r\n $markup .= \'<\' . $Element[\'name\'];\r\n\r\n if (isset($Element[\'attributes\']))\r\n {\r\n foreach ($Element[\'attributes\'] as $name => $value)\r\n {\r\n if ($value === null)\r\n {\r\n continue;\r\n }\r\n\r\n $markup .= \" $name=\\\"\".self::escape($value).\'\"\';\r\n }\r\n }\r\n }\r\n\r\n $permitRawHtml = false;\r\n\r\n if (isset($Element[\'text\']))\r\n {\r\n $text = $Element[\'text\'];\r\n }\r\n // very strongly consider an alternative if you\'re writing an\r\n // extension\r\n elseif (isset($Element[\'rawHtml\']))\r\n {\r\n $text = $Element[\'rawHtml\'];\r\n\r\n $allowRawHtmlInSafeMode = isset($Element[\'allowRawHtmlInSafeMode\']) && $Element[\'allowRawHtmlInSafeMode\'];\r\n $permitRawHtml = !$this->safeMode || $allowRawHtmlInSafeMode;\r\n }\r\n\r\n $hasContent = isset($text) || isset($Element[\'element\']) || isset($Element[\'elements\']);\r\n\r\n if ($hasContent)\r\n {\r\n $markup .= $hasName ? \'>\' : \'\';\r\n\r\n if (isset($Element[\'elements\']))\r\n {\r\n $markup .= $this->elements($Element[\'elements\']);\r\n }\r\n elseif (isset($Element[\'element\']))\r\n {\r\n $markup .= $this->element($Element[\'element\']);\r\n }\r\n else\r\n {\r\n if (!$permitRawHtml)\r\n {\r\n $markup .= self::escape($text, true);\r\n }\r\n else\r\n {\r\n $markup .= $text;\r\n }\r\n }\r\n\r\n $markup .= $hasName ? \'\' : \'\';\r\n }\r\n elseif ($hasName)\r\n {\r\n $markup .= \' />\';\r\n }\r\n\r\n return $markup;\r\n }\r\n\r\n protected function elements(array $Elements)\r\n {\r\n $markup = \'\';\r\n\r\n $autoBreak = true;\r\n\r\n foreach ($Elements as $Element)\r\n {\r\n if (empty($Element))\r\n {\r\n continue;\r\n }\r\n\r\n $autoBreakNext = (isset($Element[\'autobreak\'])\r\n ? $Element[\'autobreak\'] : isset($Element[\'name\'])\r\n );\r\n // (autobreak === false) covers both sides of an element\r\n $autoBreak = !$autoBreak ? $autoBreak : $autoBreakNext;\r\n\r\n $markup .= ($autoBreak ? \"\\n\" : \'\') . $this->element($Element);\r\n $autoBreak = $autoBreakNext;\r\n }\r\n\r\n $markup .= $autoBreak ? \"\\n\" : \'\';\r\n\r\n return $markup;\r\n }\r\n\r\n # ~\r\n\r\n protected function li($lines)\r\n {\r\n $Elements = $this->linesElements($lines);\r\n\r\n if ( ! in_array(\'\', $lines)\r\n and isset($Elements[0]) and isset($Elements[0][\'name\'])\r\n and $Elements[0][\'name\'] === \'p\'\r\n ) {\r\n unset($Elements[0][\'name\']);\r\n }\r\n\r\n return $Elements;\r\n }\r\n\r\n #\r\n # AST Convenience\r\n #\r\n\r\n /**\r\n * Replace occurrences $regexp with $Elements in $text. Return an array of\r\n * elements representing the replacement.\r\n */\r\n protected static function pregReplaceElements($regexp, $Elements, $text)\r\n {\r\n $newElements = array();\r\n\r\n while (preg_match($regexp, $text, $matches, PREG_OFFSET_CAPTURE))\r\n {\r\n $offset = $matches[0][1];\r\n $before = substr($text, 0, $offset);\r\n $after = substr($text, $offset + strlen($matches[0][0]));\r\n\r\n $newElements[] = array(\'text\' => $before);\r\n\r\n foreach ($Elements as $Element)\r\n {\r\n $newElements[] = $Element;\r\n }\r\n\r\n $text = $after;\r\n }\r\n\r\n $newElements[] = array(\'text\' => $text);\r\n\r\n return $newElements;\r\n }\r\n\r\n #\r\n # Deprecated Methods\r\n #\r\n\r\n function parse($text)\r\n {\r\n $markup = $this->text($text);\r\n\r\n return $markup;\r\n }\r\n\r\n protected function sanitiseElement(array $Element)\r\n {\r\n static $goodAttribute = \'/^[a-zA-Z0-9][a-zA-Z0-9-_]*+$/\';\r\n static $safeUrlNameToAtt = array(\r\n \'a\' => \'href\',\r\n \'img\' => \'src\',\r\n );\r\n\r\n if ( ! isset($Element[\'name\']))\r\n {\r\n unset($Element[\'attributes\']);\r\n return $Element;\r\n }\r\n\r\n if (isset($safeUrlNameToAtt[$Element[\'name\']]))\r\n {\r\n $Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element[\'name\']]);\r\n }\r\n\r\n if ( ! empty($Element[\'attributes\']))\r\n {\r\n foreach ($Element[\'attributes\'] as $att => $val)\r\n {\r\n # filter out badly parsed attribute\r\n if ( ! preg_match($goodAttribute, $att))\r\n {\r\n unset($Element[\'attributes\'][$att]);\r\n }\r\n # dump onevent attribute\r\n elseif (self::striAtStart($att, \'on\'))\r\n {\r\n unset($Element[\'attributes\'][$att]);\r\n }\r\n }\r\n }\r\n\r\n return $Element;\r\n }\r\n\r\n protected function filterUnsafeUrlInAttribute(array $Element, $attribute)\r\n {\r\n foreach ($this->safeLinksWhitelist as $scheme)\r\n {\r\n if (self::striAtStart($Element[\'attributes\'][$attribute], $scheme))\r\n {\r\n return $Element;\r\n }\r\n }\r\n\r\n $Element[\'attributes\'][$attribute] = str_replace(\':\', \'%3A\', $Element[\'attributes\'][$attribute]);\r\n\r\n return $Element;\r\n }\r\n\r\n #\r\n # Static Methods\r\n #\r\n\r\n protected static function escape($text, $allowQuotes = false)\r\n {\r\n return htmlspecialchars($text, $allowQuotes ? ENT_NOQUOTES : ENT_QUOTES, \'UTF-8\');\r\n }\r\n\r\n protected static function striAtStart($string, $needle)\r\n {\r\n $len = strlen($needle);\r\n\r\n if ($len > strlen($string))\r\n {\r\n return false;\r\n }\r\n else\r\n {\r\n return strtolower(substr($string, 0, $len)) === strtolower($needle);\r\n }\r\n }\r\n\r\n static function instance($name = \'default\')\r\n {\r\n if (isset(self::$instances[$name]))\r\n {\r\n return self::$instances[$name];\r\n }\r\n\r\n $instance = new static();\r\n\r\n self::$instances[$name] = $instance;\r\n\r\n return $instance;\r\n }\r\n\r\n private static $instances = array();\r\n\r\n #\r\n # Fields\r\n #\r\n\r\n protected $DefinitionData;\r\n\r\n #\r\n # Read-Only\r\n\r\n protected $specialCharacters = array(\r\n \'\\\\\', \'`\', \'*\', \'_\', \'{\', \'}\', \'[\', \']\', \'(\', \')\', \'>\', \'#\', \'+\', \'-\', \'.\', \'!\', \'|\', \'~\'\r\n );\r\n\r\n protected $StrongRegex = array(\r\n \'*\' => \'/^[*]{2}((?:\\\\\\\\\\*|[^*]|[*][^*]*+[*])+?)[*]{2}(?![*])/s\',\r\n \'_\' => \'/^__((?:\\\\\\\\_|[^_]|_[^_]*+_)+?)__(?!_)/us\',\r\n );\r\n\r\n protected $EmRegex = array(\r\n \'*\' => \'/^[*]((?:\\\\\\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s\',\r\n \'_\' => \'/^_((?:\\\\\\\\_|[^_]|__[^_]*__)+?)_(?!_)\\b/us\',\r\n );\r\n\r\n protected $regexHtmlAttribute = \'[a-zA-Z_:][\\w:.-]*+(?:\\s*+=\\s*+(?:[^\"\\\'=<>`\\s]+|\"[^\"]*+\"|\\\'[^\\\']*+\\\'))?+\';\r\n\r\n protected $voidElements = array(\r\n \'area\', \'base\', \'br\', \'col\', \'command\', \'embed\', \'hr\', \'img\', \'input\', \'link\', \'meta\', \'param\', \'source\',\r\n );\r\n\r\n protected $textLevelElements = array(\r\n \'a\', \'br\', \'bdo\', \'abbr\', \'blink\', \'nextid\', \'acronym\', \'basefont\',\r\n \'b\', \'em\', \'big\', \'cite\', \'small\', \'spacer\', \'listing\',\r\n \'i\', \'rp\', \'del\', \'code\', \'strike\', \'marquee\',\r\n \'q\', \'rt\', \'ins\', \'font\', \'strong\',\r\n \'s\', \'tt\', \'kbd\', \'mark\',\r\n \'u\', \'xm\', \'sub\', \'nobr\',\r\n \'sup\', \'ruby\',\r\n \'var\', \'span\',\r\n \'wbr\', \'time\',\r\n );\r\n}', 1); +INSERT INTO `onefile` VALUES (14, 'httpstat', 'Python', '用更优雅的方式展示 curl 结果的命令行工具', 12, 'https://github.com/reorx/httpstat', 8000, '#!/usr/bin/env python\r\n# coding: utf-8\r\n# References:\r\n# man curl\r\n# https://curl.haxx.se/libcurl/c/curl_easy_getinfo.html\r\n# https://curl.haxx.se/libcurl/c/easy_getinfo_options.html\r\n# http://blog.kenweiner.com/2014/11/http-request-timings-with-curl.html\r\n\r\nfrom __future__ import print_function\r\n\r\nimport os\r\nimport json\r\nimport sys\r\nimport logging\r\nimport tempfile\r\nimport subprocess\r\n\r\n\r\n__version__ = \'1.3.1\'\r\n\r\n\r\nPY3 = sys.version_info >= (3,)\r\n\r\nif PY3:\r\n xrange = range\r\n\r\n\r\n# Env class is copied from https://github.com/reorx/getenv/blob/master/getenv.py\r\nclass Env(object):\r\n prefix = \'HTTPSTAT\'\r\n _instances = []\r\n\r\n def __init__(self, key):\r\n self.key = key.format(prefix=self.prefix)\r\n Env._instances.append(self)\r\n\r\n def get(self, default=None):\r\n return os.environ.get(self.key, default)\r\n\r\n\r\nENV_SHOW_BODY = Env(\'{prefix}_SHOW_BODY\')\r\nENV_SHOW_IP = Env(\'{prefix}_SHOW_IP\')\r\nENV_SHOW_SPEED = Env(\'{prefix}_SHOW_SPEED\')\r\nENV_SAVE_BODY = Env(\'{prefix}_SAVE_BODY\')\r\nENV_CURL_BIN = Env(\'{prefix}_CURL_BIN\')\r\nENV_METRICS_ONLY = Env(\'{prefix}_METRICS_ONLY\')\r\nENV_DEBUG = Env(\'{prefix}_DEBUG\')\r\n\r\n\r\ncurl_format = \"\"\"{\r\n\"time_namelookup\": %{time_namelookup},\r\n\"time_connect\": %{time_connect},\r\n\"time_appconnect\": %{time_appconnect},\r\n\"time_pretransfer\": %{time_pretransfer},\r\n\"time_redirect\": %{time_redirect},\r\n\"time_starttransfer\": %{time_starttransfer},\r\n\"time_total\": %{time_total},\r\n\"speed_download\": %{speed_download},\r\n\"speed_upload\": %{speed_upload},\r\n\"remote_ip\": \"%{remote_ip}\",\r\n\"remote_port\": \"%{remote_port}\",\r\n\"local_ip\": \"%{local_ip}\",\r\n\"local_port\": \"%{local_port}\"\r\n}\"\"\"\r\n\r\nhttps_template = \"\"\"\r\n DNS Lookup TCP Connection TLS Handshake Server Processing Content Transfer\r\n[ {a0000} | {a0001} | {a0002} | {a0003} | {a0004} ]\r\n | | | | |\r\n namelookup:{b0000} | | | |\r\n connect:{b0001} | | |\r\n pretransfer:{b0002} | |\r\n starttransfer:{b0003} |\r\n total:{b0004}\r\n\"\"\"[1:]\r\n\r\nhttp_template = \"\"\"\r\n DNS Lookup TCP Connection Server Processing Content Transfer\r\n[ {a0000} | {a0001} | {a0003} | {a0004} ]\r\n | | | |\r\n namelookup:{b0000} | | |\r\n connect:{b0001} | |\r\n starttransfer:{b0003} |\r\n total:{b0004}\r\n\"\"\"[1:]\r\n\r\n\r\n# Color code is copied from https://github.com/reorx/python-terminal-color/blob/master/color_simple.py\r\nISATTY = sys.stdout.isatty()\r\n\r\n\r\ndef make_color(code):\r\n def color_func(s):\r\n if not ISATTY:\r\n return s\r\n tpl = \'\\x1b[{}m{}\\x1b[0m\'\r\n return tpl.format(code, s)\r\n return color_func\r\n\r\n\r\nred = make_color(31)\r\ngreen = make_color(32)\r\nyellow = make_color(33)\r\nblue = make_color(34)\r\nmagenta = make_color(35)\r\ncyan = make_color(36)\r\n\r\nbold = make_color(1)\r\nunderline = make_color(4)\r\n\r\ngrayscale = {(i - 232): make_color(\'38;5;\' + str(i)) for i in xrange(232, 256)}\r\n\r\n\r\ndef quit(s, code=0):\r\n if s is not None:\r\n print(s)\r\n sys.exit(code)\r\n\r\n\r\ndef print_help():\r\n help = \"\"\"\r\nUsage: httpstat URL [CURL_OPTIONS]\r\n httpstat -h | --help\r\n httpstat --version\r\n\r\nArguments:\r\n URL url to request, could be with or without `http(s)://` prefix\r\n\r\nOptions:\r\n CURL_OPTIONS any curl supported options, except for -w -D -o -S -s,\r\n which are already used internally.\r\n -h --help show this screen.\r\n --version show version.\r\n\r\nEnvironments:\r\n HTTPSTAT_SHOW_BODY Set to `true` to show response body in the output,\r\n note that body length is limited to 1023 bytes, will be\r\n truncated if exceeds. Default is `false`.\r\n HTTPSTAT_SHOW_IP By default httpstat shows remote and local IP/port address.\r\n Set to `false` to disable this feature. Default is `true`.\r\n HTTPSTAT_SHOW_SPEED Set to `true` to show download and upload speed.\r\n Default is `false`.\r\n HTTPSTAT_SAVE_BODY By default httpstat stores body in a tmp file,\r\n set to `false` to disable this feature. Default is `true`\r\n HTTPSTAT_CURL_BIN Indicate the curl bin path to use. Default is `curl`\r\n from current shell $PATH.\r\n HTTPSTAT_DEBUG Set to `true` to see debugging logs. Default is `false`\r\n\"\"\"[1:-1]\r\n print(help)\r\n\r\n\r\ndef main():\r\n args = sys.argv[1:]\r\n if not args:\r\n print_help()\r\n quit(None, 0)\r\n\r\n # get envs\r\n show_body = \'true\' in ENV_SHOW_BODY.get(\'false\').lower()\r\n show_ip = \'true\' in ENV_SHOW_IP.get(\'true\').lower()\r\n show_speed = \'true\'in ENV_SHOW_SPEED.get(\'false\').lower()\r\n save_body = \'true\' in ENV_SAVE_BODY.get(\'true\').lower()\r\n curl_bin = ENV_CURL_BIN.get(\'curl\')\r\n metrics_only = \'true\' in ENV_METRICS_ONLY.get(\'false\').lower()\r\n is_debug = \'true\' in ENV_DEBUG.get(\'false\').lower()\r\n\r\n # configure logging\r\n if is_debug:\r\n log_level = logging.DEBUG\r\n else:\r\n log_level = logging.INFO\r\n logging.basicConfig(level=log_level)\r\n lg = logging.getLogger(\'httpstat\')\r\n\r\n # log envs\r\n lg.debug(\'Envs:\\n%s\', \'\\n\'.join(\' {}={}\'.format(i.key, i.get(\'\')) for i in Env._instances))\r\n lg.debug(\'Flags: %s\', dict(\r\n show_body=show_body,\r\n show_ip=show_ip,\r\n show_speed=show_speed,\r\n save_body=save_body,\r\n curl_bin=curl_bin,\r\n is_debug=is_debug,\r\n ))\r\n\r\n # get url\r\n url = args[0]\r\n if url in [\'-h\', \'--help\']:\r\n print_help()\r\n quit(None, 0)\r\n elif url == \'--version\':\r\n print(\'httpstat {}\'.format(__version__))\r\n quit(None, 0)\r\n\r\n curl_args = args[1:]\r\n\r\n # check curl args\r\n exclude_options = [\r\n \'-w\', \'--write-out\',\r\n \'-D\', \'--dump-header\',\r\n \'-o\', \'--output\',\r\n \'-s\', \'--silent\',\r\n ]\r\n for i in exclude_options:\r\n if i in curl_args:\r\n quit(yellow(\'Error: {} is not allowed in extra curl args\'.format(i)), 1)\r\n\r\n # tempfile for output\r\n bodyf = tempfile.NamedTemporaryFile(delete=False)\r\n bodyf.close()\r\n\r\n headerf = tempfile.NamedTemporaryFile(delete=False)\r\n headerf.close()\r\n\r\n # run cmd\r\n cmd_env = os.environ.copy()\r\n cmd_env.update(\r\n LC_ALL=\'C\',\r\n )\r\n cmd_core = [curl_bin, \'-w\', curl_format, \'-D\', headerf.name, \'-o\', bodyf.name, \'-s\', \'-S\']\r\n cmd = cmd_core + curl_args + [url]\r\n lg.debug(\'cmd: %s\', cmd)\r\n p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=cmd_env)\r\n out, err = p.communicate()\r\n if PY3:\r\n out, err = out.decode(), err.decode()\r\n lg.debug(\'out: %s\', out)\r\n\r\n # print stderr\r\n if p.returncode == 0:\r\n if err:\r\n print(grayscale[16](err))\r\n else:\r\n _cmd = list(cmd)\r\n _cmd[2] = \'\'\r\n _cmd[4] = \'\'\r\n _cmd[6] = \'\'\r\n print(\'> {}\'.format(\' \'.join(_cmd)))\r\n quit(yellow(\'curl error: {}\'.format(err)), p.returncode)\r\n\r\n # parse output\r\n try:\r\n d = json.loads(out)\r\n except ValueError as e:\r\n print(yellow(\'Could not decode json: {}\'.format(e)))\r\n print(\'curl result:\', p.returncode, grayscale[16](out), grayscale[16](err))\r\n quit(None, 1)\r\n\r\n # convert time_ metrics from seconds to milliseconds\r\n for k in d:\r\n if k.startswith(\'time_\'):\r\n v = d[k]\r\n # Convert time_ values to milliseconds in int\r\n if isinstance(v, float):\r\n # Before 7.61.0, time values are represented as seconds in float\r\n d[k] = int(v * 1000)\r\n elif isinstance(v, int):\r\n # Starting from 7.61.0, libcurl uses microsecond in int\r\n # to return time values, references:\r\n # https://daniel.haxx.se/blog/2018/07/11/curl-7-61-0/\r\n # https://curl.se/bug/?i=2495\r\n d[k] = int(v / 1000)\r\n else:\r\n raise TypeError(\'{} value type is invalid: {}\'.format(k, type(v)))\r\n\r\n # calculate ranges\r\n d.update(\r\n range_dns=d[\'time_namelookup\'],\r\n range_connection=d[\'time_connect\'] - d[\'time_namelookup\'],\r\n range_ssl=d[\'time_pretransfer\'] - d[\'time_connect\'],\r\n range_server=d[\'time_starttransfer\'] - d[\'time_pretransfer\'],\r\n range_transfer=d[\'time_total\'] - d[\'time_starttransfer\'],\r\n )\r\n\r\n # print json if metrics_only is enabled\r\n if metrics_only:\r\n print(json.dumps(d, indent=2))\r\n quit(None, 0)\r\n\r\n # ip\r\n if show_ip:\r\n s = \'Connected to {}:{} from {}:{}\'.format(\r\n cyan(d[\'remote_ip\']), cyan(d[\'remote_port\']),\r\n d[\'local_ip\'], d[\'local_port\'],\r\n )\r\n print(s)\r\n print()\r\n\r\n # print header & body summary\r\n with open(headerf.name, \'r\') as f:\r\n headers = f.read().strip()\r\n # remove header file\r\n lg.debug(\'rm header file %s\', headerf.name)\r\n os.remove(headerf.name)\r\n\r\n for loop, line in enumerate(headers.split(\'\\n\')):\r\n if loop == 0:\r\n p1, p2 = tuple(line.split(\'/\'))\r\n print(green(p1) + grayscale[14](\'/\') + cyan(p2))\r\n else:\r\n pos = line.find(\':\')\r\n print(grayscale[14](line[:pos + 1]) + cyan(line[pos + 1:]))\r\n\r\n print()\r\n\r\n # body\r\n if show_body:\r\n body_limit = 1024\r\n with open(bodyf.name, \'r\') as f:\r\n body = f.read().strip()\r\n body_len = len(body)\r\n\r\n if body_len > body_limit:\r\n print(body[:body_limit] + cyan(\'...\'))\r\n print()\r\n s = \'{} is truncated ({} out of {})\'.format(green(\'Body\'), body_limit, body_len)\r\n if save_body:\r\n s += \', stored in: {}\'.format(bodyf.name)\r\n print(s)\r\n else:\r\n print(body)\r\n else:\r\n if save_body:\r\n print(\'{} stored in: {}\'.format(green(\'Body\'), bodyf.name))\r\n\r\n # remove body file\r\n if not save_body:\r\n lg.debug(\'rm body file %s\', bodyf.name)\r\n os.remove(bodyf.name)\r\n\r\n # print stat\r\n if url.startswith(\'https://\'):\r\n template = https_template\r\n else:\r\n template = http_template\r\n\r\n # colorize template first line\r\n tpl_parts = template.split(\'\\n\')\r\n tpl_parts[0] = grayscale[16](tpl_parts[0])\r\n template = \'\\n\'.join(tpl_parts)\r\n\r\n def fmta(s):\r\n return cyan(\'{:^7}\'.format(str(s) + \'ms\'))\r\n\r\n def fmtb(s):\r\n return cyan(\'{:<7}\'.format(str(s) + \'ms\'))\r\n\r\n stat = template.format(\r\n # a\r\n a0000=fmta(d[\'range_dns\']),\r\n a0001=fmta(d[\'range_connection\']),\r\n a0002=fmta(d[\'range_ssl\']),\r\n a0003=fmta(d[\'range_server\']),\r\n a0004=fmta(d[\'range_transfer\']),\r\n # b\r\n b0000=fmtb(d[\'time_namelookup\']),\r\n b0001=fmtb(d[\'time_connect\']),\r\n b0002=fmtb(d[\'time_pretransfer\']),\r\n b0003=fmtb(d[\'time_starttransfer\']),\r\n b0004=fmtb(d[\'time_total\']),\r\n )\r\n print()\r\n print(stat)\r\n\r\n # speed, originally bytes per second\r\n if show_speed:\r\n print(\'speed_download: {:.1f} KiB/s, speed_upload: {:.1f} KiB/s\'.format(\r\n d[\'speed_download\'] / 1024, d[\'speed_upload\'] / 1024))\r\n\r\n\r\nif __name__ == \'__main__\':\r\n main()', 1); +INSERT INTO `onefile` VALUES (15, 'py2sec', 'Python', '一款轻量级跨平台 Python “加密”、加速的脚本工具', 12, 'https://github.com/cckuailong/py2sec', 8600, '# -*- coding: utf-8 -*-\r\nimport getopt\r\nimport os\r\nimport platform\r\nimport shutil\r\nimport subprocess\r\nimport sys\r\n\r\n\r\nclass OptionsOfBuild():\r\n def __init__(self):\r\n self.pyVer = \'\'\r\n self.fileName = \'\'\r\n self.rootName = \'\'\r\n self.excludeFiles = []\r\n self.nthread = \'1\'\r\n self.quiet = \"False\"\r\n self.release = False\r\n\r\n\r\nisWindows = True if platform.system() == \'Windows\' else False\r\n\r\npy2sec_version = (\'0\', \'3\', \'1\')\r\n\r\nHELP_TEXT = \'\'\'\r\npy2sec is a Cross-Platform, Fast and Flexible tool to compile the .py to .so(Linux and Mac) or .pdy(Win).\r\nYou can use it to hide the source code of py\r\nIt can be called by the main func as \"from module import * \"\r\npy2sec can be used in the environment of python2 or python3\r\n\r\nUsage: python py2sec.py [options] ...\r\n\r\nOptions:\r\n -v, --version Show the version of the py2sec\r\n -h, --help Show the help info\r\n -p, --python Python version, default is based on the version of python you bind with command \"python\" \r\n Example: -p 3 (means you tends to encrypt python3)\r\n -d, --directory Directory of your project (if use -d, you encrypt the whole directory)\r\n -f, --file File to be transfered (if use -f, you only encrypt one file)\r\n -m, --maintain List the file or the directory you don\'t want to transfer\r\n Note: The directories should be suffix by path separate char (\'\\\\\' in Windows or \'/\'), \r\n and must be the relative path to -d\'s value\r\n Example: -m setup.py,mod/__init__.py,exclude_dir/\r\n -x, --nthread number of parallel thread to build jobs\r\n -q --quiet Quiet Mode, Default: False\r\n -r --release Release Mode, clear all the tmp files, only output the result, Default: False\r\n\r\n\r\nExample:\r\n python py2sec.py -f test.py\r\n python py2sec.py -f example/test1.py -r\r\n python py2sec.py -d example/ -m test1.py,bbb/\r\n\r\n # some OS use command \"python3\" to run python3, like Ubuntu, you can use -p to solve it\r\n python3 py2sec.py -p 3 -d example/\r\n\'\'\'\r\n\r\nbuildingScript_fileName = \'tmp_py2sec_build.py\'\r\nbuildingScript_template_fileName = \'py2sec_build.py.template\'\r\n\r\n\r\ndef getFiles_inDir(dir_path,\r\n includeSubfolder=True,\r\n path_type=0,\r\n ext_names=\"*\"):\r\n \'\'\'获得指定目录下的所有文件,\r\n :param dir_path: 指定的目录路径\r\n :param includeSubfolder: 是否包含子文件夹里的文件,默认 True\r\n :param path_type: 返回的文件路径形式\r\n 0 绝对路径,默认值\r\n 1 相对路径\r\n 2 文件名\r\n :param ext_names: \"*\" | string | list\r\n 可以指定文件扩展名类型,支持以列表形式指定多个扩展名。默认为 \"*\",即所有扩展名。\r\n 举例:\".txt\" 或 [\".jpg\",\".png\"]\r\n\r\n :return: 以 yield 方式返回结果\r\n\r\n Updated:\r\n 2020-04-21\r\n Author:\r\n nodewee (https://nodewee.github.io)\r\n \'\'\'\r\n\r\n # ext_names\r\n if type(ext_names) is str:\r\n if ext_names != \"*\":\r\n ext_names = [ext_names]\r\n # lower ext name letters\r\n if type(ext_names) is list:\r\n for i in range(len(ext_names)):\r\n ext_names[i] = ext_names[i].lower()\r\n\r\n def willKeep_thisFile_by_ExtName(file_name):\r\n if type(ext_names) is list:\r\n if file_name[0] == \'.\':\r\n file_ext = file_name\r\n else:\r\n file_ext = os.path.splitext(file_name)[1]\r\n #\r\n if file_ext.lower() not in ext_names:\r\n return False\r\n else:\r\n return True\r\n return True\r\n\r\n if includeSubfolder:\r\n len_of_inpath = len(dir_path)\r\n for root, dirs, files in os.walk(dir_path):\r\n for file_name in files:\r\n if not willKeep_thisFile_by_ExtName(file_name):\r\n continue\r\n if path_type == 0: # absolute path\r\n yield os.path.join(root, file_name)\r\n elif path_type == 1: # relative path\r\n yield os.path.join(\r\n root[len_of_inpath:].lstrip(os.path.sep), file_name)\r\n else: # filen ame\r\n yield file_name\r\n else:\r\n for file_name in os.listdir(dir_path):\r\n filepath = os.path.join(dir_path, file_name)\r\n if os.path.isfile(filepath):\r\n #\r\n if not willKeep_thisFile_by_ExtName(file_name):\r\n continue\r\n #\r\n if path_type == 0:\r\n yield filepath\r\n else:\r\n yield file_name\r\n\r\n\r\ndef makeDirs(dirpath):\r\n \'\'\'\r\n 创建目录\r\n 支持多级目录,若目录已存在自动忽略\r\n Updated: 2020-02-27\r\n Author: nodewee (https://nodewee.github.io)\r\n \'\'\'\r\n\r\n dirpath = dirpath.strip().rstrip(os.path.sep)\r\n\r\n if dirpath:\r\n if not os.path.exists(dirpath):\r\n os.makedirs(dirpath)\r\n\r\n\r\ndef getCommandOptions(opts):\r\n try:\r\n options, _ = getopt.getopt(sys.argv[1:], \"vhp:d:f:m:x:qr\", [\r\n \"version\", \"help\", \"python=\", \"directory=\", \"file=\", \"maintain=\",\r\n \"nthread=\", \"quiet\", \"release\"\r\n ])\r\n except getopt.GetoptError:\r\n print(\"Get options Error\")\r\n print(HELP_TEXT)\r\n sys.exit(1)\r\n\r\n for key, value in options:\r\n if key in [\"-h\", \"--help\"]:\r\n print(HELP_TEXT)\r\n sys.exit(0)\r\n elif key in [\"-v\", \"--version\"]:\r\n print(\"py2sec version {0}\".format(\'.\'.join(py2sec_version)))\r\n sys.exit(0)\r\n elif key in [\"-p\", \"--python\"]:\r\n opts.pyVer = value\r\n elif key in [\"-d\", \"--directory\"]:\r\n if opts.fileName:\r\n print(\"Canceled. Do not use -d -f at the same time\")\r\n sys.exit(1)\r\n if value[-1] == \'/\':\r\n opts.rootName = value[:-1]\r\n else:\r\n opts.rootName = value\r\n elif key in [\"-f\", \"--file\"]:\r\n if opts.rootName:\r\n print(\"Canceled. Do not use -d -f at the same time\")\r\n sys.exit(1)\r\n opts.fileName = value\r\n elif key in [\"-m\", \"--maintain\"]:\r\n for path_assign in value.split(\",\"):\r\n if not path_assign[-1:] in [\'/\', \'\\\\\']: # if last char is not a path sep, consider it\'s assign a file\r\n opts.excludeFiles.append(path_assign)\r\n else: # assign a dir\r\n assign_dir = path_assign.strip(\'/\').strip(\'\\\\\')\r\n tmp_dir = os.path.join(opts.rootName, assign_dir)\r\n files = getFiles_inDir(dir_path=tmp_dir,\r\n includeSubfolder=True,\r\n path_type=1)\r\n #\r\n for file in files:\r\n fpath = os.path.join(assign_dir, file)\r\n opts.excludeFiles.append(fpath)\r\n\r\n elif key in [\"-x\", \"--nthread\"]:\r\n opts.nthread = value\r\n elif key in [\"-q\", \"--quiet\"]:\r\n opts.quiet = \"True\"\r\n elif key in [\"-r\", \"--release\"]:\r\n opts.release = True\r\n\r\n return opts\r\n\r\n\r\ndef getEncryptFileList(opts):\r\n will_compile_files = []\r\n\r\n #\r\n if opts.rootName != \'\':\r\n if not os.path.exists(opts.rootName):\r\n print(\"No such Directory, please check or use the Absolute Path\")\r\n sys.exit(1)\r\n\r\n #\r\n pyfiles = getFiles_inDir(dir_path=opts.rootName,\r\n includeSubfolder=True,\r\n path_type=1,\r\n ext_names=\'.py\')\r\n # exclude __init__.py file\r\n pyfiles = [pyfile for pyfile in pyfiles if not pyfile.endswith(\'__init__.py\')]\r\n # filter maintain files\r\n tmp_files = list(set(pyfiles) - set(opts.excludeFiles))\r\n will_compile_files = []\r\n for file in tmp_files:\r\n will_compile_files.append(os.path.join(opts.rootName, file))\r\n\r\n #\r\n if opts.fileName != \'\':\r\n if opts.fileName.endswith(\".py\"):\r\n will_compile_files.append(opts.fileName)\r\n else:\r\n print(\"Make sure you give the right name of py file\")\r\n\r\n return will_compile_files\r\n\r\n\r\ndef genSetup(opts, will_compile_files):\r\n if os.path.exists(buildingScript_fileName):\r\n os.remove(buildingScript_fileName)\r\n with open(buildingScript_template_fileName, \"r\") as f:\r\n template = f.read()\r\n files = \'\", r\"\'.join(will_compile_files)\r\n cont = template % (files, opts.pyVer, opts.nthread, opts.quiet)\r\n with open(buildingScript_fileName, \"w\") as f:\r\n f.write(cont)\r\n\r\n\r\ndef clearBuildFolders():\r\n if os.path.isdir(\"build\"):\r\n shutil.rmtree(\"build\")\r\n if os.path.isdir(\"tmp_build\"):\r\n shutil.rmtree(\"tmp_build\")\r\n if os.path.isdir(\"result\"):\r\n shutil.rmtree(\"result\")\r\n\r\n\r\ndef clearTmpFiles():\r\n if os.path.isdir(\"build\"):\r\n shutil.rmtree(\"build\")\r\n if os.path.isdir(\"tmp_build\"):\r\n shutil.rmtree(\"tmp_build\")\r\n if os.path.isfile(\"tmp_py2sec_build.py\"):\r\n os.remove(\"tmp_py2sec_build.py\")\r\n\r\n\r\ndef pyEncrypt(opts):\r\n # prepare folders\r\n makeDirs(\'build\')\r\n makeDirs(\'tmp_build\')\r\n\r\n if opts.quiet == \"True\":\r\n log = \"> log.txt\"\r\n else:\r\n log = \"\"\r\n cmd = \" {0} build_ext {1}\".format(buildingScript_fileName, log)\r\n if opts.pyVer == \'\':\r\n cmd = \'python\' + cmd\r\n else:\r\n cmd = \'python\' + opts.pyVer + cmd\r\n if not isWindows:\r\n print(\'> pyEncrypt\')\r\n print(cmd)\r\n p = subprocess.Popen(cmd, shell=True, stderr=subprocess.STDOUT)\r\n code = p.wait()\r\n if code:\r\n print(\"\\nPy2Sec Encrypt Encounter Error\")\r\n sys.exit(1)\r\n\r\n\r\ndef genProject(opts, will_compile_files):\r\n makeDirs(\'result\')\r\n for file in getFiles_inDir(\'build\', True, 1, [\'.so\', \'.pyd\']):\r\n src_path = os.path.join(\'build\', file)\r\n mid_path = os.path.sep.join(file.split(os.path.sep)[1:-1])\r\n file_name_parts = os.path.basename(src_path).split(\'.\')\r\n file_name = \'.\'.join([file_name_parts[0]] + file_name_parts[-1:])\r\n dest_path = os.path.join(\'result\', mid_path, file_name)\r\n makeDirs(os.path.dirname(dest_path))\r\n shutil.copy(src_path, dest_path)\r\n # 非编译文件靠谱至生成库路径\r\n not_compile_files = get_not_compile_files(opts, will_compile_files)\r\n for not_compile_file in not_compile_files:\r\n dest_path = os.path.join(\'result\', not_compile_file)\r\n filepath, filename = os.path.split(dest_path)\r\n makeDirs(filepath)\r\n shutil.copyfile(not_compile_file, dest_path)\r\n\r\n if opts.release:\r\n clearTmpFiles()\r\n print(\"\\nPy2Sec Encrypt Finished\")\r\n\r\n\r\ndef get_not_compile_files(opts, will_compile_files):\r\n \"\"\"获取非编译文件\"\"\"\r\n files = getFiles_inDir(dir_path=opts.rootName,\r\n includeSubfolder=True,\r\n path_type=1,\r\n ext_names=\'*\')\r\n files = [os.path.join(opts.rootName, file) for file in files if not file.endswith(\'.pyc\')]\r\n not_compile_files = list(set(files) - set(will_compile_files))\r\n return not_compile_files\r\n\r\n\r\nif __name__ == \"__main__\":\r\n opts = getCommandOptions(OptionsOfBuild())\r\n will_compile_files = getEncryptFileList(opts)\r\n clearBuildFolders()\r\n if not isWindows:\r\n genSetup(opts, will_compile_files)\r\n pyEncrypt(opts)\r\n else: # Windows OS\r\n for file in will_compile_files:\r\n genSetup(opts, [file])\r\n pyEncrypt(opts)\r\n\r\n genProject(opts, will_compile_files)', 1); +INSERT INTO `onefile` VALUES (16, 'tomato-clock', 'Python', 'Python 写的命令行番茄工作法定时器', 12, 'https://github.com/coolcode/tomato-clock', 8400, '#!/usr/bin/env python3\r\n# -*- coding: utf-8 -*-\r\n\r\n# Pomodoro 番茄工作法 https://en.wikipedia.org/wiki/Pomodoro_Technique\r\n# ====== 🍅 Tomato Clock =======\r\n# ./tomato.py # start a 25 minutes tomato clock + 5 minutes break\r\n# ./tomato.py -t # start a 25 minutes tomato clock\r\n# ./tomato.py -t # start a minutes tomato clock\r\n# ./tomato.py -b # take a 5 minutes break\r\n# ./tomato.py -b # take a minutes break\r\n# ./tomato.py -h # help\r\n\r\n\r\nimport sys\r\nimport time\r\nimport subprocess\r\n\r\nWORK_MINUTES = 25\r\nBREAK_MINUTES = 5\r\n\r\n\r\ndef main():\r\n try:\r\n if len(sys.argv) <= 1:\r\n print(f\'🍅 tomato {WORK_MINUTES} minutes. Ctrl+C to exit\')\r\n tomato(WORK_MINUTES, \'It is time to take a break\')\r\n print(f\'🛀 break {BREAK_MINUTES} minutes. Ctrl+C to exit\')\r\n tomato(BREAK_MINUTES, \'It is time to work\')\r\n\r\n elif sys.argv[1] == \'-t\':\r\n minutes = int(sys.argv[2]) if len(sys.argv) > 2 else WORK_MINUTES\r\n print(f\'🍅 tomato {minutes} minutes. Ctrl+C to exit\')\r\n tomato(minutes, \'It is time to take a break\')\r\n\r\n elif sys.argv[1] == \'-b\':\r\n minutes = int(sys.argv[2]) if len(sys.argv) > 2 else BREAK_MINUTES\r\n print(f\'🛀 break {minutes} minutes. Ctrl+C to exit\')\r\n tomato(minutes, \'It is time to work\')\r\n\r\n elif sys.argv[1] == \'-h\':\r\n help()\r\n\r\n else:\r\n help()\r\n\r\n except KeyboardInterrupt:\r\n print(\'\\n👋 goodbye\')\r\n except Exception as ex:\r\n print(ex)\r\n exit(1)\r\n\r\n\r\ndef tomato(minutes, notify_msg):\r\n start_time = time.perf_counter()\r\n while True:\r\n diff_seconds = int(round(time.perf_counter() - start_time))\r\n left_seconds = minutes * 60 - diff_seconds\r\n if left_seconds <= 0:\r\n print(\'\')\r\n break\r\n\r\n countdown = \'{}:{} ⏰\'.format(int(left_seconds / 60), int(left_seconds % 60))\r\n duration = min(minutes, 25)\r\n progressbar(diff_seconds, minutes * 60, duration, countdown)\r\n time.sleep(1)\r\n\r\n notify_me(notify_msg)\r\n\r\n\r\ndef progressbar(curr, total, duration=10, extra=\'\'):\r\n frac = curr / total\r\n filled = round(frac * duration)\r\n print(\'\\r\', \'🍅\' * filled + \'--\' * (duration - filled), \'[{:.0%}]\'.format(frac), extra, end=\'\')\r\n\r\n\r\ndef notify_me(msg):\r\n \'\'\'\r\n # macos desktop notification\r\n terminal-notifier -> https://github.com/julienXX/terminal-notifier#download\r\n terminal-notifier -message \r\n\r\n # ubuntu desktop notification\r\n notify-send\r\n\r\n # voice notification\r\n say -v \r\n lang options:\r\n - Daniel: British English\r\n - Ting-Ting: Mandarin\r\n - Sin-ji: Cantonese\r\n \'\'\'\r\n\r\n print(msg)\r\n try:\r\n if sys.platform == \'darwin\':\r\n # macos desktop notification\r\n subprocess.run([\'terminal-notifier\', \'-title\', \'🍅\', \'-message\', msg])\r\n subprocess.run([\'say\', \'-v\', \'Daniel\', msg])\r\n elif sys.platform.startswith(\'linux\'):\r\n # ubuntu desktop notification\r\n subprocess.Popen([\"notify-send\", \'🍅\', msg])\r\n else:\r\n # windows?\r\n # TODO: windows notification\r\n pass\r\n\r\n except:\r\n # skip the notification error\r\n pass\r\n\r\n\r\ndef help():\r\n appname = sys.argv[0]\r\n appname = appname if appname.endswith(\'.py\') else \'tomato\' # tomato is pypi package\r\n print(\'====== 🍅 Tomato Clock =======\')\r\n print(f\'{appname} # start a {WORK_MINUTES} minutes tomato clock + {BREAK_MINUTES} minutes break\')\r\n print(f\'{appname} -t # start a {WORK_MINUTES} minutes tomato clock\')\r\n print(f\'{appname} -t # start a minutes tomato clock\')\r\n print(f\'{appname} -b # take a {BREAK_MINUTES} minutes break\')\r\n print(f\'{appname} -b # take a minutes break\')\r\n print(f\'{appname} -h # help\')\r\n\r\n\r\nif __name__ == \"__main__\":\r\n main()', 1); +INSERT INTO `onefile` VALUES (17, 'share', 'Python', '基于 HTTP 协议的文件分享工具', 12, 'https://github.com/beavailable/share', 8200, '#!/bin/env python3\r\nimport sys\r\nimport signal\r\nimport os\r\nimport argparse\r\nimport functools\r\nfrom http.server import ThreadingHTTPServer, BaseHTTPRequestHandler, HTTPStatus\r\nfrom http import cookies\r\nfrom urllib import parse\r\nimport html\r\nimport mimetypes\r\nimport shutil\r\nimport base64\r\nimport time\r\nimport io\r\nimport stat\r\nimport re\r\nimport socket\r\n\r\n\r\nclass ShareServer(ThreadingHTTPServer):\r\n\r\n def __init__(self, *args, **kwargs):\r\n if is_windows():\r\n self._print_error = self._print_error_windows\r\n else:\r\n self._print_error = self._print_error_unix\r\n super().__init__(*args, **kwargs)\r\n\r\n def handle_error(self, request, client_address):\r\n year, month, day, hh, mm, ss, x, y, z = time.localtime()\r\n t, value, traceback = sys.exc_info()\r\n self._print_error(f\'{year:04}/{month:02}/{day:02} {hh:02}:{mm:02}:{ss:02} - {client_address[0]}:{client_address[1]} - {t.__name__}: {value}\')\r\n\r\n def _print_error_windows(self, msg):\r\n sys.stderr.write(f\'{msg}\\n\')\r\n\r\n def _print_error_unix(self, msg):\r\n sys.stderr.write(f\'\\033[33m{msg}\\033[0m\\n\')\r\n\r\n\r\nclass BaseHandler(BaseHTTPRequestHandler):\r\n\r\n protocol_version = \'HTTP/1.1\'\r\n ico = base64.b64decode(\'AAABAAMAMDAAAAEAIACoJQAANgAAACAgAAABACAAqBAAAN4lAAAQEAAAAQAgAGgEAACGNgAAKAAAADAAAABgAAAAAQAgAAAAAAAAJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPAFxszwhcbM8U3GzPGZxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8ZnGzPFNxszwhcbM8AQAAAAAAAAAAAAAAAAAAAAAAAAAAcbM8B3GzPGxxszzccbM8/HGzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPPxxszzccbM8bHGzPAcAAAAAAAAAAAAAAAAAAAAAcbM8bHGzPPZxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM89nGzPGwAAAAAAAAAAAAAAABxszwhcbM823GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPNtxszwhAAAAAAAAAABxszxTcbM8+3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPPtxszxTAAAAAAAAAABxszxmcbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxmAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8+3GzPOFxszzHcbM8x3GzPOFxszz8cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszzccbM8bHGzPCJxszwMcbM8DXGzPCJxszxscbM83XGzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPNNxszw3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcbM8OHGzPNRxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM88XGzPE0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPE5xszzycbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8s3GzPAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPAlxszy0cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8fQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxszx+cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8bgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxszxvcbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz+cbM8cwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxszyFcbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPOFxszx4cbM8FQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPBBxszzCcbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz0cbM8onGzPC9xszwBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPGZxszz5cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP5xszzccbM8mXGzPHFxszxxcbM8mHGzPNxxszz+cbM8/XGzPMdxszxScbM8CAAAAAAAAAAAcbM8AXGzPDJxszxDcbM8BAAAAAAAAAAAAAAAAAAAAABxszwDcbM8V3GzPOZxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8+3GzPKBxszwkcbM8AQAAAAAAAAAAcbM8AXGzPCVxszyTcbM8fHGzPBcAAAAAAAAAAAAAAABxszwXcbM8enGzPORxszzvcbM8lXGzPEJxszwkcbM8JHGzPENxszyVcbM873GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8oHGzPA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxszwEcbM8AgAAAAAAAAAAcbM8B3GzPFFxszzGcbM8/XGzPP9xszz/cbM8/3GzPPVxszzlcbM85XGzPPVxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszzdcbM8JwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPAFxszwucbM8oXGzPPRxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszyacbM8AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPFRxszzicbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPHRxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPHRxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszyacbM8AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPFNxszzicbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszzdcbM8JwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPAFxszwucbM8oHGzPPNxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8n3GzPA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxszwEcbM8AgAAAAAAAAAAcbM8B3GzPFFxszzGcbM8/XGzPP9xszz/cbM8/3GzPPVxszzlcbM85XGzPPVxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8+3GzPKBxszwkcbM8AQAAAAAAAAAAcbM8AXGzPCVxszyTcbM8fHGzPBcAAAAAAAAAAAAAAABxszwWcbM8enGzPORxszzvcbM8lXGzPEJxszwkcbM8JHGzPENxszyVcbM873GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP5xszzccbM8mXGzPHFxszxxcbM8mHGzPNxxszz+cbM8/XGzPMdxszxScbM8CAAAAAAAAAAAcbM8AXGzPDJxszxCcbM8BAAAAAAAAAAAAAAAAAAAAABxszwDcbM8VnGzPOZxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz0cbM8onGzPC9xszwBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPGZxszz5cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPOJxszx4cbM8FQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPBBxszzCcbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz+cbM8cwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxszyFcbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8bgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxszxvcbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8fQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxszx+cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8s3GzPAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPAlxszy0cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM88XGzPE0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPE5xszzycbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPNNxszw3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcbM8OHGzPNRxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszzccbM8bHGzPCJxszwMcbM8DXGzPCJxszxscbM83XGzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8+3GzPOFxszzHcbM8x3GzPOFxszz8cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxmcbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxmAAAAAAAAAABxszxTcbM8+3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPPtxszxTAAAAAAAAAABxszwhcbM823GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPNtxszwhAAAAAAAAAAAAAAAAcbM8bHGzPPZxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM89nGzPGwAAAAAAAAAAAAAAAAAAAAAcbM8B3GzPGxxszzccbM8/HGzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPPxxszzccbM8bHGzPAcAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPAFxszwhcbM8U3GzPGZxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8ZnGzPFNxszwhcbM8AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////wAA////////AADwAAAAAA8AAOAAAAAABwAAwAAAAAADAADAAAAAAAMAAMAAAAAAAwAAwAAAAAADAADAAAAAAAMAAMAAAAfgAwAAwAAAD/ADAADAAAAf+AMAAMAAAB/4AwAAwAAAP/wDAADAAAA//AMAAMAAAD/4AwAAwAAAf/gDAADAAAD/+AMAAMABg//wAwAAwAfv48ADAADAD/+AAAMAAMAf/gAAAwAAwB/8AAADAADAP/wAAAMAAMA//AAAAwAAwB/8AAADAADAH/4AAAMAAMAP/4AAAwAAwAfv48ADAADAAYP/8AMAAMAAAP/4AwAAwAAAf/gDAADAAAA/+AMAAMAAAD/8AwAAwAAAP/wDAADAAAAf+AMAAMAAAB/4AwAAwAAAD/ADAADAAAAH4AMAAMAAAAAAAwAAwAAAAAADAADAAAAAAAMAAMAAAAAAAwAAwAAAAAADAADgAAAAAAcAAPAAAAAADwAA////////AAD///////8AACgAAAAgAAAAQAAAAAEAIAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPAFxszwOcbM8GXGzPBlxszwZcbM8GXGzPBlxszwZcbM8GXGzPBlxszwZcbM8GXGzPBlxszwZcbM8GXGzPBlxszwZcbM8GXGzPBlxszwZcbM8GXGzPBlxszwZcbM8GXGzPBlxszwZcbM8DnGzPAEAAAAAAAAAAAAAAABxszwHcbM8ZHGzPMFxszzUcbM81HGzPNRxszzUcbM81HGzPNRxszzUcbM81HGzPNRxszzUcbM81HGzPNRxszzUcbM81HGzPNRxszzUcbM81HGzPNRxszzUcbM81HGzPNRxszzUcbM81HGzPNRxszzBcbM8ZHGzPAcAAAAAAAAAAHGzPGRxszz2cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz2cbM8ZAAAAABxszwNcbM8v3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszy/cbM8DXGzPBhxszzUcbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPNRxszwYcbM8GXGzPNRxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPPlxszzdcbM80HGzPOdxszz+cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM81HGzPBlxszwZcbM81HGzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszzjcbM8Z3GzPBxxszwRcbM8K3GzPJRxszz4cbM8/3GzPP9xszz/cbM8/3GzPP9xszzUcbM8GXGzPBlxszzUcbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM883GzPFcAAAAAAAAAAAAAAAAAAAAAcbM8CXGzPJhxszz/cbM8/3GzPP9xszz/cbM8/3GzPNRxszwZcbM8GXGzPNRxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszy8cbM8CwAAAAAAAAAAAAAAAAAAAAAAAAAAcbM8NHGzPO1xszz/cbM8/3GzPP9xszz/cbM81HGzPBlxszwZcbM81HGzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPJwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxszwacbM823GzPP9xszz/cbM8/3GzPP9xszzUcbM8GXGzPBlxszzUcbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz2cbM8ewAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPCpxszzncbM8/3GzPP9xszz/cbM8/3GzPNRxszwZcbM8GXGzPNRxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPPxxszz+cbM8/3GzPP9xszz9cbM8yHGzPFJxszwIAAAAAAAAAAAAAAAAAAAAAAAAAABxszwBcbM8fXGzPP9xszz/cbM8/3GzPP9xszz/cbM81HGzPBlxszwZcbM81HGzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPNZxszx6cbM8UXGzPGRxszy3cbM84HGzPHxxszwXAAAAAHGzPBlxszxtcbM8QnGzPAhxszwCcbM8EXGzPG1xszztcbM8/3GzPP9xszz/cbM8/3GzPP9xszzUcbM8GXGzPBlxszzUcbM8/3GzPP9xszz/cbM8/3GzPP9xszzKcbM8KwAAAAAAAAAAAAAAAHGzPBBxszwkcbM8AnGzPAhxszxTcbM8yHGzPP1xszztcbM8unGzPKlxszzLcbM8+HGzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPNRxszwZcbM8GXGzPNRxszz/cbM8/3GzPP9xszz/cbM893GzPFIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxszwscbM8o3GzPPRxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM81HGzPBlxszwZcbM81HGzPP9xszz/cbM8/3GzPP9xszzfcbM8HgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPJlxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszzUcbM8GXGzPBlxszzUcbM8/3GzPP9xszz/cbM8/3GzPN9xszweAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcbM8mXGzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPNRxszwZcbM8GXGzPNRxszz/cbM8/3GzPP9xszz/cbM893GzPFIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxszwscbM8o3GzPPRxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM81HGzPBlxszwZcbM81HGzPP9xszz/cbM8/3GzPP9xszz/cbM8ynGzPCsAAAAAAAAAAAAAAABxszwQcbM8JHGzPAJxszwIcbM8U3GzPMhxszz9cbM87XGzPLpxszypcbM8y3GzPPhxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszzUcbM8GXGzPBlxszzUcbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM81nGzPHlxszxRcbM8ZHGzPLdxszzgcbM8fHGzPBcAAAAAcbM8GXGzPG1xszxCcbM8CHGzPAJxszwRcbM8bXGzPO1xszz/cbM8/3GzPP9xszz/cbM8/3GzPNRxszwZcbM8GXGzPNRxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPPxxszz+cbM8/3GzPP9xszz9cbM8yHGzPFNxszwIAAAAAAAAAAAAAAAAAAAAAAAAAABxszwBcbM8fXGzPP9xszz/cbM8/3GzPP9xszz/cbM81HGzPBlxszwZcbM81HGzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM89nGzPHsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxszwqcbM853GzPP9xszz/cbM8/3GzPP9xszzUcbM8GXGzPBlxszzUcbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPBpxszzbcbM8/3GzPP9xszz/cbM8/3GzPNRxszwZcbM8GXGzPNRxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszy8cbM8CwAAAAAAAAAAAAAAAAAAAAAAAAAAcbM8NHGzPO1xszz/cbM8/3GzPP9xszz/cbM81HGzPBlxszwZcbM81HGzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPPNxszxXAAAAAAAAAAAAAAAAAAAAAHGzPAlxszyYcbM8/3GzPP9xszz/cbM8/3GzPP9xszzUcbM8GXGzPBlxszzUcbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPONxszxncbM8HHGzPBFxszwrcbM8lHGzPPhxszz/cbM8/3GzPP9xszz/cbM8/3GzPNRxszwZcbM8GXGzPNRxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPPlxszzdcbM80HGzPOdxszz+cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM81HGzPBlxszwYcbM81HGzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszzUcbM8GHGzPA1xszy/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPL9xszwNAAAAAHGzPGRxszz2cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz2cbM8ZAAAAAAAAAAAcbM8B3GzPGRxszzBcbM81HGzPNRxszzUcbM81HGzPNRxszzUcbM81HGzPNRxszzUcbM81HGzPNRxszzUcbM81HGzPNRxszzUcbM81HGzPNRxszzUcbM81HGzPNRxszzUcbM81HGzPNRxszzUcbM8wXGzPGRxszwHAAAAAAAAAAAAAAAAcbM8AXGzPA5xszwZcbM8GXGzPBlxszwZcbM8GXGzPBlxszwZcbM8GXGzPBlxszwZcbM8GXGzPBlxszwZcbM8GXGzPBlxszwZcbM8GXGzPBlxszwZcbM8GXGzPBlxszwZcbM8GXGzPBlxszwOcbM8AQAAAAAAAAAA/////+AAAAfAAAADgAAAAYAAAAGAAAABgAAeAYAAPwGAAD+BgAA/gYAAf4GAAP+BgHP/AYD/gAGB/gABgfwAAYH8AAGB/gABgP+AAYBz/wGAAP+BgAB/gYAAP4GAAD+BgAA/AYAAHgGAAAABgAAAAYAAAAHAAAAD4AAAB/////8oAAAAEAAAACAAAAABACAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAcbM8CnGzPFxxszyIcbM8iHGzPIhxszyIcbM8iHGzPIhxszyIcbM8iHGzPIhxszyIcbM8iHGzPIhxszxccbM8CnGzPFxxszzxcbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM88XGzPFxxszyHcbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz3cbM83XGzPO5xszz/cbM8/3GzPP9xszyHcbM8iHGzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz1cbM8cXGzPBpxszxDcbM82HGzPP9xszz/cbM8iHGzPIhxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8y3GzPBEAAAAAAAAAAHGzPIdxszz/cbM8/3GzPIhxszyIcbM8/3GzPP9xszz/cbM8+3GzPPRxszz+cbM85XGzPG1xszwFAAAAAHGzPAVxszygcbM8/3GzPP9xszyIcbM8iHGzPP9xszz/cbM86HGzPGhxszw8cbM8bXGzPD1xszxXcbM8mXGzPGVxszyRcbM883GzPP9xszz/cbM8iHGzPIhxszz/cbM8/3GzPJEAAAAAAAAAAHGzPAVxszyPcbM893GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPIhxszyIcbM8/3GzPP9xszyRAAAAAAAAAABxszwFcbM8j3GzPPdxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszyIcbM8iHGzPP9xszz/cbM86HGzPGhxszw8cbM8bXGzPD1xszxXcbM8mXGzPGVxszyRcbM883GzPP9xszz/cbM8iHGzPIhxszz/cbM8/3GzPP9xszz7cbM89HGzPP5xszzlcbM8bXGzPAUAAAAAcbM8BXGzPKBxszz/cbM8/3GzPIhxszyIcbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPMtxszwRAAAAAAAAAABxszyHcbM8/3GzPP9xszyIcbM8iHGzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz1cbM8cXGzPBpxszxDcbM82HGzPP9xszz/cbM8iHGzPIdxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPPdxszzdcbM87nGzPP9xszz/cbM8/3GzPIdxszxccbM88XGzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPPFxszxccbM8CnGzPFxxszyIcbM8iHGzPIhxszyIcbM8iHGzPIhxszyIcbM8iHGzPIhxszyIcbM8iHGzPIhxszxccbM8CsADAACAAQAAAAAAAABwAAAAcAAAAPAAAA+gAAAOAAAADgAAAA+gAAAA8AAAAHAAAABwAAAAAAAAgAEAAMADAAA=\')\r\n\r\n def __init__(self, *args, password=None):\r\n self._hostname = socket.gethostname()\r\n self._password = password\r\n super().__init__(*args)\r\n\r\n def do_GET(self):\r\n if self.path == \'/favicon.ico\':\r\n self.send_response(HTTPStatus.OK)\r\n self.send_content_length(len(BaseHandler.ico))\r\n self.send_content_type(\'image/x-icon\')\r\n self.end_headers()\r\n self.wfile.write(self.ico)\r\n return\r\n if not self._password or self._validate_password():\r\n self.do_get()\r\n return\r\n self.respond_ok(self._build_html_for_password())\r\n\r\n def do_POST(self):\r\n if not self._password or self._validate_password():\r\n self.do_post()\r\n return\r\n content_length = self.headers[\'Content-Length\']\r\n if not content_length or not content_length.isdecimal():\r\n self.respond_bad_request()\r\n return\r\n content_length = int(content_length)\r\n if content_length > 100:\r\n self.respond_bad_request()\r\n return\r\n data = self.rfile.read(content_length).decode()\r\n data = parse.unquote_plus(data)\r\n if data != f\'password={self._password}\':\r\n self.respond_redirect(self.path)\r\n return\r\n self.respond_redirect_cookie(self.path, f\'password={parse.quote_plus(self._password)}; path=/\')\r\n\r\n def do_get(self):\r\n self.respond_method_not_allowed()\r\n\r\n def do_post(self):\r\n self.respond_method_not_allowed()\r\n\r\n def do_multipart(self, save_dir, redirect_location):\r\n content_length = self.headers[\'Content-Length\']\r\n if not content_length or not content_length.isdecimal():\r\n self.respond_bad_request()\r\n return\r\n content_length = int(content_length)\r\n if not self._has_freespace(content_length):\r\n self.respond_internal_server_error()\r\n return\r\n content_type = self.headers[\'Content-Type\']\r\n if not content_type:\r\n self.respond_bad_request()\r\n return\r\n boundary = self._parse_boundary(content_type)\r\n if not boundary:\r\n self.respond_bad_request()\r\n return\r\n try:\r\n parser = MultipartParser(self.rfile, boundary, content_length)\r\n while parser.has_next():\r\n name = parser.next_name()\r\n if name != \'file\':\r\n self.respond_bad_request()\r\n return\r\n filename = parser.next_filename()\r\n if not filename:\r\n self.respond_bad_request()\r\n return\r\n os.makedirs(save_dir, exist_ok=True)\r\n save_dir = save_dir.rstrip(\'/\\\\\')\r\n with open(f\'{save_dir}/{filename}\', \'wb\') as f:\r\n parser.write_next_to(f)\r\n except MultipartError:\r\n self.respond_bad_request()\r\n except PermissionError:\r\n self.respond_forbidden()\r\n else:\r\n self.respond_redirect(redirect_location)\r\n\r\n def _validate_password(self):\r\n cookie = cookies.SimpleCookie(self.headers[\'Cookie\'])\r\n password = cookie.get(\'password\')\r\n return password and parse.unquote_plus(password.value) == self._password\r\n\r\n def _build_html_for_password(self):\r\n builder = HtmlBuilder()\r\n builder.start_head()\r\n builder.start_title()\r\n builder.append(self._hostname)\r\n builder.end_title()\r\n builder.start_style()\r\n builder.append(\'.container{height: 80%; display: flex; align-items: center; justify-content: center;}\')\r\n builder.end_style()\r\n builder.end_head()\r\n builder.start_body()\r\n builder.append(\'
\')\r\n builder.append(f\'
\')\r\n builder.append(\'\')\r\n builder.append(\' \')\r\n builder.append(\'
\')\r\n builder.append(\'
\')\r\n builder.end_body()\r\n return builder.build()\r\n\r\n def _has_freespace(self, need_size):\r\n path = self._dir\r\n if is_windows():\r\n path, _ = os.path.splitdrive(path)\r\n total, used, free = shutil.disk_usage(path)\r\n return free - need_size >= 1073741824\r\n\r\n def _parse_boundary(self, content_type):\r\n parts = content_type.split(\'; \')\r\n if len(parts) != 2:\r\n return None\r\n form_data, boundary = parts\r\n if form_data != \'multipart/form-data\':\r\n return None\r\n parts = boundary.split(\'=\')\r\n if len(parts) != 2:\r\n return None\r\n key, value = parts\r\n if key != \'boundary\':\r\n return None\r\n return value\r\n\r\n def send_content_length(self, content_length):\r\n self.send_header(\'Content-Length\', str(content_length))\r\n\r\n def send_content_type(self, content_type):\r\n self.send_header(\'Content-Type\', content_type)\r\n\r\n def send_location(self, location):\r\n self.send_header(\'Location\', location)\r\n\r\n def send_accept_ranges(self):\r\n self.send_header(\'Accept-Ranges\', \'bytes\')\r\n\r\n def send_content_range(self, start, end, filesize):\r\n self.send_header(\'Content-Range\', f\'bytes {start}-{end}/{filesize}\')\r\n\r\n def send_content_disposition(self, filename):\r\n filename = parse.quote(filename)\r\n self.send_header(\'Content-Disposition\', f\'attachment;filename=\"{filename}\"\')\r\n\r\n def send_cookie(self, cookie):\r\n self.send_header(\'Set-Cookie\', cookie)\r\n\r\n def respond_ok(self, html):\r\n self.send_response(HTTPStatus.OK)\r\n response = html.encode()\r\n self.send_content_length(len(response))\r\n self.send_content_type(\'text/html; charset=utf-8\')\r\n self.end_headers()\r\n self.wfile.write(response)\r\n\r\n def respond_redirect(self, location):\r\n self.send_response(HTTPStatus.SEE_OTHER)\r\n self.send_content_length(0)\r\n self.send_location(location)\r\n self.end_headers()\r\n\r\n def respond_redirect_cookie(self, location, cookie):\r\n self.send_response(HTTPStatus.SEE_OTHER)\r\n self.send_content_length(0)\r\n self.send_location(location)\r\n self.send_cookie(cookie)\r\n self.end_headers()\r\n\r\n def respond_range_not_satisfiable(self):\r\n self.close_connection = True\r\n self.send_response(HTTPStatus.REQUESTED_RANGE_NOT_SATISFIABLE)\r\n self.send_content_length(0)\r\n self.end_headers()\r\n\r\n def respond_bad_request(self):\r\n self.close_connection = True\r\n self.send_response(HTTPStatus.BAD_REQUEST)\r\n self.send_content_length(0)\r\n self.end_headers()\r\n\r\n def respond_forbidden(self):\r\n self.close_connection = True\r\n self.send_response(HTTPStatus.FORBIDDEN)\r\n self.send_content_length(0)\r\n self.end_headers()\r\n\r\n def respond_not_found(self):\r\n self.close_connection = True\r\n self.send_response(HTTPStatus.NOT_FOUND)\r\n self.send_content_length(0)\r\n self.end_headers()\r\n\r\n def respond_method_not_allowed(self):\r\n self.close_connection = True\r\n self.send_response(HTTPStatus.METHOD_NOT_ALLOWED)\r\n self.send_content_length(0)\r\n self.end_headers()\r\n\r\n def respond_internal_server_error(self):\r\n self.close_connection = True\r\n self.send_response(HTTPStatus.INTERNAL_SERVER_ERROR)\r\n self.send_content_length(0)\r\n self.end_headers()\r\n\r\n def log_request(self, code, size=None):\r\n if isinstance(code, HTTPStatus):\r\n code = code.value\r\n self.log_message(\'%s %s %s\', self.command, str(code), parse.unquote(self.path))\r\n\r\n def log_message(self, format, *args):\r\n year, month, day, hh, mm, ss, x, y, z = time.localtime()\r\n t = f\'{year:04}/{month:02}/{day:02} {hh:02}:{mm:02}:{ss:02}\'\r\n sys.stderr.write(\'%s - %s:%s - %s\\n\' % (t, self.client_address[0], self.client_address[1], format % args))\r\n\r\n\r\nclass BaseFileShareHandler(BaseHandler):\r\n\r\n def __init__(self, *args, upload=False, **kwargs):\r\n self._upload = upload\r\n self._ua_prefixes = {\'curl\', \'Wget\', \'wget2\', \'aria2\', \'Axel\'}\r\n if is_windows():\r\n self.is_hidden = self._is_hidden_windows\r\n else:\r\n self.is_hidden = self._is_hidden_unix\r\n super().__init__(*args, **kwargs)\r\n\r\n def split_path(self, path):\r\n parts = path.split(\'?\', 1)\r\n path = parts[0]\r\n params = {}\r\n if len(parts) > 1:\r\n query = parts[1]\r\n for q in query.split(\'&\'):\r\n if q:\r\n words = q.split(\'=\')\r\n if len(words) == 2:\r\n params[words[0]] = words[1]\r\n return (path, params)\r\n\r\n def respond_for_file(self, file):\r\n include_content_disposition = self._is_from_commandline()\r\n try:\r\n f = open(file, \'rb\')\r\n except PermissionError:\r\n self.respond_forbidden()\r\n return\r\n except FileNotFoundError:\r\n self.respond_not_found()\r\n return\r\n with f:\r\n filename = os.path.basename(file)\r\n filesize = os.path.getsize(file)\r\n content_type = self._guess_type(file)\r\n content_range = self.headers[\'Range\']\r\n if filesize == 0 or not content_range:\r\n self.send_response(HTTPStatus.OK)\r\n self.send_content_length(filesize)\r\n self.send_content_type(content_type)\r\n self.send_accept_ranges()\r\n if include_content_disposition:\r\n self.send_content_disposition(filename)\r\n self.end_headers()\r\n self._copy_file(f, self.wfile)\r\n return\r\n content_range = self._parse_range(content_range, filesize)\r\n if not content_range:\r\n self.respond_range_not_satisfiable()\r\n return\r\n start, end = content_range\r\n content_length = end - start + 1\r\n self.send_response(HTTPStatus.PARTIAL_CONTENT)\r\n self.send_content_length(content_length)\r\n self.send_content_type(content_type)\r\n self.send_accept_ranges()\r\n self.send_content_range(start, end, filesize)\r\n if include_content_disposition:\r\n self.send_content_disposition(filename)\r\n self.end_headers()\r\n self._copy_file_range(f, self.wfile, start, content_length)\r\n\r\n def build_html(self, path, dirs, files):\r\n if path == \'/\':\r\n title = self._hostname\r\n else:\r\n title = os.path.basename(path.rstrip(\'/\'))\r\n builder = HtmlBuilder()\r\n builder.start_head()\r\n builder.start_title()\r\n builder.append(title)\r\n builder.end_title()\r\n builder.start_style()\r\n builder.append(\'.container{height: 100%; display: flex; flex-direction: column; padding: 0 8px; overflow-wrap: break-word;}\')\r\n builder.append(\'.header{display: flex; justify-content: space-between; padding: 8px 0; font-size: x-large;}\')\r\n builder.append(\'hr{width: 100%;}\')\r\n builder.append(\'.main{flex: auto; padding: 16px 0;}\')\r\n builder.append(\'.content{width: 100%; height: 100%;}\')\r\n builder.append(\'.list-item{display: flex; justify-content: space-between; padding: 2px 0; word-break: break-all;}\')\r\n builder.append(\'.list-item:nth-child(even){background-color: #f8f8f8;}\')\r\n builder.append(\'.item-left{display: flex}\')\r\n builder.append(\'.item-right{min-width: 140px; max-width: 140px; text-align: right;}\')\r\n builder.append(\'.item-icon{flex: none; margin-right: 4px;}\')\r\n builder.append(\'.size{color: #666666;}\')\r\n builder.append(\'iframe{border: 0;}\')\r\n builder.append(\'a{color: #2965c7; text-decoration: none;}\')\r\n builder.append(\'a.hidden{color: #42a5f5;}\')\r\n builder.append(\'a:hover{color: #ff5500;}\')\r\n if self._upload:\r\n builder.append(\'.upload{background-color: #76797b; border-color: #76797b; color: white; border-radius: 16px;}\')\r\n builder.append(\'.upload:hover{background-color: #565e64; border-color: #565e64;}\')\r\n builder.append(\'.dragging{border: 4px dashed #cccccc; border-radius: 4px;}\')\r\n builder.append(\'button{cursor: pointer; border: 1px solid #cccccc; color: #333333; background-color: white; border-radius: 4px;}\')\r\n builder.append(\'button:hover{background-color: #e6e6e6;}\')\r\n builder.append(\'button:disabled{opacity: .65; pointer-events: none; user-select: none;}\')\r\n builder.append(\'.btn-view{padding: 1px 4px;}\')\r\n builder.end_style()\r\n builder.start_script()\r\n builder.append(\'function view_file(){\')\r\n builder.append(\' src = this.getAttribute(\"src\");\')\r\n builder.append(\' let frame = document.createElement(\"iframe\");\')\r\n builder.append(\' frame.setAttribute(\"src\",src);\')\r\n builder.append(\' frame.setAttribute(\"allow\",\"fullscreen\");\')\r\n builder.append(\' frame.setAttribute(\"width\",\"100%\");\')\r\n builder.append(\' frame.setAttribute(\"height\",\"100%\");\')\r\n builder.append(\' content = document.getElementById(\"content\");\')\r\n builder.append(\' content.replaceWith(frame);\')\r\n builder.append(\' document.title=src;\')\r\n builder.append(\'}\')\r\n if self._upload:\r\n builder.append(\'function on_upload_click(){\')\r\n builder.append(\' document.getElementById(\"file\").click();\')\r\n builder.append(\'}\')\r\n builder.append(\'function on_upload(){\')\r\n builder.append(\' document.getElementById(\"upload\").setAttribute(\"disabled\", \"\");\')\r\n builder.append(\' document.getElementById(\"form\").submit();\')\r\n builder.append(\'}\')\r\n builder.append(\'let drag_counter = 0;\')\r\n builder.append(\'function on_dragenter(e){\')\r\n builder.append(\' e.preventDefault();\')\r\n builder.append(\' drag_counter++;\')\r\n builder.append(\' e.currentTarget.classList.add(\"dragging\");\')\r\n builder.append(\'}\')\r\n builder.append(\'function on_dragover(e){\')\r\n builder.append(\' e.preventDefault();\')\r\n builder.append(\'}\')\r\n builder.append(\'function on_dragleave(e){\')\r\n builder.append(\' e.preventDefault();\')\r\n builder.append(\' drag_counter--;\')\r\n builder.append(\' if (drag_counter === 0) {\')\r\n builder.append(\' e.currentTarget.classList.remove(\"dragging\");\')\r\n builder.append(\' }\')\r\n builder.append(\'}\')\r\n builder.append(\'function on_drop(e){\')\r\n builder.append(\' e.preventDefault();\')\r\n builder.append(\' drag_counter = 0;\')\r\n builder.append(\' e.currentTarget.classList.remove(\"dragging\");\')\r\n builder.append(\' if (e.dataTransfer.files.length == 0) {return;}\')\r\n builder.append(\' document.getElementById(\"file\").files = e.dataTransfer.files;\')\r\n builder.append(\' on_upload();\')\r\n builder.append(\'}\')\r\n builder.append(\'function on_load() {\')\r\n builder.append(\' let btns = document.getElementsByClassName(\"btn-view\");\')\r\n builder.append(\' for (let i = 0; i < btns.length; i++) {\')\r\n builder.append(\' btns[i].onclick = view_file;\')\r\n builder.append(\' }\')\r\n if self._upload:\r\n builder.append(\'let upload = document.getElementById(\"upload\");\')\r\n builder.append(\'upload.onclick = on_upload_click;\')\r\n builder.append(\'let content = document.getElementById(\"content\");\')\r\n builder.append(\'content.ondragenter = on_dragenter;\')\r\n builder.append(\'content.ondragover = on_dragover;\')\r\n builder.append(\'content.ondragleave = on_dragleave;\')\r\n builder.append(\'content.ondrop = on_drop;\')\r\n builder.append(\'let file = document.getElementById(\"file\");\')\r\n builder.append(\'file.onchange = on_upload;\')\r\n builder.append(\'}\')\r\n builder.append(\'window.onload = on_load;\')\r\n builder.end_script()\r\n builder.end_head()\r\n builder.start_body()\r\n builder.append(\'
\')\r\n builder.append(\'
\')\r\n builder.append(\'
\')\r\n builder.append(f\'{html.escape(self._hostname)}\')\r\n p = \'\'\r\n for name in path.split(\'/\'):\r\n if name:\r\n p = f\'{p}/{name}\'\r\n builder.append(f\' / {html.escape(name)}\')\r\n builder.append(\'
\')\r\n if self._upload:\r\n builder.append(\'\')\r\n builder.append(f\'
\')\r\n builder.append(\'\')\r\n builder.append(\'
\')\r\n builder.append(\'
\')\r\n builder.append(\'
\')\r\n builder.append(\'
\')\r\n builder.append(\'
\')\r\n builder.append(\'\')\r\n builder.append(\'
\')\r\n builder.append(\'
\')\r\n builder.append(\'
\')\r\n builder.end_body()\r\n return builder.build()\r\n\r\n def cmp_path(self, s1, s2):\r\n if s1[0] == \'.\' and s2[0] != \'.\':\r\n return -1\r\n if s1[0] != \'.\' and s2[0] == \'.\':\r\n return 1\r\n len1, len2 = len(s1), len(s2)\r\n i, min_len = 0, min(len1, len2)\r\n while i < min_len:\r\n ch1, ch2 = ord(s1[i]), ord(s2[i])\r\n if 65 <= ch1 <= 90:\r\n ch1 += 32\r\n if 65 <= ch2 <= 90:\r\n ch2 += 32\r\n if ch1 == ch2:\r\n i += 1\r\n elif 48 <= ch1 <= 57 and 48 <= ch2 <= 57:\r\n num1, idx1 = self._check_number(s1, len1, i)\r\n num2, idx2 = self._check_number(s2, len2, i)\r\n if num1 != num2:\r\n return num1 - num2\r\n i = idx1\r\n else:\r\n return ch1 - ch2\r\n return len1 - len2\r\n\r\n def _is_from_commandline(self):\r\n ua = self.headers[\'User-Agent\']\r\n if not ua:\r\n return False\r\n prefix = ua.split(\'/\', 1)[0]\r\n return prefix in self._ua_prefixes\r\n\r\n def _guess_type(self, path):\r\n guess, _ = mimetypes.guess_type(path)\r\n return guess if guess else \'text/plain\'\r\n\r\n def _parse_range(self, content_range, filesize):\r\n if len(content_range) < 8 or content_range[:6] != \'bytes=\':\r\n return None\r\n parts = content_range[6:].split(\'-\')\r\n if len(parts) != 2:\r\n return None\r\n start, end = parts\r\n if not start.isdecimal():\r\n return None\r\n if end and not end.isdecimal():\r\n return None\r\n start = int(start)\r\n end = int(end) if end else filesize - 1\r\n if start > end or end >= filesize:\r\n return None\r\n return (start, end)\r\n\r\n def _copy_file(self, src, dest):\r\n while True:\r\n data = src.read(65536)\r\n if not data:\r\n return\r\n dest.write(data)\r\n\r\n def _copy_file_range(self, src, dest, start, length):\r\n src.seek(start)\r\n buf_size = 65536\r\n while length:\r\n if length <= buf_size:\r\n dest.write(src.read(length))\r\n return\r\n dest.write(src.read(buf_size))\r\n length -= buf_size\r\n\r\n def _is_hidden_windows(self, file_path):\r\n return self._is_hidden_unix(file_path) or os.stat(file_path).st_file_attributes & stat.FILE_ATTRIBUTE_HIDDEN != 0\r\n\r\n def _is_hidden_unix(self, file_path):\r\n return os.path.basename(file_path).startswith(\'.\')\r\n\r\n def _check_number(self, s, n, start):\r\n num, end = 0, start\r\n while end < n:\r\n ch = ord(s[end])\r\n if ch < 48 or ch > 57:\r\n break\r\n num = num * 10 + ch - 48\r\n end += 1\r\n return (num, end)\r\n\r\n def _format_size(self, size):\r\n lst = ((1024, \'KiB\'), (1048576, \'MiB\'), (1073741824, \'GiB\'), (1099511627776, \'TiB\'))\r\n idx = 0\r\n if size < 1048576:\r\n idx = 0\r\n elif size < 1073741824:\r\n idx = 1\r\n elif size < 1099511627776:\r\n idx = 2\r\n else:\r\n idx = 3\r\n return f\'{size/lst[idx][0]:.2f} {lst[idx][1]}\'\r\n\r\n\r\nclass FileShareHandler(BaseFileShareHandler):\r\n\r\n def __init__(self, files, *args, **kwargs):\r\n self._files = files\r\n super().__init__(*args, **kwargs)\r\n\r\n def do_get(self):\r\n path, _ = self.split_path(parse.unquote(self.path))\r\n if path == \'/\':\r\n files = []\r\n for f in self._files:\r\n size = 0\r\n try:\r\n size = os.path.getsize(f)\r\n except PermissionError:\r\n pass\r\n except FileNotFoundError:\r\n continue\r\n files.append((os.path.basename(f), self.is_hidden(f), size))\r\n files.sort(key=functools.cmp_to_key(lambda s1, s2: self.cmp_path(s1[0], s2[0])))\r\n self.respond_ok(self.build_html(path, [], files))\r\n return\r\n path = path[1:]\r\n for f in self._files:\r\n if path == os.path.basename(f):\r\n self.respond_for_file(f)\r\n return\r\n if path == \'file\' and len(self._files) == 1:\r\n self.respond_for_file(self._files[0])\r\n else:\r\n self.respond_not_found()\r\n\r\n\r\nclass DirectoryShareHandler(BaseFileShareHandler):\r\n\r\n def __init__(self, dir, all, *args, **kwargs):\r\n self._dir = dir.rstrip(\'/\\\\\') + \'/\'\r\n self._all = all\r\n if is_windows():\r\n self._contains_hidden_segment = self._contains_hidden_segment_windows\r\n else:\r\n self._contains_hidden_segment = self._contains_hidden_segment_unix\r\n super().__init__(*args, **kwargs)\r\n\r\n def do_get(self):\r\n path, _ = self.split_path(parse.unquote(self.path))\r\n if not self._all and self._contains_hidden_segment(path):\r\n self.respond_not_found()\r\n return\r\n file_path = self._dir.rstrip(\'/\') + path\r\n if os.path.isdir(file_path):\r\n try:\r\n dirs, files = self.list_dir(file_path)\r\n dirs.sort(key=functools.cmp_to_key(lambda s1, s2: self.cmp_path(s1[0], s2[0])))\r\n files.sort(key=functools.cmp_to_key(lambda s1, s2: self.cmp_path(s1[0], s2[0])))\r\n except PermissionError:\r\n self.respond_forbidden()\r\n except FileNotFoundError:\r\n self.respond_not_found()\r\n else:\r\n self.respond_ok(self.build_html(path, dirs, files))\r\n elif os.path.isfile(file_path):\r\n self.respond_for_file(file_path)\r\n else:\r\n self.respond_not_found()\r\n\r\n def do_post(self):\r\n if self._upload:\r\n self.do_multipart(self._dir.rstrip(\'/\') + parse.unquote(self.path), self.path)\r\n else:\r\n super().do_post()\r\n\r\n def list_dir(self, dir):\r\n dirs, files = [], []\r\n for name in os.listdir(dir):\r\n path = dir + name\r\n hidden = self.is_hidden(path)\r\n if self._all or not hidden:\r\n if os.path.isdir(path):\r\n items = []\r\n try:\r\n items = [f for f in os.listdir(path) if self._all or not self.is_hidden(f\'{path}/{f}\')]\r\n except:\r\n pass\r\n dirs.append((name, hidden, len(items)))\r\n else:\r\n size = 0\r\n try:\r\n size = os.path.getsize(path)\r\n except:\r\n pass\r\n files.append((name, hidden, size))\r\n return (dirs, files)\r\n\r\n def _contains_hidden_segment_windows(self, path):\r\n prefix = self._dir\r\n for segment in path.strip(\'/\').split(\'/\'):\r\n if self.is_hidden(prefix + segment):\r\n return True\r\n prefix = prefix + segment + \'/\'\r\n return False\r\n\r\n def _contains_hidden_segment_unix(self, path):\r\n return path.find(\'/.\') != -1\r\n\r\n\r\nclass FileReceiveHandler(BaseHandler):\r\n\r\n def __init__(self, dir, *args, **kwargs):\r\n self._dir = dir\r\n super().__init__(*args, **kwargs)\r\n\r\n def do_get(self):\r\n if self.path != \'/\':\r\n self.respond_redirect(\'/\')\r\n return\r\n self.respond_ok(self.build_html())\r\n\r\n def build_html(self):\r\n builder = HtmlBuilder()\r\n builder.start_head()\r\n builder.start_title()\r\n builder.append(self._hostname)\r\n builder.end_title()\r\n builder.start_style()\r\n builder.append(\'.container{height: 80%; padding: 0 8px; display: flex; align-items: center; justify-content: center;}\')\r\n builder.append(\'.upload{width: 100%; height: 60%; border: 1px solid #cccccc; border-radius: 16px; cursor: pointer; background-color: white; color: #333333; font-size: x-large;}\')\r\n builder.append(\'.upload:hover{background-color: #e6e6e6;}\')\r\n builder.append(\'.upload:disabled{opacity: .65; pointer-events: none; user-select: none;}\')\r\n builder.append(\'.dragging{border: 4px dashed #cccccc;}\')\r\n builder.end_style()\r\n builder.start_script()\r\n builder.append(\'function on_upload_click(){\')\r\n builder.append(\' document.getElementById(\"file\").click();\')\r\n builder.append(\'}\')\r\n builder.append(\'function on_upload(){\')\r\n builder.append(\' document.getElementById(\"upload\").setAttribute(\"disabled\", \"\");\')\r\n builder.append(\' document.getElementById(\"form\").submit();\')\r\n builder.append(\'}\')\r\n builder.append(\'function on_dragenter(e){\')\r\n builder.append(\' e.preventDefault();\')\r\n builder.append(\' e.currentTarget.classList.add(\"dragging\");\')\r\n builder.append(\'}\')\r\n builder.append(\'function on_dragover(e){\')\r\n builder.append(\' e.preventDefault();\')\r\n builder.append(\'}\')\r\n builder.append(\'function on_dragleave(e){\')\r\n builder.append(\' e.preventDefault();\')\r\n builder.append(\' e.currentTarget.classList.remove(\"dragging\");\')\r\n builder.append(\'}\')\r\n builder.append(\'function on_drop(e){\')\r\n builder.append(\' e.preventDefault();\')\r\n builder.append(\' e.currentTarget.classList.remove(\"dragging\");\')\r\n builder.append(\' if (e.dataTransfer.files.length == 0) {return;}\')\r\n builder.append(\' document.getElementById(\"file\").files = e.dataTransfer.files;\')\r\n builder.append(\' on_upload();\')\r\n builder.append(\'}\')\r\n builder.append(\'function on_load() {\')\r\n builder.append(\' let upload = document.getElementById(\"upload\");\')\r\n builder.append(\' upload.onclick = on_upload_click;\')\r\n builder.append(\' upload.ondragenter = on_dragenter;\')\r\n builder.append(\' upload.ondragover = on_dragover;\')\r\n builder.append(\' upload.ondragleave = on_dragleave;\')\r\n builder.append(\' upload.ondrop = on_drop;\')\r\n builder.append(\' let file = document.getElementById(\"file\");\')\r\n builder.append(\' file.onchange = on_upload;\')\r\n builder.append(\'}\')\r\n builder.append(\'window.onload = on_load;\')\r\n builder.end_script()\r\n builder.end_head()\r\n builder.start_body()\r\n builder.append(\'
\')\r\n builder.append(\'\')\r\n builder.append(\'
\')\r\n builder.end_body()\r\n return builder.build()\r\n\r\n def do_post(self):\r\n if self.path != \'/\':\r\n self.respond_bad_request()\r\n return\r\n self.do_multipart(self._dir, \'/\')\r\n\r\n\r\nclass TextShareHandler(BaseHandler):\r\n\r\n def __init__(self, text, *args, **kwargs):\r\n self._text = text\r\n super().__init__(*args, **kwargs)\r\n\r\n def do_get(self):\r\n if self.path != \'/\':\r\n self.respond_redirect(\'/\')\r\n return\r\n self.respond_ok(self.build_html())\r\n\r\n def build_html(self):\r\n builder = HtmlBuilder()\r\n builder.start_head()\r\n builder.start_title()\r\n builder.append(self._hostname)\r\n builder.end_title()\r\n builder.start_style()\r\n builder.append(\'.container{height: 100%; display: flex; flex-direction: column;}\')\r\n builder.append(\'.content{flex: auto; margin: 10% 8px; word-wrap: break-word; white-space: pre-wrap; overflow-y: auto;}\')\r\n builder.end_style()\r\n builder.start_body()\r\n builder.append(\'
\')\r\n builder.append(\'
\')\r\n        builder.append(html.escape(self._text))\r\n        builder.append(\'
\')\r\n builder.append(\'
\')\r\n builder.end_body()\r\n return builder.build()\r\n\r\n\r\nclass TextReceiveHandler(BaseHandler):\r\n\r\n def do_get(self):\r\n if self.path != \'/\':\r\n self.respond_redirect(\'/\')\r\n return\r\n self.respond_ok(self.build_html())\r\n\r\n def build_html(self):\r\n builder = HtmlBuilder()\r\n builder.start_head()\r\n builder.start_title()\r\n builder.append(self._hostname)\r\n builder.end_title()\r\n builder.start_style()\r\n builder.append(\'.container{height: 100%; display: flex; flex-direction: column;}\')\r\n builder.append(\'.content{flex: auto; margin: 10% 8px; display: flex; flex-direction: column;}\')\r\n builder.append(\'.textarea{flex: auto; width: 100%;}\')\r\n builder.append(\'.submit{width: 100%;}\')\r\n builder.end_style()\r\n builder.start_script()\r\n builder.append(\'function on_keydown(e){\')\r\n builder.append(\' if (e.key==\"Control\"){return;}\')\r\n builder.append(\' if (e.ctrlKey && e.key==\"Enter\") {\')\r\n builder.append(\' form = document.getElementById(\"form\");\')\r\n builder.append(\' if (form.reportValidity()) {\')\r\n builder.append(\' form.submit();\')\r\n builder.append(\' e.preventDefault();\')\r\n builder.append(\' e.stopPropagation();\')\r\n builder.append(\' }\')\r\n builder.append(\' }\')\r\n builder.append(\'}\')\r\n builder.append(\'function on_load() {\')\r\n builder.append(\' text = document.getElementById(\"text\");\')\r\n builder.append(\' text.onkeydown = on_keydown;\')\r\n builder.append(\'}\')\r\n builder.append(\'window.onload = on_load;\')\r\n builder.end_script()\r\n builder.end_head()\r\n builder.start_body()\r\n builder.append(\'
\')\r\n builder.append(\'
\')\r\n builder.append(\'\')\r\n builder.append(\'
\')\r\n builder.append(\'\')\r\n builder.append(\'
\')\r\n builder.append(\'
\')\r\n builder.end_body()\r\n return builder.build()\r\n\r\n def do_post(self):\r\n if self.path != \'/\':\r\n self.respond_bad_request()\r\n return\r\n content_type = self.headers[\'Content-Type\']\r\n if content_type != \'application/x-www-form-urlencoded\':\r\n self.respond_bad_request()\r\n return\r\n content_length = self.headers[\'Content-Length\']\r\n if not content_length or not content_length.isdecimal():\r\n self.respond_bad_request()\r\n return\r\n content_length = int(content_length)\r\n if content_length <= 5 or content_length > 1048576:\r\n self.respond_bad_request()\r\n return\r\n text = self.rfile.read(5).decode()\r\n if text != \'text=\':\r\n self.respond_bad_request()\r\n return\r\n text = self.rfile.read(content_length - 5).decode()\r\n text = parse.unquote_plus(text)\r\n self.respond_redirect(\'/\')\r\n print(text)\r\n\r\n\r\nclass HtmlBuilder:\r\n\r\n def __init__(self):\r\n self._out = io.StringIO()\r\n\r\n def start_head(self):\r\n self._out.write(\'\')\r\n self._out.write(\'\')\r\n self._out.write(\'\')\r\n self._out.write(\'\')\r\n\r\n def end_head(self):\r\n self._out.write(\'\')\r\n\r\n def start_title(self):\r\n self._out.write(\'\')\r\n\r\n def end_title(self):\r\n self._out.write(\'\')\r\n\r\n def start_style(self):\r\n self._out.write(\'\')\r\n\r\n def start_script(self):\r\n self._out.write(\'\')\r\n\r\n def start_body(self):\r\n self._out.write(\'\')\r\n\r\n def end_body(self):\r\n self._out.write(\'\')\r\n\r\n def append(self, code):\r\n self._out.write(code)\r\n\r\n def build(self):\r\n self._out.write(\'\')\r\n return self._out.getvalue()\r\n\r\n def __del__(self):\r\n self._out.close()\r\n\r\n\r\nclass MultipartParser:\r\n\r\n def __init__(self, stream, boundary, content_length):\r\n self._stream = stream\r\n self._total_length = content_length\r\n self._read_length = 0\r\n self._separator = f\'--{boundary}\\r\\n\'.encode()\r\n self._terminator = f\'--{boundary}--\\r\\n\'.encode()\r\n self._state = MultipartState.INIT\r\n self._content_dispositon_pattern = re.compile(r\'^form-data; name=\"(.+)\"; filename=\"(.+)\"\\r\\n$\')\r\n self._name = None\r\n self._filename = None\r\n\r\n def has_next(self):\r\n if self._state == MultipartState.INIT:\r\n if self._next_line() != self._separator:\r\n raise MultipartError\r\n self._state = MultipartState.HEADER_START\r\n if self._state == MultipartState.HEADER_START:\r\n self._parse_headers()\r\n self._state = MultipartState.PART_START\r\n return True\r\n if self._state == MultipartState.END and self._read_length == self._total_length:\r\n return False\r\n raise MultipartError\r\n\r\n def next_name(self):\r\n if self._state != MultipartState.PART_START:\r\n raise MultipartError\r\n return self._name\r\n\r\n def next_filename(self):\r\n if self._state != MultipartState.PART_START:\r\n raise MultipartError\r\n return self._filename\r\n\r\n def write_next_to(self, out):\r\n if self._state != MultipartState.PART_START:\r\n raise MultipartError\r\n line, next = None, None\r\n while True:\r\n if not line:\r\n line = self._next_line()\r\n if len(line) >= 2 and line[-2:] == b\'\\r\\n\':\r\n next = self._next_line()\r\n if next == self._separator:\r\n if len(line) > 2:\r\n out.write(line[:-2])\r\n self._state = MultipartState.HEADER_START\r\n return\r\n if next == self._terminator:\r\n if len(line) > 2:\r\n out.write(line[:-2])\r\n self._state = MultipartState.END\r\n return\r\n out.write(line)\r\n line = next\r\n else:\r\n out.write(line)\r\n line = None\r\n\r\n def _parse_headers(self):\r\n self._name = None\r\n self._filename = None\r\n while True:\r\n line = self._next_line().decode()\r\n if line == \'\\r\\n\':\r\n break\r\n parts = line.split(\': \')\r\n if len(parts) != 2:\r\n raise MultipartError\r\n key, value = parts\r\n if key == \'Content-Disposition\':\r\n match = self._content_dispositon_pattern.match(value)\r\n if not match:\r\n raise MultipartError\r\n self._name = match.group(1)\r\n self._filename = match.group(2)\r\n if not self._name or not self._filename:\r\n raise MultipartError\r\n\r\n def _next_line(self):\r\n if self._read_length >= self._total_length:\r\n raise MultipartError\r\n l = min(65536, self._total_length - self._read_length)\r\n line = self._stream.readline(l)\r\n if not line:\r\n raise MultipartError\r\n self._read_length += len(line)\r\n return line\r\n\r\n\r\nclass MultipartState:\r\n\r\n INIT = 0\r\n HEADER_START = 1\r\n PART_START = 2\r\n END = 3\r\n\r\n\r\nclass MultipartError(ValueError):\r\n pass\r\n\r\n\r\ndef get_best_family(host, port):\r\n info = socket.getaddrinfo(host, port, type=socket.SOCK_STREAM, flags=socket.AI_PASSIVE)\r\n family, type, proto, canonname, addr = info[0]\r\n return family, addr\r\n\r\n\r\ndef is_windows():\r\n return os.name == \'nt\'\r\n\r\n\r\ndef on_interrupt(a, b):\r\n if not is_windows():\r\n sys.stderr.write(\'\\n\')\r\n sys.exit(1)\r\n\r\n\r\ndef print_prompt():\r\n if is_windows():\r\n sys.stderr.write(\'Enter your text, then press Ctrl + Z followed by the Enter key:\\n\')\r\n else:\r\n sys.stderr.write(\'Enter your text, then press Ctrl + D:\\n\')\r\n\r\n\r\ndef start_server(address, port, handler_class):\r\n ShareServer.address_family, addr = get_best_family(address, port)\r\n with ShareServer(addr, handler_class) as server:\r\n host, port = server.socket.getsockname()[:2]\r\n sys.stderr.write(f\'Serving HTTP on {host} port {port} ...\\n\')\r\n server.serve_forever()\r\n\r\n\r\ndef main():\r\n sys.tracebacklimit = 0\r\n signal.signal(signal.SIGINT, on_interrupt)\r\n parser = argparse.ArgumentParser(allow_abbrev=False)\r\n parser.add_argument(\'-b\', \'--bind\', dest=\'address\', help=\'bind address [default: all interfaces]\')\r\n parser.add_argument(\'-p\', \'--port\', type=int, default=8888, help=\'port [default: 8888]\')\r\n parser.add_argument(\'-s\', \'--share\', action=\'store_true\', help=\'share mode (default mode)\')\r\n parser.add_argument(\'-r\', \'--receive\', action=\'store_true\', help=\'receive mode, can be used with -s option (only for directory)\')\r\n parser.add_argument(\'-a\', \'--all\', action=\'store_true\', help=\'show all files, including hidden ones, only for directory\')\r\n parser.add_argument(\'-t\', \'--text\', action=\'store_true\', help=\'for text\')\r\n parser.add_argument(\'-P\', \'--password\', nargs=\'?\', const=os.getenv(\'SHARE_PASSWORD\'), help=\'access password, if no PASSWORD is specified, the environment variable SHARE_PASSWORD will be used\')\r\n parser.add_argument(\'arguments\', nargs=\'*\', help=\'a directory, files or texts\')\r\n args = parser.parse_args()\r\n if args.password and len(args.password) < 3:\r\n raise ValueError(\'password is too short\')\r\n if not args.receive:\r\n args.share = True\r\n if args.share and args.receive:\r\n dir = None\r\n if not args.arguments:\r\n dir = os.getcwd()\r\n elif os.path.isdir(args.arguments[0]):\r\n dir = args.arguments[0]\r\n else:\r\n raise FileNotFoundError(f\'{args.arguments[0]} is not a directory\')\r\n handler_class = functools.partial(DirectoryShareHandler, dir, args.all, upload=True, password=args.password)\r\n elif args.share:\r\n if args.text:\r\n if args.arguments:\r\n text = \'\\n\'.join(args.arguments)\r\n else:\r\n print_prompt()\r\n text = \'\'.join(sys.stdin.readlines())\r\n if not text:\r\n sys.exit(1)\r\n handler_class = functools.partial(TextShareHandler, text, password=args.password)\r\n else:\r\n dir, files = None, None\r\n if not args.arguments:\r\n dir = os.getcwd()\r\n elif os.path.isdir(args.arguments[0]):\r\n dir = args.arguments[0]\r\n else:\r\n for f in args.arguments:\r\n if not os.path.isfile(f):\r\n raise FileNotFoundError(f\'{f} is not a file\')\r\n files = [os.path.realpath(f) for f in args.arguments]\r\n if dir:\r\n handler_class = functools.partial(DirectoryShareHandler, dir, args.all, password=args.password)\r\n else:\r\n handler_class = functools.partial(FileShareHandler, files, password=args.password)\r\n else:\r\n if args.text:\r\n handler_class = functools.partial(TextReceiveHandler, password=args.password)\r\n else:\r\n dir = None\r\n if not args.arguments:\r\n dir = os.getcwd()\r\n elif os.path.isdir(args.arguments[0]):\r\n dir = args.arguments[0]\r\n else:\r\n raise FileNotFoundError(f\'{args.arguments[0]} is not a directory\')\r\n handler_class = functools.partial(FileReceiveHandler, dir, password=args.password)\r\n start_server(args.address, args.port, handler_class)\r\n\r\n\r\nmain()', 1); +INSERT INTO `onefile` VALUES (18, 'web-server', 'Python', '一个简单的 Web 框架', 12, 'https://github.com/521xueweihan/OneFile/blob/main/src/python/web-server.py', 9700, '#!/usr/bin/env python\r\n# -*- coding:utf-8 -*-\r\n#\r\n# Author : XueWeiHan\r\n# E-mail : 595666367@qq.com\r\n# Date : 2022-03-29 16:56\r\n# Desc : 迷你 Web 服务器\r\nimport sys\r\nimport socket\r\nimport selectors\r\nimport datetime\r\nimport time\r\nimport html\r\n\r\n# 默认错误信息 HTML 模版\r\nDEFAULT_ERROR_MESSAGE = \"\"\"\\\r\n\r\n\r\n \r\n \r\n Error response\r\n \r\n \r\n

Error response

\r\n

Error code: %(code)d

\r\n

Message: %(message)s.

\r\n

Error code explanation: %(code)s - %(explain)s.

\r\n \r\n\r\n\"\"\"\r\n\r\n# 处理连接进行数据通信\r\nclass HTTPServer(object):\r\n def __init__(self, server_address, RequestHandlerClass):\r\n self.server_address = server_address\r\n self.RequestHandlerClass = RequestHandlerClass\r\n self.request_queue_size = 5\r\n self.__shutdown_request = False\r\n\r\n # 创建 TCP Socket\r\n self.socket = socket.socket(socket.AF_INET,\r\n socket.SOCK_STREAM)\r\n try:\r\n # 绑定 socket 和端口\r\n self.server_bind()\r\n # 开始监听端口\r\n self.server_activate()\r\n except:\r\n # 关闭 socket\r\n self.server_close()\r\n raise\r\n\r\n def server_bind(self):\r\n self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\r\n # 绑定端口\r\n self.socket.bind(self.server_address)\r\n\r\n def server_activate(self):\r\n # 监听端口\r\n self.socket.listen(self.request_queue_size)\r\n\r\n def server_close(self):\r\n # 关闭socket\r\n self.socket.close()\r\n\r\n def fileno(self):\r\n \"\"\"\r\n 返回 socket 文件号\r\n 用于 select 监控文件句柄状态\r\n \"\"\"\r\n return self.socket.fileno()\r\n\r\n def serve_forever(self, poll_interval=0.5):\r\n \"\"\"\r\n 服务器启动入口\r\n \"\"\"\r\n\r\n with selectors.SelectSelector() as selector:\r\n # 基于 select 轮询获取已准备好的可读句柄\r\n selector.register(self, selectors.EVENT_READ)\r\n while True:\r\n ready = selector.select(poll_interval)\r\n if ready:\r\n # 有准备好的可读文件句柄,则与客户端的链接建立完毕\r\n # 可以进行下一步\r\n self._handle_request_noblock()\r\n\r\n def _handle_request_noblock(self):\r\n \"\"\"\r\n 处理请求\r\n \"\"\"\r\n try:\r\n # 接收来自客户端的请求:request 对象\r\n request, client_address = self.get_request()\r\n except socket.error:\r\n return\r\n try:\r\n # 开始处理请求\r\n self.process_request(request, client_address)\r\n except:\r\n self.handle_error(client_address)\r\n self.shutdown_request(request)\r\n\r\n def get_request(self):\r\n # 接收请求\r\n return self.socket.accept()\r\n\r\n def process_request(self, request, client_address):\r\n # 处理请求\r\n self.finish_request(request, client_address)\r\n self.shutdown_request(request)\r\n\r\n def finish_request(self, request, client_address):\r\n # 调用处理请求的类\r\n self.RequestHandlerClass(request, client_address, self)\r\n\r\n def shutdown_request(self, request):\r\n try:\r\n # 后续不允许发送和接收\r\n request.shutdown(socket.SHUT_WR)\r\n except socket.error:\r\n pass # some platforms may raise ENOTCONN here\r\n # 结束请求\r\n request.close()\r\n\r\n def handle_error(self, client_address):\r\n print(\'-\'*40, file=sys.stderr)\r\n print(\'Exception occurred during processing of request from\',\r\n client_address, file=sys.stderr)\r\n import traceback\r\n traceback.print_exc()\r\n print(\'-\'*40, file=sys.stderr)\r\n\r\n# 处理请求\r\nclass HTTPRequestHandler(object):\r\n responses = {\r\n 100: (\'Continue\', \'Request received, please continue\'),\r\n 101: (\'Switching Protocols\',\r\n \'Switching to new protocol; obey Upgrade header\'),\r\n\r\n 200: (\'OK\', \'Request fulfilled, document follows\'),\r\n 201: (\'Created\', \'Document created, URL follows\'),\r\n 202: (\'Accepted\',\r\n \'Request accepted, processing continues off-line\'),\r\n 203: (\'Non-Authoritative Information\', \'Request fulfilled from cache\'),\r\n 204: (\'No Content\', \'Request fulfilled, nothing follows\'),\r\n 205: (\'Reset Content\', \'Clear input form for further input.\'),\r\n 206: (\'Partial Content\', \'Partial content follows.\'),\r\n\r\n 300: (\'Multiple Choices\',\r\n \'Object has several resources -- see URI list\'),\r\n 301: (\'Moved Permanently\', \'Object moved permanently -- see URI list\'),\r\n 302: (\'Found\', \'Object moved temporarily -- see URI list\'),\r\n 303: (\'See Other\', \'Object moved -- see Method and URL list\'),\r\n 304: (\'Not Modified\',\r\n \'Document has not changed since given time\'),\r\n 305: (\'Use Proxy\',\r\n \'You must use proxy specified in Location to access this \'\r\n \'resource.\'),\r\n 307: (\'Temporary Redirect\',\r\n \'Object moved temporarily -- see URI list\'),\r\n\r\n 400: (\'Bad Request\',\r\n \'Bad request syntax or unsupported method\'),\r\n 401: (\'Unauthorized\',\r\n \'No permission -- see authorization schemes\'),\r\n 402: (\'Payment Required\',\r\n \'No payment -- see charging schemes\'),\r\n 403: (\'Forbidden\',\r\n \'Request forbidden -- authorization will not help\'),\r\n 404: (\'Not Found\', \'Nothing matches the given URI\'),\r\n 405: (\'Method Not Allowed\',\r\n \'Specified method is invalid for this resource.\'),\r\n 406: (\'Not Acceptable\', \'URI not available in preferred format.\'),\r\n 407: (\'Proxy Authentication Required\', \'You must authenticate with \'\r\n \'this proxy before proceeding.\'),\r\n 408: (\'Request Timeout\', \'Request timed out; try again later.\'),\r\n 409: (\'Conflict\', \'Request conflict.\'),\r\n 410: (\'Gone\',\r\n \'URI no longer exists and has been permanently removed.\'),\r\n 411: (\'Length Required\', \'Client must specify Content-Length.\'),\r\n 412: (\'Precondition Failed\', \'Precondition in headers is false.\'),\r\n 413: (\'Request Entity Too Large\', \'Entity is too large.\'),\r\n 414: (\'Request-URI Too Long\', \'URI is too long.\'),\r\n 415: (\'Unsupported Media Type\', \'Entity body in unsupported format.\'),\r\n 416: (\'Requested Range Not Satisfiable\',\r\n \'Cannot satisfy request range.\'),\r\n 417: (\'Expectation Failed\',\r\n \'Expect condition could not be satisfied.\'),\r\n\r\n 500: (\'Internal Server Error\', \'Server got itself in trouble\'),\r\n 501: (\'Not Implemented\',\r\n \'Server does not support this operation\'),\r\n 502: (\'Bad Gateway\', \'Invalid responses from another server/proxy.\'),\r\n 503: (\'Service Unavailable\',\r\n \'The server cannot process the request due to a high load\'),\r\n 504: (\'Gateway Timeout\',\r\n \'The gateway server did not receive a timely response\'),\r\n 505: (\'HTTP Version Not Supported\', \'Cannot fulfill request.\'),\r\n }\r\n\r\n def __init__(self, request, client_address, server):\r\n self.request = request\r\n self.client_address = client_address\r\n self.server = server\r\n self.protocol_version = \"HTTP/1.0\"\r\n self.setup()\r\n try:\r\n self.handle()\r\n finally:\r\n self.finish()\r\n\r\n def setup(self):\r\n self.connection = self.request\r\n # 初始化请求和响应的文件句柄\r\n self.rfile = self.connection.makefile(\'rb\', -1)\r\n self.wfile = self.connection.makefile(\'wb\', 0)\r\n\r\n def handle(self):\r\n try:\r\n # 设置请求体限制:65536\r\n self.raw_requestline = self.rfile.readline(65537)\r\n # 如果超过限制则返回 414 HTTP code\r\n if len(self.raw_requestline) > 65536:\r\n self.requestline = \'\'\r\n self.request_version = \'\'\r\n self.command = \'\'\r\n self.send_error(414)\r\n return\r\n # 解析 HTTP 请求,并把值通过 self 属性传递\r\n if not self.parse_request():\r\n return\r\n # 具体处理请求的方法 do_方法,比如:do_get、do_post\r\n mname = (\'do_\' + self.command).lower()\r\n if not hasattr(self, mname):\r\n self.send_error(501, \"Unsupported method (%r)\" % self.command)\r\n return\r\n method = getattr(self, mname)\r\n # 对应到具体的处理 HTTP method 的方法\r\n method()\r\n # 返回响应\r\n self.wfile.flush()\r\n except socket.timeout as e:\r\n self.log_error(\"Request timed out: %r\", e)\r\n return\r\n\r\n def finish(self):\r\n if not self.wfile.closed:\r\n try:\r\n self.wfile.flush()\r\n except socket.error:\r\n pass\r\n # 关闭请求和响应的句柄\r\n self.wfile.close()\r\n self.rfile.close()\r\n\r\n def parse_request(self):\r\n \"\"\"\r\n 解析 HTTP 请求\r\n \"\"\"\r\n self.command = None # set in case of error on the first line\r\n self.request_version = version = \"HTTP/1.0\"\r\n # 开始解析 HTTP 请求,数据格式如下:\r\n \"\"\"\r\n {HTTP method} {PATH} {HTTP version}\\r\\n\r\n {header field name}:{field value}\\r\\n\r\n ...\r\n \\r\\n\r\n {request body}\r\n \"\"\"\r\n # 解析请求头\r\n requestline = str(self.raw_requestline, \'iso-8859-1\')\r\n requestline = requestline.rstrip(\'\\r\\n\')\r\n self.requestline = requestline\r\n words = requestline.split()\r\n if len(words) == 3:\r\n # HTTP method, PATH, HTTP version\r\n command, path, version = words\r\n if version[:5] != \'HTTP/\':\r\n self.send_error(400, \"Bad request version (%r)\" % version)\r\n return False\r\n # 检查 HTTP version 正确性\r\n try:\r\n base_version_number = version.split(\'/\', 1)[1]\r\n version_number = base_version_number.split(\".\")\r\n if len(version_number) != 2:\r\n raise ValueError\r\n version_number = int(version_number[0]), int(version_number[1])\r\n if version_number >= (2, 0):\r\n self.send_error(\r\n 505, \"Invalid HTTP Version (%s)\" % base_version_number)\r\n return False\r\n except (ValueError, IndexError):\r\n self.send_error(400, \"Bad request version (%r)\" % version)\r\n return False\r\n\r\n elif len(words) == 2:\r\n # 如果没有 HTTP version 则使用默认版本即:HTTP/1.0\r\n command, path = words\r\n elif not words:\r\n return False\r\n else:\r\n self.send_error(400, \"Bad request syntax (%r)\" % requestline)\r\n return False\r\n\r\n self.command, self.path, self.request_version = command, path, version\r\n\r\n # 解析 header,仅做解析\r\n self.headers = self.parse_headers()\r\n return True\r\n\r\n def parse_headers(self):\r\n headers = {}\r\n while True:\r\n line = self.rfile.readline()\r\n if line in (b\'\\r\\n\', b\'\\n\', b\'\'):\r\n break\r\n line_str = str(line, \'utf-8\')\r\n key, value = line_str.split(\': \')\r\n headers[key] = value.strip()\r\n return headers\r\n\r\n def log_message(self, format, *args):\r\n log_data_time_string = datetime.datetime.now().strftime(\"%d/%m/%Y %H:%M:%S\")\r\n sys.stderr.write(\"%s - - [%s] %s\\n\" %\r\n (self.client_address[0],\r\n log_data_time_string,\r\n format%args))\r\n\r\n def log_request(self, code=\'-\', size=\'-\'):\r\n self.log_message(\'\"%s\" %s %s\',\r\n self.requestline, str(code), str(size))\r\n\r\n def log_error(self, format, *args):\r\n self.log_message(format, *args)\r\n\r\n def send_error(self, code, message=None):\r\n \"\"\"\r\n 返回异常响应 code+message\r\n \"\"\"\r\n try:\r\n short, long = self.responses[code]\r\n except KeyError:\r\n short, long = \'???\', \'???\'\r\n if message is None:\r\n message = short\r\n explain = long\r\n self.log_error(\"code %d, message %s\", code, message)\r\n self.send_response(code, message)\r\n\r\n content = None\r\n # 状态码大于 200,并且不是 204、205、304 为异常\r\n if code > 200 and code not in (204, 205, 304):\r\n content = (DEFAULT_ERROR_MESSAGE % {\r\n \'code\': code,\r\n \'message\': html.escape(message),\r\n \'explain\': explain\r\n })\r\n self.send_header(\"Content-Type\", \"text/html;charset=utf-8\")\r\n self.end_headers()\r\n body = content.encode(\'UTF-8\', \'replace\')\r\n if self.command != \'HEAD\' and content:\r\n # 返回响应\r\n self.wfile.write(body)\r\n\r\n def send_response(self, code, message=None):\r\n self.log_request(code)\r\n if message is None:\r\n if code in self.responses:\r\n message = self.responses[code][0]\r\n else:\r\n message = \'\'\r\n # 响应体格式\r\n \"\"\"\r\n {HTTP version} {status code} {status phrase}\\r\\n\r\n {header field name}:{field value}\\r\\n\r\n ...\r\n \\r\\n\r\n {response body}\r\n \"\"\"\r\n # 写响应头\r\n self.wfile.write((\"%s %d %s\\r\\n\" %\r\n (self.protocol_version, code, message)).encode(\r\n \'latin-1\', \'strict\'))\r\n self.send_header(\'Server\', \"HG/Python \" + sys.version.split()[0])\r\n # 写响应 header\r\n self.send_header(\'Date\', self.date_time_string())\r\n\r\n def send_header(self, keyword, value):\r\n self.wfile.write((\"%s: %s\\r\\n\" % (keyword, value)).encode(\'latin-1\', \'strict\'))\r\n\r\n def end_headers(self):\r\n # header 结束的标识符\r\n self.wfile.write(b\"\\r\\n\")\r\n\r\n @staticmethod\r\n def date_time_string(timestamp=None):\r\n weekdayname = [\'Mon\', \'Tue\', \'Wed\', \'Thu\', \'Fri\', \'Sat\', \'Sun\']\r\n\r\n monthname = [None, \'Jan\', \'Feb\', \'Mar\', \'Apr\', \'May\', \'Jun\', \'Jul\',\r\n \'Aug\', \'Sep\', \'Oct\', \'Nov\', \'Dec\']\r\n if timestamp is None:\r\n timestamp = time.time()\r\n year, month, day, hh, mm, ss, wd, y, z = time.gmtime(timestamp)\r\n s = \"%s, %02d %3s %4d %02d:%02d:%02d GMT\" % (\r\n weekdayname[wd],\r\n day, monthname[month],\r\n year, hh, mm, ss)\r\n return s\r\n\r\n# 请求具体的处理方式\r\nclass RequestHandler(HTTPRequestHandler):\r\n def handle_index(self):\r\n page = \'\'\'\r\n \r\n \r\n

你好, HG Web Server!

\r\n \r\n \r\n \'\'\'\r\n self.send_response(200) # status code\r\n # 试试删除这行\r\n self.send_header(\"Content-Type\", \"text/html; charset=utf-8\")\r\n self.send_header(\"Content-Length\", str(len(page)))\r\n self.end_headers()\r\n self.wfile.write(page.encode(\'utf-8\'))\r\n\r\n def handle_favicon(self):\r\n page = \'\'\'\r\n \r\n \r\n

这里还未开发

\r\n \r\n \r\n \'\'\'\r\n self.send_response(200) # status code\r\n self.send_header(\"Content-Type\", \"text/html; charset=utf-8\")\r\n self.send_header(\"Content-Length\", str(len(page)))\r\n self.end_headers()\r\n self.wfile.write(page.encode(\'utf-8\'))\r\n\r\n # 处理 GET 请求\r\n def do_get(self):\r\n # 根据 path 对应到具体的处理方法\r\n if self.path == \'/\':\r\n self.handle_index()\r\n elif self.path.startswith(\'/favicon\'):\r\n self.handle_favicon()\r\n else:\r\n self.send_error(404)\r\n\r\n\r\nif __name__ == \'__main__\':\r\n server = HTTPServer((\'\', 8080), RequestHandler)\r\n # 启动服务\r\n server.serve_forever()', 1); -- ---------------------------- -- Table structure for project @@ -179,10 +219,10 @@ CREATE TABLE `project` ( -- ---------------------------- -- Records of project -- ---------------------------- -INSERT INTO `project` VALUES (26, 12, 'agarrharr/awesome-macos-screensavers', 'https://github.com/agarrharr/awesome-macos-screensavers', 'https://avatars.githubusercontent.com/u/3266363?v=4', '令人惊艳的 macOS 屏保集合', '这里有不同风格、样式、趣味性十足的 macOS 屏保,相信总有一款适合你。', 0, '2023-07-10 16:08:35', 15, 1, NULL, 1, 59, '2023-07-10 22:29:58'); -INSERT INTO `project` VALUES (27, 12, 'microsoft/AI-For-Beginners', 'https://github.com/microsoft/AI-For-Beginners', 'https://avatars.githubusercontent.com/u/6154722?v=4', '微软开源的入门级人工智能教程', '这是一份完全免费、面向零基础人群的 AI 课程,为期 12 周共计 24 节课。你将学习到关于 AI 的历史、基本知识、主流框架、CV 和 NLP 等知识。', 1, '2023-07-10 17:10:07', 15, 1, NULL, 1, 18, '2023-07-10 22:29:58'); -INSERT INTO `project` VALUES (32, 12, 'loft-sh/devpod', 'https://github.com/loft-sh/devpod', 'https://avatars.githubusercontent.com/u/65466868?v=4', '连接本地 IDE 和远程开发环境的工具', '它通过在本地启动一个连接远程开发机器的容器,让开发环境跑在容器里,从而轻松启动任何开发环境。支持 K8s 集群、云端虚拟机、任何可访问的远程机器,还能够自动关闭云服务降低成本。', 0, '2023-07-10 21:28:34', 5, 1, NULL, 0, 2, '2023-07-10 22:29:58'); -INSERT INTO `project` VALUES (43, 13, 'facebook/redex', 'https://github.com/facebook/redex', 'https://avatars.githubusercontent.com/u/69631?v=4', '优化 Android 应用的字节码优化器', 'Facebook 开源的字节码优化器,它提供了一个框架,可用来读取、写入和分析 .dex 文件,并使用这个框架进行一系列的优化处理来改进字节码。经过 ReDex 优化后的 APK 会比其源文件更小且更快。', 0, '2023-07-10 22:54:53', 3, 1, NULL, 0, 5, '2023-07-10 22:54:53'); +INSERT INTO `project` VALUES (26, 12, 'agarrharr/awesome-macos-screensavers', 'https://github.com/agarrharr/awesome-macos-screensavers', 'https://avatars.githubusercontent.com/u/3266363?v=4', '令人惊艳的 macOS 屏保集合', '这里有不同风格、样式、趣味性十足的 macOS 屏保,相信总有一款适合你。', 0, '2023-07-10 16:08:35', 15, 1, NULL, 1, 77, '2023-07-10 22:29:58'); +INSERT INTO `project` VALUES (27, 12, 'microsoft/AI-For-Beginners', 'https://github.com/microsoft/AI-For-Beginners', 'https://avatars.githubusercontent.com/u/6154722?v=4', '微软开源的入门级人工智能教程', '这是一份完全免费、面向零基础人群的 AI 课程,为期 12 周共计 24 节课。你将学习到关于 AI 的历史、基本知识、主流框架、CV 和 NLP 等知识。', 1, '2023-07-10 17:10:07', 15, 1, NULL, 1, 23, '2023-07-10 22:29:58'); +INSERT INTO `project` VALUES (32, 12, 'loft-sh/devpod', 'https://github.com/loft-sh/devpod', 'https://avatars.githubusercontent.com/u/65466868?v=4', '连接本地 IDE 和远程开发环境的工具', '它通过在本地启动一个连接远程开发机器的容器,让开发环境跑在容器里,从而轻松启动任何开发环境。支持 K8s 集群、云端虚拟机、任何可访问的远程机器,还能够自动关闭云服务降低成本。', 0, '2023-07-10 21:28:34', 5, 1, NULL, 0, 7, '2023-07-10 22:29:58'); +INSERT INTO `project` VALUES (43, 13, 'facebook/redex', 'https://github.com/facebook/redex', 'https://avatars.githubusercontent.com/u/69631?v=4', '优化 Android 应用的字节码优化器', 'Facebook 开源的字节码优化器,它提供了一个框架,可用来读取、写入和分析 .dex 文件,并使用这个框架进行一系列的优化处理来改进字节码。经过 ReDex 优化后的 APK 会比其源文件更小且更快。', 0, '2023-07-10 22:54:53', 3, 1, NULL, 0, 11, '2023-07-10 22:54:53'); -- ---------------------------- -- Table structure for project_category