About
RSS

Bit Focus


如何弄一个在不同站点做不同事情的 Chromium 扩展

    先解释一下为什么有这个需求.
    国内似乎有不少所谓的说好听叫资源聚合网站说直白叫盗文章的网站, 虽然鄙博客文章质量很一般, 但也至少被三个不同的网站全文抓取了 (http://outofmemory.cn/ http://www.taocms.org/ http://www.tuicool.com/). 其实流量点击量什么的都不是个事, 我也没打算靠写博客赚钱, 问题是这些网站长得都太残了. (tuicool 还好一点, outofmemory 代码都没用等宽字体你那网站能看! 简直白白浪费这么好个域名) 于是就有了这么个需求: 当访问到这些网站时自动跳转到原博客页面.
    当然了各位读者不必搞这么过河拆桥的需求, 大可写个插件去展开豆瓣页面上的那些短网址什么的.

    简单看一下 Chromium 扩展的结构, 无非就是一个配置文件 (manifest.json) 加上一些 JS 文件, 有必要的话再加上一些 HTML 文件. 这里就说说最简单的, 进入一个网站在页面加载完毕之后执行一个指定 JS 文件中的代码. 那么配置文件要这么写
{
  "name": "ExtensionName",
  "version": "0.1.0",
  "description": "ext descr",
  "browser_action": {
    "default_title": "Extension Title"
  },
  "content_scripts": [
    {
      "matches": ["http://ju.outofmemory.cn/entry/*"],
      "js": ["outofmemory.js"]
    }
  ],
  "manifest_version": 2
}
    以上 JSON 中, content_scripts 部分是个数组, 其中每个元素有至少两个属性, matches 表示在 URL 满足什么条件时加载脚本, 而 js 则是加载那些脚本; 如果扩展要用到 jQuery 之类的库, 可以加到此数组最前面.
    也就是说, 上面这个例子会在所有 URL 开头为 http://ju.outofmemory.cn/entry/ 的网页上, 调用 outofmemory.js 这个文件. 而这个文件的内容很简单
-function() {
    console.log('Hello world!');
    // window.location = document.getElementsByClassName('copyright')[0].getElementsByTagName('a')[1].href;
}()
    想了一下我还是把代码藏着点, 仍然用业界标准的 Hello world 来开场好了.
    新建一个目录, 把这两个文件保存在此目录下.
    点 Chromium 浏览器菜单 -> 工具 (Tools) -> 扩展程序 (Extensions), 勾选开发者模式 (Developer mode) 下面会刷出来 3 个按钮, 最左边就是加载野生扩展, 在对话框中选中刚才新建的目录, 这样扩展就上线了. 然后挑个页面进去看看控制台吧, 比如 http://ju.outofmemory.cn/entry/81081
    到此代码能执行了, 后面就没什么需要继续说的了, 剩下就是自己抓 DOM 看该怎么搞怎么搞吧.

    附项目地址

Permanent Link: /p/523 Load full text

Post tags:

 Javascript
 Chromium 扩展

VerbalExpressions 与状态机词法分析器

VerbalExpressions

    说到字符串检索分析替换修改自然会想到正则表达式, 不过这东西实在是一个只写语言. 更改系统中一个一般复杂的正则表达式, 传统的读懂代码然后替换一条语句或者加上一个分支或者参数的模式不管用, 而是直接重写, 就像清理一个塞满的垃圾桶, 方法不是把垃圾一点点挖出来, 而是整个倒掉再铺上新的垃圾袋; 正则表达式有时太复杂了, 一条语句一个调用就顶过一打的循环和分支.
    人们总会想到一些更节省脑细胞的方式来对付字符串, 让机器理解人类的咒语, 于是发明了 VerbalExpressions.
    下面是一个 JS 的例子
var tester = VerEx()
            .startOfLine()
            .then("http")
            .maybe("s")
            .then("://")
            .maybe("www.")
            .anythingBut(" ")
            .endOfLine();
    上面这一串等价于 /^(http)(s)?(\:\/\/)(www\.)?([^\ ]*)$/ 这么个正则表达式, 不过书写起来显得科学多了; 如果需要更改逻辑, 也很容易下手到底是什么地方需要增加或者减少一点什么.

基于自动机的词法分析器

    这个轮子很有启发性, 于是乎想到以类似的方式构造个词法解析器. 接口上的愿景是类似
var t = Tokenizer();
t.simpleSymbols('+-*/=', 'operator')
 // ...
 .ignore(' ')
 .ignore('\t')
 .ignore('\r')
 // ...
 // 上面都是单独的一个字符, 接下来是循环的模式
 .loop(DIGITS)
 .accept('integer') // 以 0-9 循环的模式, 接受为整数类型

 .startWith(LETTERS)
 .loop(LETTERS + DIGITS)
 .accept('identifier') // 以字母开头, 数字和字母循环的模式, 接受为标识符

 // 接下来是保留字
 .fixed('if')
 .fixed('for')
 // 以及一些超过 1 字符的操作符
 .fixed('==', 'operator')
 .fixed('<=', 'operator')
 // ...
;
var inputString = 'for (i = 0; i < 10; i = i + 1) { if (i % 3 == 0) { print(i); } }';
var tokenArray = t.tokenize(inputString);
console.log(tokenArray);
    看起来应该是这么回事. 以这种方式构造出来的东西应该是一个状态机而不是一大波正则表达式形成的集群. 因此首先得构造一个状态数据结构. 作为一个演示就不弄太复杂了, 它看起来类似

Permanent Link: /p/519 Load full text

Post tags:

 Javascript
 VerbalExpressions
 Compiler Construction

记一些 (没) 有意义的 reduce 用法

    在 Python 或 Javascript 等许多语言中都有 reduce 函数. 其中 Python 中 reduce 作为全局函数出现, 而 Javascript 中则是 Array 的成员函数. 大量的用 reduce 来做累加累乘之类的例子就不说了, 这里探讨一个特殊的用例.
    前端经常会需要将页面中用户填写的一些内容打包成 JSON 字典, 比如一个注册页面片段
<div>
    <input id='email' placeholder='Email'>
    <input id='password' placeholder='Password'>
    <input id='conform_password' placeholder='Confirm Password'>
    <input id='address' placeholder='Address'>
    <input id='phonenum' placeholder='Phone Number'>
    <button id='subm'>Submit</button>
</div>

<script>
document.getElementById('subm').onclick = function() {
    var inputValues = {
        email: document.getElementById('email').value,
        password: document.getElementById('password').value,
        address: document.getElementById('address').value,
        phonenum: document.getElementById('phonenum').value
    };
    /* process inputValues */
};
</script>
    以后每次这个表单多一项时, 构造 inputValues 时就会多一项, 代码维护会很烦.
    如果能这样写的话可能会好一些
var inputValues = {k: document.getElementById(k).value
                   for k in ['email', 'password', 'address', 'phonenum']};
    可惜 Javascript 里面没有温暖人心的 dict comprehension... 于是, 就有了下面这种 reduce 替代品 (终于正题了)
var inputValues = ['email', 'password', 'address', 'phonenum'].reduce(
    function(obj, item) {
        obj[item] = document.getElementById(item).value;
        return obj;
    }, {}));

Permanent Link: /p/515 Load full text

Post tags:

 Javascript
 函数式程序设计
 Python

移花接木 - 编译同步代码为异步代码 [条件表达式篇]

上篇

条件表达式

    如果设计如下的类生成 Javascript 中条件表达式 predicate ? consequence : alternative
# semantic.py

class Conditional(Expression):
    def __init__(self, predicate, consequence, alternative):
        self.predicate = predicate
        self.consequence = consequence
        self.alternative = alternative

    def compile(self, context):
        return output.Conditional(self.predicate.compile(context),
                                  self.consequence.compile(context),
                                  self.alternative.compile(context))
# output.py

class Conditional(Expression):
    def __init__(self, predicate, consequence, alternative):
        self.predicate = predicate
        self.consequence = consequence
        self.alternative = alternative

    def str(self):
        return '({p} ? {c} : {a})'.format(p=self.predicate.str(),
                                          c=self.consequence.str(),
                                          a=self.alternative.str())
    这在条件表达式的三个子表达式均不包含异步调用时, 或仅 predicate 包含正规异步调用没问题, 如
root_context = ContextFlow()
root_flow = root_context.block

Block([
        Arithmetics(
            Binary(
                '=',
                Reference('m'),
                Conditional(
                    Reference('a'),
                    Reference('b'),
                    Reference('c')))),
        Arithmetics(
            Binary(
                '=',
                Reference('n'),
                Conditional(
                    RegularAsyncCall(
                        Reference('f'),
                        [ NumericLiteral(0) ]),
                    Reference('x'),
                    Reference('y')))),
    ]).compile(root_context)

print root_flow.str()
    这些节点编译后会生成类似如下代码

Permanent Link: /p/508 Load full text

Post tags:

 Python
 Javascript
 Asynchronous Expression
 Compiler Construction

移花接木 - 编译同步代码为异步代码 [补述篇]

上篇

编译步骤解释

多个正规异步调用出现在同一个语句中的编译顺序

    RegularAsyncCall 对象的 compile 代码如下
def compile(self, context):
    # 0
    compl_callee = self.callee.compile(context)

    # 1
    compl_args = [ arg.compile(context) for arg in self.arguments ]

    # 2
    callback_body_context = ContextFlow()
    cb_body_flow = callback_body_context.block
    compl_args.append(output.Lambda([ 'error', 'result' ], cb_body_flow))

    # 3
    context.block.add(output.Arithmetics(output.Call(compl_callee, compl_args)))

    # 4
    context.block = cb_body_flow

    return output.Reference('result')
    步骤依次是
  • 0 编译被调用对象
  • 1 依次编译调用实参
  • 2 向实参列表中追加一个匿名函数, 函数体语句流是新建的一个 ContextFlow 中的语句流
  • 3 向当前上下文语句流中插入一个算术节点, 节点的内容是编译后的异步调用
  • 4 修改上下文语句流
    它们的顺序不可随意颠倒, 不仅仅因为有些步骤依赖于前面的步骤 (比如步骤 3 构造 Call 对象须 0 与 1 中得到的结果), 更多地是因为在编译过程中上下文语句流随时可能变化. 比如下面这个例子
async_call('xx', %%)('oo', %%)
这应当生成下面这段 Javascript 代码
async_call('xx', function(error, result) {
    result('oo', function(error, result) {
        result;
    });
});
    async_call('xx', %%) 编译时第一次修改了上下文语句流, 而之后对前一次异步调用结果的调用再一次修改了上下文语句流. 用语法树来表示即

+-------------+
| origin flow |
+-------------+
       |         +-------------+
       +---------| arithmetics |
                 +-------------+
                        |
            [A]====================+
              | regular async call |
              +====================+
                  /               \
    [B]====================+    +------+
      | regular async call |    | 'oo' |
      +====================+    +------+
           /           \
    +------------+  +------+
    | async_call |  | 'xx' |
    +------------+  +------+

Permanent Link: /p/507 Load full text

Post tags:

 Python
 Javascript
 Asynchronous Expression
 Compiler Construction

移花接木 - 编译同步代码为异步代码 [数据结构篇]

目标

    我说 JS 代码的表达能力是一流的, 可能一票 Python 党 (作为高级黑, 我不得不说我自己也是个 Python 党) 要笑了. 我是说真的, Python 的 Lambda 基本上就是半残废, 所以, 要用 Python 写异步代码那就像是用挂面上吊一样不来劲了.
    然而任何写 JS 代码遇到三层以上的异步代码套在一起的时候手撕键盘的心都有了, 蔽博先前也介绍了一个工具 (确切地说是玩具) 用来使用类似同步的代码生成异步代码. 而现在则有更明确的目标, 如把这样一段代码
fs: require('fs')
content: fs.readFile('/etc/passwd', %%)
console.log(content)
变成这样一段 Javascript
var fs, content;
fs = require('fs');
fs.readFile('etc/passwd', function(err, result) {
    content = result;
    console.log(content);
});
    为什么是这样? 首先 JS 中很多异步调用的回调函数都遵循 function(error, result) 这个形式, 这俨然成为了 Javascript 中的一种约定, 因此在接下来的分析中把这样的回调函数参数称为正规异步回调 (regular asynchronous callback). 既然它们都是这么个形式, 那么写代码的时候完全可以不这么罗嗦写个回调声明, 而是用 %% 这个符号来替代它.
    而包含正规异步回调的函数调用称之为正规异步调用 (regular asynchronous call).

    更详细地说, fs.readFile('/etc/passwd', %%) 这个表达式的意思是
  • 调用 fs.readFile
  • 第一个参数是字符串常量 '/etc/passwd'
  • 第二个参数是一个正规异步回调函数
  • 从这个表达式之后, 所有的语句归入上述正规异步回调函数的函数体中
    那么下面要做的事请是, 如果在一次编译过程中, 语法分析已经完成, 对应的同步代码的语法树已经建起来, 怎么根据上述规则去生成异步代码.

    比如上面的例子, 源语法树形式是

Permanent Link: /p/506 Load full text

Post tags:

 Compiler Construction
 Python
 Javascript
 Asynchronous Expression

位运算的优先级

    在现代计算机程序设计语言中, 每一种成熟的语言都支持许多算符. 对于除了像 Lisp 那样表达式本身以中缀式形式书写的语言, 一般语言都会规定表达式的结合顺序, 也就是算符优先级. 由于语言算符数量的庞大, 算符优先级很难整个记下来, 但是通常有迹可循, 比如看到下面的代码
x = y + z
一般会自然地认为 + 将在 = 之前执行, 这是因为很多语言的编译器设计中, 赋值类运算在算术类运算之后进行, 可以认为这是一个常识, 跟乘除在加减之前进行一样自然. 同理下面的代码
x == y and y == z
x == y + z
很自然地能想到, 比较运算 == 会在逻辑运算 and 之前, 而算术运算 + 应该在比较运算之前.

    如果要给算符分个类. 以整数运算为例, 日常二元算符分类的大类应该按照运算结果的类型来分类, 也就是
结果类型算符
整数+ - * / % ^ & | >> <<
boolean== != < > >= <= or and
    从结果类型上也可以看出一些端倪, 比如
x == y + z
这个算式, 如果先计算 ==, 此时 + 左边是 boolean, 而右边是个整数, 这样根本没法圆场了. 所以一般来说, 算术运算拥有较高的优先级完全是天经地义的.

    现在的问题是, 位运算 (^ & |) 到底算啥?
    从之前的说法来看, 它们也应该是算术运算的一种. 不过优先级还真不好说. 最近在某个项目中我将一个后台功能转移到前台, 需要把一段 Python 代码转换成 Javascript 代码, 这时发生了一个悲剧: Python 对于位运算优先级的实现与我的个人观点非常接近, 认为时至今日, 位运算是日常的算术运算之一, 因此它们的优先级要高于比较运算, 也就是说
x + y == x ^ y
在 Python 中等价于
(x + y) == (x ^ y)
而与之相对的, Javascript 认为位运算的优先级要低, 甚至低过了比较运算, 因此上述代码在 V8 引擎眼中等价于
((x + y) == x) ^ y
    这种槽点爆表的设计真是坑死爹了. 好吧, 我去溯源一下, 谁家的语言设计这么疼. 结果原来 C 语言老大哥是这么个整法的, 什么 Javascript 还有个跟这名字差不多不过没有 script 的语言这么承袭的. 哎, 你们这么折腾到底图个啥啊.

Permanent Link: /p/454 Load full text

Post tags:

 Javascript
 Python
 C
 Operator Precedence


. Back to Bit Focus
NijiPress - Copyright (C) Neuron Teckid @ Bit Focus
About this site