/tmp/duangsuse.sock
23 subscribers
303 photos
3 videos
92 files
337 links
从 duangsuse::Echo (@dsuse) 跟进出来的分支,将在作者恢复原帐号访问的时候合并删除。
Download Telegram
『以为是各种问题很奇怪,其实是因为思路不清晰,把简单的问题弄复杂了』
辣鸡架构,拜拜喽!
const p = require('./fp').parserc;
let __ = p.wsP(), _ = p.ws0P();

function NumList(l, ws0, rxs) {
this.wss = []; this.ary = [];
this.wss.push(ws0); // {ws0} x...
if (rxs === ']') { return; }
this.ary.push(rxs[0]); this.wss.push(rxs[1]); // x {a}, y, ..
for (let [wl, x, wr] of rxs[2]) { this.ary.push(x); this.wss.push(wl, wr); }
}
let numP = p.someFold(p.elemP('0123456789'.split(''), 'digit'), [0, (a,x) => a*10+(x-'0')], f => 'number value expected');
let pa = p.seq([p.charP(','), _, numP, _], xs => [xs[1], xs[2], xs[3]]);
let List_ItemsP = p.manyFold(pa, [p.makeNew(Array), (v,xs)=>{v.push(xs); return v;}]);
let ListP = p.seq([ p.charP('['), _, p.possible([ p.charP(']'), p.seq([numP, _, List_ItemsP, p.charP(']')]) ]) ], p.makeNew(NumList));
pp = p.run(ListP);


尾逗号的问题是正常情况,因为这个毕竟是 manyFold, 解析到不能匹配了就 fail... 第一个 charP 还是可以解析那个尾逗号的
如果需要不支持尾逗号的话,还需语法变形才行...(当然你也可以设置 pa 的 msgr 为直接 throw error 的那种,所有组合基本都只会 handle pfail,不会 handle msgr 抛出的 exception)
『就说呢,一定是可以做到的!』 这样就有了第一门自己的 markup language!
怎么样~ 成功了吗?
This media is not supported in your browser
VIEW IN TELEGRAM
接下来,一起实现这门新的 『DEF』 DSL (领域专属语言)语言吧! #Parser #PLT

使用 FP.js 的解析组合子功能,就可以快速制造各种拥有良好用户界面和错误处理的的 DSL!

就像之前写 GeekSpec 的时候说的:

面向语言编程,是个不错的逻辑。如果你觉得要处理的问题使用某门语言的文法十分鸡肋冗长繁复难看,为什么不创建一门专门的语言来清晰地描述它呢?
在完成这门 『DEF』 DSL 只后,你还可以写更多更好、甚至写的更像一种通用编程语言的 DSL

并且,以后的编程路上,拥有这类 DSL 的能力会让你写出更优质的程序、提升你编程的速度,避免模板代码的编写、完成更有特色的程序...
/tmp/duangsuse.sock
那咱来看一下这个 继续 🐱 我们刚才说道了第一个组合解析器 — seq 函数,现在测试一下 首先,你需要同步 FP.js 库的版本,以引入新的一些抽象操作 const p = require('./fp').parserc; function DefItem(_d, w0, name, w1, _eq, w2, val) { this.wss = [w0, w1, w2]; this.name = name; this.value = val; } ps = p.seq([p.kwP('def')…
有了这些语法定义和之前对 def abc = 123 \ [1,2,3,4] 的解析程序,我们现在已经可以实现这样的语法了:

TheDefLang = {{ Def }}
Def = (<def> Name <=> Value NL) | (<lets> Name Name) | (<just> Name)
NL = '\n'|'\r'
Letter = ('a'~'z'|'A'~'Z'|'_')
Digit = ('0'~'9')
Name = Letter (Letter|Digit)*
Value = Bool | Num | Str | Name | Array | Map

Bool = <true> | <false>
Num = Digit+
Str = <"> char* <">
Array = <[> (<]> | Value { <,> Value } <]>)
KVPair = Name <:> Value
Map = <{> (<}> | { KVPair { <;> KVPair } <}>)

Def 语言是一门简单的数据描述语言,你可以定义(全局)变量、引用变量、赋值变量、返回变量。
用啥 XML 呢???万能的 XML。

幸运的是,这意味着你已经可以编写 GeekSpec 这样的 DSL 了!(记得 GeekSpec 可是把之前需要两天的『劳动密集』繁复任务量,缩减到了两个小时!)可能过一段时间我会再写更多的,甚至实现一个 ES5 (JavaScript)的解析器... 之前本频道也有教写计算器(动态二元运算符优先级解析程序)什么的

下面我只发实现,如果你还不知道怎么做,请复习一下刚才的内容,动手实践一下
如果还不知道的话,大概是需要时间理解了。
利用惰性导出和代理,来实现递归(没 Y 组合子的好,迫真,虽然 Y 组合子是递归自己...)
第一个完全自己写的数据描述语言? 🤔
编写的直播到此就 告一段落了,接下来是一些语言工具设计的考虑。
真香
很好的功能,国内呵呵。
就凭国内大部分程序员的水平,和小部分真·大佬的速度和创意,想\做不到这种东西
至少在信号处理和语音合成层面,国外比国内强很多
/tmp/duangsuse.sock
第一个完全自己写的数据描述语言? 🤔 编写的直播到此就 告一段落了,接下来是一些语言工具设计的考虑。
三个附加项目 —
0. 如何实现 DEF 语言的 AST Walker 求值器
1. 如何实现 DEF 语言的 REPL(不包含如何给 LookAhead1 流加不需要打两个 <NEWLINE> 即可求值的技术,因为我也不知道,好像很困难或者不容易零开销\太具侵入式)
2. 如何实现 DEF 语言的自动格式化 — 包含两种
一种是直接从 AST 输出、一种是检查 & 自动化提供矫正方案(在某个位置删除几个空格)
首先是咱们的求值器,咱的复用解析器,已经被作为一个 CommonJS 模块包装好了。
下面来科普下,怎么写简单的求值器 — 就是后序遍历语法树。

为什么我们要自顶向下、从左到右解析这个代码呢?因为代码存在诸多分支和不确定,右边最终 reduce 出的结构可能依赖左边的输入字符来决定。LookAhead(1) 也是这个原因(因为不允许重置流,但有时候如果没有手段去重置的话,很多语法哪怕是 "" 字符串都几乎不能实现)

为什么我们要后序遍历语法树?

求值的顺序(AST Walker 对语法树遍历,或者说扩普排序的顺序)有关系吗?
如果保证所有节点都是『纯』的,它不依赖也不改变外部的环境(不是说对子节点的那种『值』的依赖)
则求值序实际上关系不大,因为只有对值本身的依赖

后序是什么意思?

语法的属性上,有综合节点和继承节点的说法:前者的『值』依赖自己的子节点;后者的『值』依赖自己的父节点
因为大部分节点都会依赖自己子节点的值,所以往往要先求值子节点、再父节点

data Add = Lit Int | Add (Add, Add)
sum :: Add -> Int
sum (Add a, Add b) = sum a + sum b
sum (Lit x) = x

— edit: 之前误把这个 Add 定义成 newtype (single product) 了,我没主意到它其实是 sum type(tagged union)... 现在已经修复 致歉

所以要先算子节点,在依据孙子们的值,去解决爸爸(跑

— 所以应该怎么写

咱只需一席话语,̶饶̶舌(跑

考虑一下咱的实际语法,归纳一下呗。然后定义一个抽象语义。

我们的 Entry 是 Term — 就是一个 def|lets|just 项目

Term = DefT | LetsT | JustT

DefT = 'def' _ Name '=' Value
letter = [a-zA-Z_]
digit = [0-9]
Name = letter (letter | digit)*
Value = Name | Bool | Num | Str | List | KvList
Bool(Name) = "true" | "false"
Num = digit+
Str = '"' [any ->]* '"' -- '->' 的意思是 [any - '"']*

ListItems = (',' Value)*
List = '[' ( ']' | Value ListItems ']' )

KvPair = Value ':' Value
KvListItems = ( (','|';') KvPair)*
KvList = '{' ( '}' | KvPair KvListItems '}' )

LetsT = 'lets' Name Name
JustT = 'just' Name


我们认为,这些东西的语义应该是『对数据结构进行组织』
所以:

def name = value 没有值,但是有一个副作用 — 把 value 赋值到全局环境的 name
lets name funame 没有值,但是有一个副作用 — Global[name] = getFun(funame) (Global[name])
just name 没有值,但它把 Global[name] 推入输出值列表

Value:
Name ident 的值是 Global[ident]
Bool 的值是 true / false
Num 的值是 Number 数字
Str 的值是 String
List 的值是 Array
KvList 的值是 Object

就这样,我们的求值程序的目标是逐条语句执行这些代码,操作全局状态机;最终输出 just 操作过的列表。
非常抱歉... 因为时间不够的原因,这次就只教这个了(请大家原谅呐