duangsues.is_a? SaltedFish
60 subscribers
609 photos
6 videos
91 files
562 links
🌶🐔🐟 duangsuse 的日常
尤其喜欢发些奇奇怪怪的东西
和转载别人的东西
Download Telegram
Forwarded from dnaugsuz
后来 Dart2 开倒车了 👾
#daily 见到了一些非常简单的逻辑,但是居然算法就无法立刻想出来...

比如三个并排 LED 的『进度』提示

while true
all_lights on
sleep k
light_r off
sleep k
light_c off
sleep k
light_l off
sleep k
continue


然后这个循环可以包装一下,成为 Coroutine 或者 Thread 模式的...

和 MDWiki 的 未读提示

routine update
if view_top < element_pos.low_y
make_gray view
伪代码实现付
Forwarded from dnaugsuz
LLVM 做得也挺多的... PassManager 要加载一大堆不是完全无关的 Pass,这些 Pass 还要初始化...
JIT 编译是很耗时的,虽然词法分析和语法分析之前都做了,bitcode 到机器代码要经过大得吓人的工序,数十个 Pass... 分析 Pass 和优化 Pass 都比较花时间
循环优化和启发式余赘指令合并、各种图操作、寄存器选择大概都是比较花时间的吧

AOT 就基本不用再多花时间做这些了
LineTerminator = \r|\n|\r\n
InputCharacter = [^\r\n]

TraditionalComment = "/*" [^*] ~"*/" | "/*" "*"+ "*/"
EndOfLineComment = "//" {InputCharacter}* {LineTerminator}?

Comment = {TraditionalComment} | {EndOfLineComment}

%x YYINITIAL
%s STRING CHAR

%%

<YYINITIAL> {
{Comment} { }
\" { yybegin(STRING); }
\' { yybegin(CHAR); }
}

<STRING>("\"") { yybegin(YYINITIAL); printf("\""); }

<CHAR>("\'") { yybegin(YYINITIAL); printf("'"); }

<<EOF>> {}

%%

int main(int argc, const char *argv[]) {
if (argc > 1)
yyin = fopen(argv[1], "r");
else
yyin = stdin;

yylex();

return 0;
}
#include <stdio.h> 
#include <stdlib.h>

static int lex(const char *YYCURSOR, const char *YYLIMIT) {
const char *YYMARKER;

/*!re2c
re2c:define:YYCTYPE = char;
re2c:define:YYFILL:naked = 1;
re2c:define:YYFILL = "return 0;";

end = "\x00";
bin = '0b' [01]+;
oct = "0" [0-7]*;
dec = [1-9][0-9]*;
hex = '0x' [0-9a-fA-F]+;

* { printf("a"); }
bin end { printf("a"); }
oct end { printf("a"); }
dec end { printf("a"); }
hex end { printf("a"); }
*/

return -1;
}

int main(int argc, const char *argv[]) {
if (argc > 1) {
FILE *lexf = fopen(argv[1], "rb");

if (lexf == NULL) { perror("Cannot open file"); exit(-1); };

fseek(lexf, 0, SEEK_END);
const size_t fsize = (size_t) ftell(lexf);
fseek(lexf, 0, SEEK_SET);

char *buffer = (char*) malloc(fsize);
fread(buffer, 1, fsize, lexf);

int result = lex(buffer, buffer + fsize);

free(buffer);
fclose(lexf);

return result;
} else {
fprintf(stderr, "Streaming is not supported\n");
}
}
如果可能的话,我愿意尝试 Kotlin/Multiplatform 来解决这两个,不过对 JS 来说其实 Kt 里必须写的东西其实不必要,比如数值类型自动提升、Number 对象操作等

ApkBundler 是我打算彻底独立于 GeekApk 和 GitApk 的作品,设计上会恢复之前 GeekApk 早期的大部分设计、包括依赖系统
我不知道我打不打算真正把它当一个 “商业项目” 运作,当然即使商业也只是商业标准,个人兴趣还是主打(

(当然,如果是商业项目的话,首先必须等我高中毕业了,其次,它得符合天朝变态的审查制度,但它明显会成为最良心的国产软件,而且像极了以前的酷安
(当然,我不会“借鉴”现在的酷安,它只会面向发烧友,至少是我运行的时候
(当然,我保存了之前所有真正有价值的设计文档,所以基本不会丢失好的设计,包括 PM(私信)也会加上
(当然,之前我貌似在 @dsuse 上发过一些要加的东西

ApkBundler Scheme 可能是,大家都知道很多极具扩展性的、著名的、Geek 的软件都拥有自己的脚本语言来扩展自己的功能(比如,Emacs/Emacs Lisp、Vim/VimScript)
ApkBundler 的 Android 客户端和 Web 客户端当然会支持 Java Class 文件插件和 JavaScript 插件,但是主打依然是 ABS(ApkBundler Scheme) 和 Kotlin/ES5 混合开发

ApkBundler Scheme

UpValue 由于 JVM 已有 GC 且闭包不需如此实现不需要使用

tailrec heredoc constarg-folding string-templating
exception square brackets call-by-need

BreakLexer
lex-features comment break binop(right left all) keyword

current(frame)
stack

.gotofast1
.gotofast2
.stop
.yield
.wait
.goto
.value
tailrec
.

varargs apend eval pure noreturn noarg onearg twoarg

string $templating
string $(.value 'templating)

'(string 's)

org.apkbundler.scheme

text/
Lexer
BreakLexer
(SubSequence)Parser
HeapParser
Token
TokenType
TokenStream
SexpStream
Combinator

type/
Symbol
Reference
Chunk
Closure
Sexp
SexpList
SexpAtom
AbsStack
AbsFrame

port/
ListImpl
HashImpl
StringImpl
StringBuilderImpl
ReflectImpl

error/
Error
LexerError
ParserError
ReferenceError
TypeError
ArgumentError
Errno
ReflectError
ControlFlowTransfer

front/
Main
SchemeDebugger
SchemeServer
SchemeRepl

utils/
Coroutine
SchemeThread
Future
AbsClassLoader
(https://gitapk.popf.rip/OSS-Mirrors/BeanShell/src/branch/merge-fork-beanshell2/src/main/java/bsh/classpath/BshClassLoader.java)AbsClassPath
(https://gitapk.popf.rip/OSS-Mirrors/BeanShell/src/branch/merge-fork-beanshell2/src/main/java/bsh/classpath/BshClassPath.java)AbsClassManager
(https://gitapk.popf.rip/OSS-Mirrors/BeanShell/src/branch/merge-fork-beanshell2/src/main/java/bsh/classpath/ClassManagerImpl.java)AutoCloseable
SchemeJava
NumericOperations
AbsTyper

Stdlib
Scheme

基本数据类型
nil ()
bool #t #f
number 01 0x1 0b1001 0o122 2323N 32323D 2233.223F 66666L 6B

支持 Java 的 Byte Short Int Long Double BigInteger BigDecimal 类型
list (a b c)
string '' "" "\"" "\n" 'string
symbol dsdsd abc identifier
char 'c'

高等数据类型
chunk / closure
ref
array
pair
vector
map
enum
promise
fiber
date


行注释
; comment
任何不可识别的都作为标识符
p.s. 冰封波博客上那个字符串 escape 失败不报错作为标识符的真是奇妙

展开规则:
对任何宏:检查参数列表长度,对所有参数 sexp 列表递归展开并对标识符解引用,然后填充宏参数递归展开,如果是 varargs 宏就直接将求值后的列表填充到 varargs 参数里
^* 宏:检查参数列表长度,然后填充宏参数递归展开,如果是 varargs 宏就直接将求值后的列表填充到 varargs 参数里
. 宏:检查对应 send handler 参数列表长度,将所有参数 sexp 列表递归展开并对标识符解引用,发送调用到处理 handler

插件:
1. 取值 hook
2. 找不到变量 proc
3. SEND handler
4. 错误 hook

作用域:
全局变量 args 在每次递归展开时有效,它是惰性求值的
全局变量 __stack 储存了所有作用域表
不过它是惰性求值的
Lime 使用嵌套作用域

全局变量 __env 表示整个 Lime 全局表
__lime 表示解释器对象

"Lime 的「六个展开」"

1. (symbol arglist)
2. (. handlerid arglist)
3. ((macro sexp) arglist)
4. ()
5. (macro-value arglist)
6. object

额外展开:

^# 定义特殊的宏对象,它接受后面所有 S 表达式
(1 args) 如果对象拥有 Macro toMacro() 方法,尝试使用 toMacro() 返回的宏
否则,尝试全局的 (->macro 1) 返回的宏,如果没有定义直接失败,如果返回的不是宏也失败

这所谓「六个展开」其实可以归纳为三类

1. 宏展开

Lime 基于宏和自省功能来实现类似 Scheme 的语法,宏最终被展开为原生调用执行

(symbol arglist)
((macro sexp) arglist)
(macro-value arglist)
(object arglist)

「宏解析」允许解析「符号」、S 表达式列表、宏对象、可以转换为宏的对象

Apply(施用宏)过程中,S 表达式首项必须是一个能被解析为宏的对象
而其后皆为宏的参数,如果参数长度不对,抛出 Arity Mismatch,除非是 vararg 宏

2. 原生调用

Lime 使用原生调用赋予拥有反射能力的 JVM 方便执行动态动作的能力

(. handlerid arglist)

Lime 必须有一个手段把宏变为实际行动,这就是 Lime 如此做的途径,“原生”调用

handlerid 其后皆为宏的参数,如果参数长度不对,抛出 Arity Mismatch,除非是 vararg 接收器

3. 无展开

不进行展开,直接返回原对象

()
object

Lime 不知道该如何展开这些对象,一般它认为这些对象已经被展开,所以直接返回他们
(显然地,我不会刻意抄 Lice 的模式... 比如将 S 表达式作为 AST 处理并且给 Token 加上过多的调试信息(元数据)

不过我要理解它,并刻意把优秀的实践添加上,当然我现在还是很菜,所以连手写计算器可能都不会,接下来怎么办看学习了
Lice 模式实现检查:


Strict syntax
Lisp-style comments
Variables
Basic functions
Compare
Literals
Casts

Functional Programming

Functions are first-class

(# (a b) (+ a b))

Call by value

((# (a b) (+ a b)) 1 2) ; 3

Call by name 可以使用前置宏实现

Call-by-name 宏打算实现

Call by need 可以使用宏前置和本地引用型参数实现

Call by need 宏打算支持
新语言就被称为 sexp

打算支持手动尾递归优化、Heredoc 文本、纯函数的折叠、字符串内联代码、异常系统(和解释器内部堆栈)、方括号、动态自定义语法(类似 R6RS 的特定字符序列自动结合,当然用 Trie 树实现比较)

支持 call-by-value(称为 direct)、call-by-name(称为 evaluated)、call-by-need(称为 lazy)

类似 R6RS 的 ' 语法

允许控制流异常 .goback.gonext 步进到语句
允许 .stop 终止解释和 .yield 立刻执行权交回上层栈帧
允许 .value 直接返回值和 tailrec 直接替换调用堆栈的函数调用
允许使用 . 调用原生 Handler

函数叫做 Closure,支持 vararg(不定参) append(前置展开) eval(求值时自动展开) lazy(求值时自动展开一次,以后都使用第一次的值) pure(纯函数) noreturn(不会返回)

noarg(无参数) onearg(一个参数) twoarg(两个参数) threearg(三个参数) inline(强制内联) internal(代表内部原生展开器) void(无需返回) noexcept(不会抛出异常) nobind(不是闭包)

optimized(识别方法体所代表的操作,编译时虚拟机江创建优化指令,例如 add)

当然大部分都是没卵用的 flag(

Closure 还可以保存执行状态,可以作为 Fiber 随时挂起和恢复执行

sexp/

text/
Lexer
Parser
Token
Metadata
TokenType
ICharStream
ITokenStream
ISexpStream

type/
Symbol
Reference
Chunk
Binding
Closure
Pair

model/
Sexp
SexpList
SexpAtom

virtual/
SexpVM
Instruction
Compiler
Stack
Frame

port/
ListImpl
HashImpl
StringImpl
StringBufferImpl
ReflectImpl
ArrayImpl
VectorImpl
RealImpl
ComplexImpl

except/
LexError
ParseError
ReferenceError
TypeError
ArgumentError
ReflectError
ControlFlowException

front/
Main
SexpRepl
SexpServer

utils/
Promise
ClassHelper
JavaFFI
NumberOp
Stdlib

api/scripting/
SexpContext
ScriptEngine
EngineFactory

Sexp

执行模式和以前无异,不过这次更不 Functional 了一些(迫真),也不使用作用域优化,只是默认一般没人使用作用域,都是使用 apply

作用域还是函数调用作用域

还是支持 Locals、Globals 取值赋值 Hook、找不到变量 Hook、Send 操作 Hook、异常 Hook、公开作用域列表(也即调用帧)

1. (. handlerid arglist)
2. (symbol arglist)
3. (macro-value arglist)
4. ((macro sexp) arglist)
5. ()
6. object
7. .stop .yield
8. (.value obj)
9. (.goback) (.gonext)
10. (tailrec macro-repr args)

原生字面还是支持

1. nil ()
2. bool #t #f
3. number 01 0x1 0b1001 0o122 2323N 32323D 2233.223F 66666L 6B
支持 Java 的 Byte Short Int Long Double BigInteger BigDecimal 类型

4. list (a b c)
5. string '' "" "\"" "\n" 'string
6. symbol dsdsd abc identifier
7. char 'c'
R6RS 实现文档上有写的第三种实现方式比较有趣,但是友协无法参考,比如作用域优化中使用 box... 这里是没有必要的,因为它支持本地调用
准备定义一套新的字节码和执行方式
因为有些优化是应该理解的,所以在实现前我要先翻译好 https://www.biwascheme.org/doc/internal/opecodes.html 这个...

补一个不想让人看的,毕竟现实骨感,打算拿 PHP 写一个类似 ApkBundler 的,设计类似 ApkBundler,不过小一些,包含 web 前端

暂时叫做 ApkBin,经典的 PHP Laravel + RDBMS + RESTFul 架构
而那个暂时代替 ABS 的解释器叫做 Sexp

并且这次是以抄酷安为主收集 GeekApk 的扩展特性为辅(就是说新概念不实现,简单的添加 Sexp 插件系统),而且提供安装包托管服务
解释一下什么是 HeapParser:

之前 Lime 的 Parser 是一个递归下降法解析器,伪代码看来就是(而且,那时候 Parsing 和 Lexer 不是同时进行的):
(所以,当时的非流模式还要给递归 parser 取子序列,麻烦很多,现在不需要了)

def parse()
sexp = []

while (tok = token()) != EOS
if tok.type == LPAREN
sexp << parse()
elseif tok.type == RPAREN
return sexp
sexp << objectize(tok)

return sexp

乍一看的确不容易理解,因为递归一般比循环更加困难,我们来举例分析

假设你写了一个分词器,它只会产生数字词条序列,而你也正确的定义了 objectize() 将数值字面解析为数字对象

(1 323 3444 (23333 (1) () 666.666) (((1))))

一个复杂的 S 表达式。

result = parse()
sexp = []
while (tok = token()) != EOS # Token is LPAREN (
sexp << parse() # recursive call self
sexp = []
while (tok = token()) != EOS
# tok = 1
sexp << objectize(tok) # [1]
sexp << objectize(tok) # [1 323]
sexp << objectize(tok) # [1 323 3444]
sexp << parse() # [1 323 3444 [23333 [1] [] 666.666]]
sexp << parse() # [1 323 3444 [23333 [1] [] 666.666] [[[1]]]]
... frame ommited ..

最后的结果是 [[1 323 3444 [23333 [1] [] 666.666] [[[1]]]]]

而 HeapParser 则使用堆上的链表模拟这个解析栈,它完全不需要递归,所以被称为 HeapParser
优点是效果好、理论上能解析嵌套更深的表达式,缺点是因为堆分配太多可能增加 GC 压力
这个看不懂就算了,太长也懒得看..

def heapParse()
emultedStack = []

while (tok = token()) != EOS
if tok.type == LPAREN
emulatedStack << []
elseif tok.type == RPAREN
emulatedStack[emulatedStack.lastIndex - 1] << emulatedStack.pop()

emulatedStack[emulatedStack.lastIndex] << objectize(tok)

return emultedStack
然后写完那篇 IL 文章后我打算结合 ABS 的设计再手写

+ Ruby 版本的解释器
+ Java 版本的解释器
+ Rust no_std 版本的解释器
还是支持 Locals、Globals 取值赋值 Hook、找不到变量 Hook、Send 操作 Hook、异常 Hook、公开作用域列表(也即调用帧)
10 月二日啊,那时是