MuJS官網示例講解

前提:已經在linux中安裝好MuJS,MuJS安裝比較簡單,參考安裝包中的readme文件
本章介紹的環境:vm+centos6.5 32bit
官網示例鏈接:http://dev.mujs.com/docs/examples.html

示例1
A stand-alone interpreter
interpreter.c

#include <stdio.h>
#include <mujs.h>

int main(int argc, char **argv)
{
    char line[256];
    int ret;
    //js_State *J = js_newstate(NULL, NULL, 0);
    js_State *J = js_newstate(NULL, NULL, JS_STRICT);
    //printf("JS_STRICT:%d\n", JS_STRICT);
    while(fgets(line, sizeof(line), stdin))
    {
        ret=js_dostring(J, line);
        //printf("dostring ret:%d\n", ret);
    }
    js_freestate(J);

    return 0;
}

標題:一個獨立的解釋器
功能:藉助mujs的庫用C實現的一個javascript腳本解釋器;編譯和運行由標準輸入得到的js腳本命令,如果腳本有錯誤,則返回錯誤信息。
下面是編譯操作,我這裏加上了g選項用於gdb調試
這裏寫圖片描述

執行結果如下
這裏寫圖片描述
輸入var a = 1; 回車系統執行成功,未報異常;輸入b=2則返回異常,提示b變量未定義。在一般的javascript模式下定義變量不帶var是可以的,但在strict嚴格模式下不帶var是不允許的。
在mujs.h中找到JS_STRICT的宏定義,flag標記只有一個值

/* State constructor flags */
enum {
    JS_STRICT = 1,
};

下面做個嘗試,將flag設置爲0 ,即執行代碼

js_State *J = js_newstate(NULL, NULL, 0);

編譯後,我們可以看到在終端中輸入b=0時就沒有上報異常了。

我們結合前一篇參考手冊中js_dostring函數的說明來看
函數原型

int js_dostring(js_State *J, const char *source); 
// J爲js_State指針,與c進行交互的關鍵集合,需要先進行創建, 
// source爲javascript腳本,字符串格式。
// 如果發生錯誤,調用report上報異常,並返回1; 返回0表示成功。

所以放開interpreter.c中的註釋,可以看到執行成功時ret的值爲0,失敗時爲1。

下面我們重點看看js_State結構體,這個太重要了,javascript和c的互相作用就靠它了,在文件jsi.h中可以找到定義。

/* State struct */

struct js_State
{
    void *actx;
    void *uctx;
    js_Alloc alloc;
    js_Report report;
    js_Panic panic;

    js_StringNode *strings;

    int default_strict;
    int strict;

    ......
    ......

    int nextref; /* for js_ref use */
    js_Object *R; /* registry of hidden values */
    js_Object *G; /* the global object */
    js_Environment *E; /* current environment scope */
    js_Environment *GE; /* global environment scope (at the root) */

    /* execution stack */
    int top, bot;
    js_Value *stack;

    /* garbage collector list */
    int gcmark;
    int gccounter;
    js_Environment *gcenv;
    js_Function *gcfun;
    js_Object *gcobj;
    js_String *gcstr;

    /* environments on the call stack but currently not in scope */
    int envtop;
    js_Environment *envstack[JS_ENVLIMIT];

    /* debug info stack trace */
    int tracetop;
    js_StackTrace trace[JS_ENVLIMIT];

    /* exception stack */
    int trytop;
    js_Jumpbuf trybuf[JS_TRYLIMIT]; //異常跟蹤 jump_buf類型,被setjmp調用。
};

結構體中的註釋已經很清楚了,裏面stack有:
1.execution stack
2.environments on the call stack but currently not in scope
3.debug info stack trace
4.exception stack

示例2
Hello, world!
hello.c

//Hello, world!

#include <stdio.h>
#include <mujs.h>

static void hello(js_State *J)
{
    const char *name = js_tostring(J, 1);
    printf("Hello, %s!\n", name);
    js_pushundefined(J);
}

int main(int argc, char **argv)
{
    js_State *J = js_newstate(NULL, NULL, JS_STRICT);

    js_newcfunction(J, hello, "hello", 1);
    js_setglobal(J, "hello"); //設置爲全局變量

    js_dostring(J, "hello('world');"); // 調用javascript

    js_freestate(J); // 釋放J
}

執行結果爲:
Hello world!
基本的邏輯如下圖,hello函數是自定義的函數,用c語言實現,並封裝爲“js_CFunction”類型。javascript腳本通過js_newcfunction創建(註冊)hello函數。然後c主程序調用js_dostring函數加載js腳本,在腳本中hello(‘world)’就是完成對hello函數的調用,帶入的’world’爲傳入參數。
這裏寫圖片描述
我們逐行代碼解讀,在進入到函數內部是,點到爲止,不再深入。

首先看看hello函數

// 函數定義遵循js_CFunction的函數指針定義
// 在mujs中js_CFunction的定義爲
// typedef void (*js_CFunction)(js_State *J);
static void hello(js_State *J)
{
    // 從J->stack的1位置獲取參數1,本例只有1個參數,且參數類型爲
    // string,所以調用js_tostring(J, 1)
    const char *name = js_tostring(J, 1);
    printf("Hello, %s!\n", name);
    js_pushundefined(J); // 無返回值,壓入undefined補全
}
typedef void (*js_CFunction)(js_State *J);
/*
由定義可知,創建javascript腳本可調用的函數,返回類型void, 函數參數只有js_State *J。
*/

現在對程序進行修改,加深對J->stack的理解

#include <stdio.h>
#include <mujs.h>

static void hello(js_State *J)
{
    const char *name = js_tostring(J, 1);
    int num = js_tonumber(J, 2); // 獲取第二個參數
    printf("Hello, %s! num:%d\n", name, num);
    js_pushundefined(J);
}


int main(int argc, char **argv)
{
    js_State *J = js_newstate(NULL, NULL, JS_STRICT);

    js_newcfunction(J, hello, "hello", 2);// 2個參數

    js_setglobal(J, "hello");

    js_dostring(J, "hello('world',100);");

    js_freestate(J);

    return 0;
}

執行結果
Hello, world! num:100

再修改爲簡單一點的調用,這樣便於我們理解參考手冊中參數的放置順序
printargs.c

#include <stdio.h>
#include <mujs.h>


static void printargs(js_State *J)
{
    int this_value = js_tonumber(J, 0);
    int arg1 = js_tonumber(J, 1);
    int arg2 = js_tonumber(J, 2);
    int negative_arg1 = js_tonumber(J, -1);
    int negative_arg2 = js_tonumber(J, -2);
    int negative_this_value = js_tonumber(J, -3);
    printf("this value:%x\n", this_value);
    printf("arg1:%d\n", arg1);
    printf("arg2:%d\n", arg2);
    printf("negative arg1:%d\n", negative_arg1);
    printf("negative arg2:%d\n", negative_arg2);
    printf("negative this value:%x\n", negative_this_value);

}

int main(void)
{
  js_State *J = js_newstate(NULL, NULL, JS_STRICT);
  js_newcfunction(J, printargs, "printargs", 2);
  js_setglobal(J, "printargs");
  js_dostring(J, "printargs(1,2);");

  js_freestate(J);
  return 0;

}

編譯後執行結果

[root@bogon javascript]# gcc printargs.c -lmujs -g -o printargs
[root@bogon javascript]# ./printargs
this value:80000000
arg1:1
arg2:2
negative arg1:2
negative arg2:1
negative this value:8000000

js_State->stack中的參數和this value排序如圖,正序從下往上0,1,2;逆序從上往下,-1,-2,-3。this value的值爲0x80000000,屬於無效值

這裏寫圖片描述

示例3
Configuration file
從config.js腳本中加載全局變量
config.js,包含全局變量foo coo 和 局部變量boo

var foo=2;
var coo = "string"
function afunc()
{
    var boo = 100; 
}

config_file.c

#include <stdio.h>
#include <mujs.h>


int main(void)
{
    js_State *J = js_newstate(NULL, NULL, JS_STRICT);
    js_dofile(J, "config.js"); // 加載js文件
    js_getglobal(J, "foo");    // 獲取js中的全局變量foo
    int foo = js_tonumber(J, -1);
    printf("foo: %d\n", foo);
    js_pop(J,1);               // 彈出棧頂1個元素,即foo
    foo = 0;
    //  js_getglobal(J, "foo"); // 如放開註釋,foo可以再次獲取
    foo = js_tonumber(J, -1); // 此時再獲取foo,則已經沒有了
    printf("foo: %d\n", foo);
    return 0;
}

編譯後執行結果

[root@bogon javascript]# ./config_file
foo: 2
foo: -2147483648

這裏寫圖片描述

修改一下例子,再看看
config_file_ex.c

#include <stdio.h>
#include <mujs.h>

int main(void)
{
    js_State *J = js_newstate(NULL, NULL, JS_STRICT);
    js_dofile(J, "config.js");
    js_getglobal(J, "foo");
    js_getglobal(J, "coo");
    js_getglobal(J, "boo");
    int foo = js_tonumber(J, -3);
    char *coo = js_tostring(J, -2);
    int boo = js_tonumber(J, -1);
    printf("foo: %d\n", foo);
    printf("coo: %s\n", coo);
    printf("boo: %d\n", boo);
    js_pop(J,1);
    foo = 0;
    foo = js_tonumber(J, -2);
    coo = NULL;
    coo = js_tostring(J, -1);
    printf("foo: %d\n", foo);
    printf("coo: %s\n", coo);
    return 0;
}

執行結果

[root@bogon javascript]# ./config_file_ex1
foo: 2
coo: string
boo: -2147483648
foo: 2
coo: string

局部變量boo無法獲取,在執行js_getglobal(J, “boo”)時,也會壓入一個空值undefined。

再看一個例子
config_file_ex2.c

#include <stdio.h>
#include <mujs.h>

int main(void)
{
    js_State *J = js_newstate(NULL, NULL, JS_STRICT);
    js_dofile(J, "config.js");
    js_getglobal(J, "coo"); /* js全局變量 */
    js_getglobal(J, "foo"); /* js全局變量 */
    js_getglobal(J, "doo"); /* 無效 */
    js_getglobal(J, "eoo"); /* 無效 */
    char *coo = js_tostring(J, -4);
    int foo = js_tonumber(J, -3);
    int doo = js_tonumber(J, -2);
    int eoo = js_tonumber(J, -1);
    printf("foo: %d\n", foo);
    printf("coo: %s\n", coo);
    printf("doo: %d\n", doo);
    printf("eoo: %d\n", eoo);
    js_pop(J,2);
    coo = js_tostring(J, -2);
    foo = js_tonumber(J, -1);
    printf("*******poped*******\n");
    printf("foo: %d\n", foo);
    printf("coo: %s\n", coo);

    return 0;

}

執行結果

[root@bogon javascript]# ./config_file_ex2
foo: 2
coo: string
doo: -2147483648
eoo: -2147483648
*******poped*******
foo: 2
coo: string

doo和eoo沒有,所以獲取失敗,但也會壓棧佔用堆棧空間;棧頂排序和js腳本中全局變量的位置無關,只與壓入堆棧的順序有關;這種全局變量的獲取,應該也包含函數變量,這個目前只是猜測,後面用例子來驗證。

示例4
Object manipulation
源碼

// t = { foo: 42, bar: true }

js_newobject(J);
{
    js_pushnumber(J, 42);
    js_setproperty(J, -2, "foo");
    js_pushboolean(J, 1);
    js_setproperty(J, -2, "bar");
}
js_setglobal(J, "t");

擴展爲可執行程序,代碼如下
object1.c

#include <stdio.h>
#include <mujs.h>

struct T
{
    int foo;
    unsigned char bar;
};

int main(void)
{
    js_State *J = js_newstate(NULL, NULL, JS_STRICT);
    // t = {foo:42, bar:12};
    js_newobject(J);
    {
        js_pushnumber(J, 42);
        js_setproperty(J, -2, "foo");
        js_pushnumber(J, 12);
        js_setproperty(J, -2, "bar");
    }
    js_setglobal(J, "t");

    struct T t;
    js_getglobal(J, "t"); // 找到t並將object壓棧
    js_getproperty(J, -1, "foo"); // 此時t在棧頂,得到foo後再將foo的值壓棧
    js_getproperty(J, -2, "bar"); // 此時t已經下沉一次,所以idx爲-2
    t.foo = js_tonumber(J, -2);
    t.bar = js_tonumber(J, -1); 
    printf("t.foo:%d, t.bar:%d\n", t.foo, t.bar);
    return 0;
}

執行結果

[root@bogon javascript]# gcc object1.c -lmujs -g  -o object1
[root@bogon javascript]# ./object1
t.foo:42, t.bar:12

這裏面難點是在於理解js_setproperty中的idx參數,例子中都是”-2”,這個值是如何確定的,官方的參考手冊too simple了,根本沒有解釋,只能去啃源碼。
void js_setproperty(js_State *J, int idx, const char *name);
下面用堆棧圖來說明object的創建和引用步驟
這裏寫圖片描述

我們可以通過一個例子來驗證,在創建對象時增加屬性,經過了以下幾個步驟:
1、創建object,將object壓入stack;
2、壓入屬性,可能是number, string, boolean等等
3、將屬性與object關聯(加入到object的property chain),丟棄該屬性
4、如果要繼續添加屬性,重複2,3步驟

object2.c

#include <stdio.h>
#include <mujs.h>

struct T
{
    int foo;
    unsigned char bar;
};

int main(void)
{
    js_State *J = js_newstate(NULL, NULL, JS_STRICT);
    js_dofile(J, "object.js");
    int i;
    js_pushnumber(J, 100);
    // t = {foo:42, bar:12};
    js_newobject(J);
    {
    i = js_tonumber(J, -2);
        printf("i:%d\n", i);
        js_pushnumber(J, 42);
        js_setproperty(J, -2, "foo");
        i = js_tonumber(J, -2);
        printf("i:%d\n", i);
        js_pushnumber(J, 12);
        js_setproperty(J, -2, "bar");
    }
    js_setglobal(J, "t");
    i = js_tonumber(J, -1);
    printf("i:%d\n", i);
}

執行結果

[root@bogon javascript]# gcc object2.c -lmujs -g  -o object2
[root@bogon javascript]# ./object2
i:100
i:100
i:100

三個地方打印的i都是100,都獲取到了number的值,所以大家可以自己對照着看

示例5
Callbacks from C to JS (by name)

static int call_callback(js_State *J, const char *arg1, int arg2)
{
    int result;

    /* Find the function to call. */
    js_getglobal(J, "my_callback");

    /* Push arguments to function. */
    js_pushnull(J); /* the 'this' object to use */
    js_pushstring(J, arg1);
    js_pushnumber(J, arg2);

    /* Call function and check for exceptions. */
    if (js_pcall(J, 2)) {
        fprintf(stderr, "an exception occurred in the javascript callback\n");
        js_pop(J, 1);
        return -1;
    }

    /* Retrieve return value. */
    result = js_tonumber(J, -1);
    js_pop(J, 1);

    return result;
}

擴展爲可執行程序
callback1.c

#include <stdio.h>
#include <mujs.h>


static void jsB_print(js_State *J)
{
    int i, top = js_gettop(J);
    for (i = 1; i < top; ++i) {
        const char *s = js_tostring(J, i);
        if (i > 1) putchar(' ');
        fputs(s, stdout);
    }
    putchar('\n');
    js_pushundefined(J);
}

static int call_callback(js_State *J, const char *arg1, int arg2)
{
    int result;

    /* Find the function to call. */
    js_getglobal(J, "my_callback");

    /* Push arguments to function. */
    js_pushnull(J); /* the 'this' object to use */
    js_pushstring(J, arg1);
    js_pushnumber(J, arg2);

    /* Call function and check for exceptions. */
    if (js_pcall(J, 2)) {
        fprintf(stderr, "an exception occurred in the javascript callback\n");
        js_pop(J, 1);
        return -1;
    }

    /* Retrieve return value. */
    result = js_tonumber(J, -1);
    js_pop(J, 1);

    return result;
}

void main(void)
{
    js_State * J = js_newstate(NULL, NULL, JS_STRICT);
    js_dofile(J, "callback.js");

    js_newcfunction(J, jsB_print, "print", 0);
    js_setglobal(J, "print");

    int ret = call_callback(J, "Hello world!", 100);

    printf("ret:%d\n", ret);
}

對應的callback.js腳本也很簡單

function my_callback(arg1, arg2)
{
    print(arg1);
    return (arg2+2);
}

執行結果

[root@bogon javascript]# gcc callback1.c -g -lmujs -o callback1
[root@bogon javascript]# ./callback1
Hello world!
ret:102

源碼簡單,註釋也相對完整,功能不多說,還是畫一下堆棧圖,這樣就比較清楚,在這之前,還是用之前壓入一個number值來測試不同階段number值的位置來看看,整個過程做了哪些操作。

callback2.c

#include <stdio.h>
#include <mujs.h>


static void jsB_print(js_State *J)
{
    int i, top = js_gettop(J);
    for (i = 1; i < top; ++i) {
        const char *s = js_tostring(J, i);
        if (i > 1) putchar(' ');
        fputs(s, stdout);
    }
    putchar('\n');
    js_pushundefined(J);
}
static int call_callback(js_State *J, const char *arg1, int arg2)
{
    int result;
    int i;
    js_pushnumber(J, 100);

    /* Find the function to call. */
    js_getglobal(J, "my_callback");

    i = js_tonumber(J, -2); // 1
    printf("i:%d\n", i);

    /* Push arguments to function. */
    js_pushnull(J); /* the 'this' object to use */
    js_pushstring(J, arg1);
    js_pushnumber(J, arg2);

    i = js_tonumber(J, -5); // 2
    printf("i:%d\n", i);

    /* Call function and check for exceptions. */
    if (js_pcall(J, 2)) {
        fprintf(stderr, "an exception occurred in the javascript callback\n");
        js_pop(J, 1);
        return -1;
    }

    i = js_tonumber(J, -2); // 3
    printf("i:%d\n", i);

    /* Retrieve return value. */
    result = js_tonumber(J, -1);
    js_pop(J, 1);

    i = js_tonumber(J, -1); // 4
    printf("i:%d\n", i);
    return result;
}

執行結果

[root@bogon javascript]# ./callback2
i:100
i:100
Hello world!
i:100
i:100
ret:102

這裏寫圖片描述

說明
1. 函數調用都是圍繞着stack的壓棧和出棧的操作,在函數callback之後,要清空使用記錄,恢復到調用前的狀態。
2. 例子中還借用了mujs開源包main.c中的代碼,實現了js腳本的print功能,支持終端輸出。我們知道javascript主要是web開發,所以在標準輸入輸出,文本操作方面支持的不好,mujs考慮到這點,在main.c舉例增加了相關函數,使得javascript可以調用這些功能函數,但這樣一來javascript就不是標準的版本,必須在mujs解釋器下執行,如果更換其他解釋器,那麼這些函數需要調整。

示例6
Callbacks from C to JS

const char *handle = NULL; /* handle to stowed away js function */

static void set_callback(js_State *J)
{
    if (handle)
        js_unref(J, handle); /* delete old function */
    js_copy(J, 1);
    handle = js_ref(J); /* stow the js function in the registry */
}

static void call_callback(js_State *J, int arg1, int arg2)
{
    js_getregistry(J, handle); /* retrieve the js function from the registry */
    js_pushnull(J);
    js_pushnumber(J, arg1);
    js_pushnumber(J, arg2);
    js_pcall(J, 2);
    js_pop(J, 1);
}

這個例子沒有看懂,後續解決。

示例7
Complete userdata example
源碼比較長

#include <stdio.h>
#include <mujs.h>

#define TAG "File"

static void new_File(js_State *J)
{
    FILE *file;

    if (js_isundefined(J, 1)) {
        file = stdin;
    } else {
        const char *filename = js_tostring(J, 1);
        file = fopen(filename, "r");
        if (!file)
            js_error(J, "cannot open file: '%s'", filename);
    }

    js_currentfunction(J);
    js_getproperty(J, -1, "prototype");
    js_newuserdata(J, TAG, file);
}

static void File_prototype_readByte(js_State *J)
{
    FILE *file = js_touserdata(J, 0, TAG);
    js_pushnumber(J, getc(file));
}

static void File_prototype_readLine(js_State *J)
{
    char line[256], *s;
    FILE *file = js_touserdata(J, 0, TAG);
    s = fgets(line, sizeof line, file);
    if (s)
        js_pushstring(J, line);
    else
        js_pushnull(J);
}

static void File_prototype_close(js_State *J)
{
    FILE *file = js_touserdata(J, 0, TAG);
    fclose(file);
    js_pushundefined(J);
}

void initfile(js_State *J)
{
    js_getglobal(J, "Object");
    js_getproperty(J, -1, "prototype"); // File.prototype.[[Prototype]] = Object.prototype
    js_newuserdata(J, TAG, stdin);      // File.prototype.[[Userdata]] = stdin
    {
        js_newcfunction(J, File_prototype_readByte, "File.prototype.readByte", 0);
        js_defproperty(J, -2, "readByte", JS_DONTENUM);

        js_newcfunction(J, File_prototype_readLine, "File.prototype.readLine", 0);
        js_defproperty(J, -2, "readLine", JS_DONTENUM);

        js_newcfunction(J, File_prototype_close, "File.prototype.close", 0);
        js_defproperty(J, -2, "close", JS_DONTENUM);
    }
    js_newcconstructor(J, new_File, new_File, "File", 1);
    js_defglobal(J, "File", JS_DONTENUM);
}

待續….
這個例子,也沒看懂~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章