ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

SJX Script,纯C实现的类JS语法的极简引擎,Version 2.0

SJX Script,纯C实现的类JS语法的极简引擎,Version 2.0 设计目的缓解自己的强迫症。使用方法从 https://github.com/shajunxing/sjx-script/releases 下载包含 windows 和 linux 可执行程序。创建文本文件 hello.js 内容为print(hello);执行js hello.js就能看到欢迎语。源代码由多行语句组成首行可以是 shebang 格式即#!开头的遵循 posix 约定。大部分语句以分号;结尾。如果不愿公开源代码可将其编译执行jsc hello.js生成汇编文件 hello.asm.txt 和字节码文件 hello.bin.txt 。字节码文件可用jse hello.bin.txt执行。运行期错误信息也会泄露部分源代码信息。比如创建文件 error.js 内容为throw boom;执行js error.js可观察到错误文件名和行号。执行jsc error.js和jse error.bin.txt同样也可观察到。现在打开 error.bin.txt 删除其中的line mapping和file names两段再次执行jse error.bin.txt可发现文件名和行号不再显示。执行jsrepl可进入交互式环境其中输入的语句会立即执行。另外也有内置命令比如查看之前输入的历史、保存到磁盘等。变量和值标识符以字母或_开头中间只能包含字母、数字或_。变量以标识符命名变量定义语句格式为let 变量名;赋值语句格式为变量名 值;变量可以在定义时候赋值let 变量名 值如果不赋值默认为null。数值类型包括空值字面量为null表示不存在;布尔值字面量为true和false数字字面量同c语言浮点型引擎内部存储为 double 字符串字面量类似c语言字符串字面量约定 utf-8 编码支持\ \ \? \\ \a \b \e \f \n \r \t \v \0\e表示 0x1b 即ansi转义符数组用[]括起来的0个或多个数值之间用,分隔对象用{}括起来的0个或多个键值对之间用,分隔键和值用:分隔键是字符串或简写为标识符但内部都解析为字符串值是任意数值函数字面量表示为function (参数) { 函数体 }。不支持表达式因为观感不佳容易与等号混淆数组用数组[数字]访问其中成员数字是下标是从 0 开始的正整数0 是第一个成员。对象用对象[字符串]或对象.标识符访问其中成员还可用对象?.标识符当对象为null时返回null。数组和对象读取不存在的成员等同于读取赋值为null的成员都是返回null不做区分。函数赋值给变量let 名字 function(){};可以写成function 名字(){}结尾不需要分号。表达式和运算符大部分运算符功能类似c语言。运算符的优先级从低到高为三元运算符?:逻辑或运算符||逻辑与运算符关系运算符!加减运算符-乘除运算符*/%指数运算符**前缀运算符-!typeof数组/对象成员访问和函数调用运算符[].?.()::运算符遵循严格规则没有隐式转换。只有布尔值可以进行逻辑运算。三元运算符条件 ? 表达式1 : 表达式2当条件为true时表达式2不会执行false时表达式1不会执行。如果有多个串联的逻辑或表达式那么当计算到第一个true之后的表达式不会执行。如果有多个串联的逻辑与表达式那么当计算到第一个false之后的表达式不会执行。 !判断规则是如果类型不同那么不等如果都是null那么相等如果都是布尔值那么同为true或同为false时候相等如果都是数字那么做浮点值比较如果都是字符串那么用 c 的 strcmp 比较如果都是其它类型那么只有指向同一个值时候才相等而非比较内容。数字支持所有关系和数值运算符。字符串支持所有关系运算符和连接操作用 c 的 strcmp 比较大小。typeof返回数值/表达式的字符串表示的类型分别是null boolean numberstringarrayobjectfunction。赋值表达式-***/%||--类似 c 语言但不返回值。展开语法...数组将数组所有成员拎出来由另一个数组或作为参数由另一个函数接收例如let a [1, 2, 3]那么[...a, 4]相当于[1, 2, 3, 4]foo(0, ...a)相当于foo(0, 1, 2, 3)。语句块语句{}自身是语句内部可以包含多个语句。条件语句是if (条件) 语句1 else 语句2else可选。循环语句有三类while (条件) 语句do 语句 while (条件)和for循环条件必须是布尔值。for循环支持以下语法[]表示可选部分for ([[let] 变量名 表达式 ] ; [条件] ; [赋值表达式])for ([let] 变量名 in 数组/对象)for ([let] 变量名 of 数组/对象)for in循环数组下标或对象键for of循环数组或对象的值它们只循环非null成员。break;语句退出循环continue;语句跳转到循环开头。return 表达式;语句如果在函数内部意为函数返回值如果在函数外部则是退出程序并返回值。程序默认返回null如果返回值不是null脚本解释器一般会打印它。运行时错误处理语句try {语句组1} catch (变量名) {语句组2}错误是一个数值可以用throw 表达式;手动抛出。如果语句组1里出现错误那么会赋给catch的变量名然后执行语句组2。catch是可选的如果没有则不处理错误。如果没有错误处理语句那么错误会发给解释器一般会终止程序并打印错误。不支持finally因为我认为根本不需要反而会使代码执行顺序显得怪异其实可以简化为try 语句1 catch (变量名) 语句2如果单个语句可以不用大括号但不兼容javascript其实主要是因为vscode不支持缩进否则try 语句;还是很漂亮的变量作用域作用域就是可见范围不可见也就是不存在。变量对于源代码里出现在它定义之前的语句都是不存在的访问会引发未找到错误。所以变量作用域只存在与定义之后。在大括号里面的变量对于外面的语句而言也是不存在的。所以变量作用域都是局部的。函数参数与函数体处于相同作用域。这些都符合编程语言的约定俗成。函数函数总是会返回值未写return语句的默认返回null。函数支持默认参数参数名 表达式如果没有对应的实参也没有默认参数那么为null。函数也支持剩余参数...参数名剩余参数必须在最后会把未收集的所有实参按顺序合并为数组例如函数foo(a, b, ...c){}调用foo(1, 2, 3, 4)那么c为[3, 4]。语法糖数值::foo(参数)相当于foo(数值, 参数)据此可写出优雅的链式语句比如arr::filter()::map()::reduce()函数支持闭包。在定义时也就是创建函数数值的时候该函数依照作用域约定把当前所能见到的所有变量都记录进函数数值内部称之为闭包之后闭包就固定不再变动了。在运行时函数体内的代码搜索变量是沿着调用栈由栈顶开始逆向到该函数闭包为止。例如function invoke(func) { func(); } let a 1; invoke(function() { let b 2; invoke(function() { let c 3; invoke(function() { print(a, b, c); // 正确显示 1 2 3 }); }); });例二function invoke(func) { func(); } let a 1; function outer() { print(a, b, c); } invoke(function() { let b 2; invoke(function() { let c 3; invoke(outer); // outer()在定义时不可见b和c虽然调用outer的代码处于它们的作用域但outer依旧见不到它们 }); });例三function foo() { let val; function bar(v) { val v; } function qux() { return val; } return {bar : bar, qux : qux}; } let f foo(); // foo()返回bar和qux两个函数数值变量val存在于bar和qux的闭包里 f.bar(3); // 虽然该代码在foo外部但还是能访问其内部的变量val print(f.qux()); // 可以看到val的值被成功修改注意以下递归函数代码会在第2行提示Variable not found即找不到foo变量。这是因为foo变量是在函数数值创建完毕后再赋值的所以创建期间的闭包不包含该变量这一点类似lua。function foo() { foo(); } foo();解决办法是先把foo变量定义出来let foo; foo function() { foo(); } foo();闭包规则符合人类直觉。垃圾回收引擎自动执行引用计数算法。为提高性能未使用的变量和数值会被缓存而不是被释放以加速下一次申请。如果存在循环引用引用计数算法是无法处理的。比如let a [], b []; a[0] b; b[0] a;可以a[0] null;打破循环。再比如let a; let b function() {}; a function() {};a和b都存在对方的闭包里面形成循环引用。所以引擎也提供清除算法gc()函数供程序手动调用该函数同时也释放所有缓存。标记清除算法是比较耗时的一般而言编码时候应该尽量避免循环引用。内置函数库命名规则最常用的取用最常用的名称例如控制台输入和输出inputprint单一特征的匹配单个 windows / posix 命令或 c 标准库 / unistd 函数的例如cd、md和rd使用最简短的同时也可以提高执行速度非单一特征的自定义名称。参数和返回值记法-: 空值无返回值的函数实际上返回空值b: 布尔值n: 数字s: 字符串(): 函数[]: 数组或可选参数{}: 对象*: 任意类型/: 多种类型或名字分隔符…: 不限参数一般地如果函数参数数量少于定义或参数类型与定义不符会抛出错误。函数执行期间遇到错误也会抛出。定义描述n argc同c语言[s …] argv同c语言输入输出定义描述n stdin同c语言n stdout同c语言n stderr同c语言n fclose(n fp)同c语言n fcloseall()同c语言n fopen(s path, s mode)同c语言n fprintf(n fp, s fmt, * …)同c语言支持的格式有f F e E g G s ps fread(n fp)类似c语言读取文件所有内容s freadline(n fp)读取文件一行如果读完则返回nullfreadlines(n fp, (s line))按行读取文件并把每一行作为参数调用回调函数s fwrite(n fp, s content)类似c语言写文件s input(s prompt)同c语言readline终端输入n pclose(n fp)同c语言n popen(s cmd, s type)同c语言print(* …)终端输出不定数量值的字符串呈现空格分隔结束后换行n printf(s fmt, * …)同c语言参数描述同上s sprintf(s fmt, * …)类似c语言返回格式化后的字符串参数描述同上语言定义描述* call((), * …)调用某函数并返回其返回值b endswith(s str, s …)判断字符串是否以任意一个字串结尾[* …] filter([* …] arr, b (* elem))将数组的每一个成员作为参数调用回调函数如果返回true那么把该成员加入返回数组里gc()执行标记清除算法垃圾回收并释放所有未用缓存s join([s …] arr, s sep)将字符串数组的所有成员用分隔符连接n length([]/{}/s val)返回数组长度或对象的键值对数量或字符串的字节数量[* …] map([* …] arr, * (* elem))将数组的每一个成员作为参数调用回调函数返回值加入返回数组里[s …]/- match(s text, s pattern)正则表达式匹配如果匹配则返回所有捕获否则返回null。当前支持^ $ () \d \s \w . [] - * ?* pop([* …] arr)删除并返回数组最后一个成员push([* …] arr, * val)把数值加到数组末尾* reduce([* …] arr, * (* lhs, * rhs))初始返回值为null。对于数组的每一个成员如果是第一个则替换返回值否则以返回值和成员作为参数调用回调函数用它的返回值替代返回值[* …] sort([* …] arr)按照以下规则对数组排序已排序的数组同时也是返回值1、如果类型不同那么依照前面章节介绍数值类型的顺序2、boolean类型false小于true3、string自然排序4、array或object按长度排序[s …] split(s str, [s sep])分割字符串。如果不指定分隔符那么返回数组里只有原字符串一个成员如果分隔符为空字符串那么字符串会被拆成单个字符b startswith(s str, s sub, s …)判断字符串是否以任意一个字串开头s tolower(s str)使用c的tolower将字符串转换为小写n tonumber(s str)使用c的strtod将字符串转换为数字s tostring(* val)返回数值的字符串呈现s toupper(s str)使用c的toupper将字符串转换为大写s urlescape(s str)依据rfc3986为保留字符做转义数学定义描述n ceil(n val)同c语言n floor(n val)同c语言[n, n] modf(n val)同c语言整数和小数部分封装进数组作为返回值n round(n val)同c语言n trunc(n val)同c语言操作系统定义描述s oswindows或posixs pathsep\或/n F_OKaccess参数n R_OKaccess参数n W_OKaccess参数n X_OKaccess参数b access(s path, n mode)同posixs basename(s path)同posixcd(s path)同posixchdirn clock()同c语言, 返回秒数表示的进程时间s ctime(n time)同c语言参数为unix epoch也就是utc 1970-01-01 00:00:00 0000至今的秒数s cwd()同posixgetcwds dirname(s path)同posixexit(n status)同c语言参数会强制转换为整数ls(s dir, cb(s fname, b isdir))遍历目录为每一个子目录和文件调用回调函数md(s path)同posixmkdirrd(s path)同posixrmdirrm(s path)同posixremovesleep(n timeout)睡眠指定秒数spawn(s …)创建进程参数相当于execvp的第二个参数展开{} stat(s path)同posix返回值包含sizeatimectimemtimeuidgid这些键n system(s cmd)同c语言n time()类似c语言但是高精度返回 unix epochtouch(s path)同posix
返回列表