前提:已經在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);
}
待續….
這個例子,也沒看懂~