-
Notifications
You must be signed in to change notification settings - Fork 0
yet another emacs lisp compiler / interpreter written in javascript / nodejs
License
jawatech/elisp4js
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
#+AUTHOR: Sigmund Tzeng * synopsis #+BEGIN_SRC sh node js/temacs-24.5.js js/loadup_1.el # first list; working!! node js/temacs-24.5.js js/loadup.el # benchmark; shall work finally #+END_SRC 以上指令將載入 emacs 預載的 loadup.el 並進行 sanity check ,驗證 temacs 的完成度。 接下來的工作,將分為兩個方向進行。 從 mal 方找尋 emacs 方的對應元素,以及從 emacs 的執行期行為找尋對應的程式以決定實作的優先順序。 * 從 emacs 的執行期行為找尋對應的程式碼 程式執行開頭頭有顯示一個 splash 畫面,我們將從此畫面的元素著手,得到初始載入順序相關資訊。 startup.el 是第一個被載入執行的 lisp 檔案。 經分析後發現此一行為由 Makefile.in 所指定。 ** building *** bare emacs: Emacs Lisp interpreter and I/O routines [[bookmark:temacs][Bookmark: temacs]] 在建置 emacs 的過程中,第一步為產生此一空殼 temacs *** dumped (pure) Emacs: set up the normal Emacs editing environment [[bookmark:dumped%20Emacs][Bookmark: dumped Emacs]] c.f. 末傾印,只進行 "拔靴法(Bootstrap)" [[bookmark:bootstrap-emacs][Bookmark: bootstrap-emacs]] 在建置 emacs 的過程中,第二步為產生此一 "dumped Emacs" 的預載映象檔;其 --load 參數指定了 loadup.el 這個預載檔案;其中 loadup.el 會再指定預載檔案 **** load "startup" [[bookmark:load%20"startup"][Bookmark: load "startup"]] 在此檔末端並指定執行 startup.el 中的 top-level 函數,也就是 normal-top-level。 startup.el 有特定的字串,似乎與視窗介面的建立有關。 其它載入檔案如下: (load "emacs-lisp/byte-run") (load "emacs-lisp/backquote") (load "subr") (load "version") (load "widget") (load "custom") (load "emacs-lisp/map-ynp") (load "international/mule") (load "international/mule-conf") (load "env") (load "format") (load "bindings") (load "cus-start") (load "window") ; Needed here for `replace-buffer-in-windows'. (load "files") (load "emacs-lisp/macroexp") (load "emacs-lisp/pcase")) (load "emacs-lisp/macroexp")) (load "cus-face") (load "faces") ; after here, `defface' may be used. (load "button") (load "startup") (load "loaddefs.el") (file-error (load "ldefs-boot.el"))) (load "emacs-lisp/nadvice") (load "minibuffer") (load "abbrev") ;lisp-mode.el and simple.el use define-abbrev-table. (load "simple") (load "help") (load "jka-cmpr-hook") (load "epa-hook") (load "international/mule-cmds") (load "case-table") (load "international/charprop.el" t) (load "international/characters") (load "composite") (load "language/chinese") (load "language/cyrillic") (load "language/indian") (load "language/sinhala") (load "language/english") (load "language/ethiopic") (load "language/european") (load "language/czech") (load "language/slovak") (load "language/romanian") (load "language/greek") (load "language/hebrew") (load "language/japanese") (load "language/korean") (load "language/lao") (load "language/tai-viet") (load "language/thai") (load "language/tibetan") (load "language/vietnamese") (load "language/misc-lang") (load "language/utf-8-lang") (load "language/georgian") (load "language/khmer") (load "language/burmese") (load "language/cham") (load "indent") (load "frame") (load "term/tty-colors") (load "font-core") (load "facemenu") (load "emacs-lisp/syntax") (load "font-lock") (load "jit-lock") (load "mouse") (load "scroll-bar")) (load "select"))) (load "emacs-lisp/timer") (load "isearch") (load "rfn-eshadow") (load "menu-bar") (load "emacs-lisp/lisp") (load "textmodes/page") (load "register") (load "textmodes/paragraphs") (load "progmodes/prog-mode") (load "emacs-lisp/lisp-mode") (load "textmodes/text-mode") (load "textmodes/fill") (load "newcomment") (load "replace") (load "emacs-lisp/tabulated-list") (load "buff-menu") (load "fringe") (load "emacs-lisp/regexp-opt") (load "image") (load "international/fontset") (load "dnd") (load "tool-bar"))) (load "dynamic-setting")) (load "x-dnd") (load "term/common-win") (load "term/x-win"))) (load "term/common-win") (load "w32-vars") (load "term/w32-win") (load "disp-table") (load "w32-common-fns") (load "w32-fns") (load "ls-lisp") (load "dos-w32")))) (load "dos-w32") (load "dos-fns") (load "dos-vars") (load "term/internal") (load "term/pc-win") (load "ls-lisp") (load "disp-table"))) ; needed to setup ibm-pc char set, see internal.el (load "term/common-win") (load "term/ns-win"))) (load "mwheel")) (load "emacs-lisp/float-sup") (load "vc/vc-hooks") (load "vc/ediff-hook") (load "uniquify") (load "electric") (if (not (eq system-type 'ms-dos)) (load "tooltip")) (load "leim/leim-list.el" t) (load "site-load" t) (load "site-init" t) *** daily emacs, finally [[bookmark:Unless%20next%20switch%20is%20-nl,%20load%20"loadup.el"%20first%20thing.][Bookmark: Unless next switch is -nl, load "loadup.el" first thing.]] Unless next switch is -nl, load "loadup.el" first thing. ** running *** 初始化@main()@emacs.c: initXXX & syms_of_XXX (defsubr, DEFVAR & DEFSYM) **** init_obarray (object array = Vobarray) [[bookmark:init_obarray%20(void)][Bookmark: init_obarray (void)]] obarray 就是 object array 的意思,object 的型態都是 Lisp_Object initial_obarray 是最初始的物件陣列,包含了最開始的三個常量: Qt, Qnil, Qunbound 最陽春的符號就是 Qt ,其定義代表最低要求 intern_c_string & SET_SYMBOL_VAL ***** Qt: "t" #+BEGIN_SRC C Qt = intern_c_string ("t"); SET_SYMBOL_VAL (XSYMBOL (Qt), Qt); XSYMBOL (Qt)->constant = 1; #+END_SRC ***** Qnil: "nil" #+BEGIN_SRC C /* Set temporary dummy values to Qnil and Vpurify_flag to satisfy the NILP (Vpurify_flag) check in intern_c_string. */ Qnil = make_number (-1); Vpurify_flag = make_number (1); Qnil = intern_c_string ("nil"); SET_SYMBOL_VAL (XSYMBOL (Qnil), Qnil); XSYMBOL (Qnil)->constant = 1; XSYMBOL (Qnil)->declared_special = 1; set_symbol_plist (Qnil, Qnil); set_symbol_function (Qnil, Qnil); XSYMBOL (Qnil)->declared_special = 1; #+END_SRC ***** Qunbound: "unbound" #+BEGIN_SRC C Qunbound = Fmake_symbol (build_pure_c_string ("unbound")); /* Fmake_symbol inits fields of new symbols with Qunbound and Qnil, so those two need to be fixed manually. */ SET_SYMBOL_VAL (XSYMBOL (Qunbound), Qunbound); set_symbol_function (Qunbound, Qnil); set_symbol_plist (Qunbound, Qnil); #+END_SRC **** init_XXX: 以 Vcommand_line_args 為例 [[bookmark:init_cmdargs%20(argc,%20argv,%20skip_args,%20original_pwd);][Bookmark: init_cmdargs (argc, argv, skip_args, original_pwd);]] [[bookmark:init_cmdargs%20(int%20argc,%20char%20**argv,%20int%20skip_args,%20char%20*original_pwd)][Bookmark: init_cmdargs (int argc, char **argv, int skip_args, char *original_pwd)]] [[bookmark:Fcons%20(build_unibyte_string%20(argv%5Bi%5D),%20Vcommand_line_args);][Bookmark: Fcons (build_unibyte_string (argv{i}), Vcommand_line_args);]] #+BEGIN_SRC C Vcommand_line_args = Qnil; for (i = argc - 1; i >= 0; i--) { if (i == 0 || i > skip_args) /* For the moment, we keep arguments as is in unibyte strings. They are decoded in the function command-line after we know locale-coding-system. */ Vcommand_line_args = Fcons (build_unibyte_string (argv[i]), Vcommand_line_args); } #+END_SRC **** DEFVAR in syms_of_emacs () [[bookmark:syms_of_emacs%20();][Bookmark: syms_of_emacs ();]] [[bookmark:void%20syms_of_emacs%20(void)][Bookmark: void syms_of_emacs (void)]] [[bookmark:DEFVAR_LISP%20("command-line-args",%20Vcommand_line_args,][Bookmark: DEFVAR_LISP ("command-line-args", Vcommand_line_args,]] #+BEGIN_SRC C DEFVAR_LISP ("command-line-args", Vcommand_line_args, doc: /* Args passed by shell to Emacs, as a list of strings. Many arguments are deleted from the list as they are processed. */); #+END_SRC DEFVAR 的作用似乎是直接在 global 的 symbol table 中寫入一筆資料,作用比較單純 **** DEFVAR ***** DEFVAR_LISP @ lread.c [[bookmark:DEFVAR_LISP%20("command-line-args",%20Vcommand_line_args,][Bookmark: DEFVAR_LISP ("command-line-args", Vcommand_line_args,]] [[bookmark:#define%20DEFVAR_LISP(lname,%20vname,%20doc)][Bookmark: #define DEFVAR_LISP(lname, vname, doc)]] #+BEGIN_SRC C /* Macros we use to define forwarded Lisp variables. These are used in the syms_of_FILENAME functions. An ordinary (not in buffer_defaults, per-buffer, or per-keyboard) lisp variable is actually a field in `struct emacs_globals'. The field's name begins with "f_", which is a convention enforced by these macros. Each such global has a corresponding #define in globals.h; the plain name should be used in the code. E.g., the global "cons_cells_consed" is declared as "int f_cons_cells_consed" in globals.h, but there is a define: #define cons_cells_consed globals.f_cons_cells_consed All C code uses the `cons_cells_consed' name. This is all done this way to support indirection for multi-threaded Emacs. */ #define DEFVAR_LISP(lname, vname, doc) \ do { \ static struct Lisp_Objfwd o_fwd; \ defvar_lisp (&o_fwd, lname, &globals.f_ ## vname); \ } while (false) #+END_SRC 在執行最開始的 make 的過程中,所有 DEFVAR 的 vname 會被收集到 globals.h 當中, 成為 emacs_globasl 的一個欄位, 名稱會加上前綴 f_ , 然後又被巨集宣告為與 lname 同名的變數。 ***** defvar_lisp @ lread.c 由以下程式碼可看出,這些變數會存到 staticvec 這個陣列中, 這個部分與 gargabe collection 有關, 在此不詳述。 與 defsubr 的作法類似, 此變數的名稱會被註冊為一符號,並以 obarray 存放,方便以變數名查詢符號。 符號再經 SET_SYMBOL_FWD 存放此一變數的指標。 [[bookmark:defvar_lisp%20(struct%20Lisp_Objfwd%20*o_fwd,][Bookmark: defvar_lisp (struct Lisp_Objfwd *o_fwd,]] #+BEGIN_SRC C /* Similar but define a variable whose value is the Lisp Object stored at address. Two versions: with and without gc-marking of the C variable. The nopro version is used when that variable will be gc-marked for some other reason, since marking the same slot twice can cause trouble with strings. */ void defvar_lisp_nopro (struct Lisp_Objfwd *o_fwd, const char *namestring, Lisp_Object *address) { Lisp_Object sym; sym = intern_c_string (namestring); o_fwd->type = Lisp_Fwd_Obj; o_fwd->objvar = address; XSYMBOL (sym)->declared_special = 1; XSYMBOL (sym)->redirect = SYMBOL_FORWARDED; SET_SYMBOL_FWD (XSYMBOL (sym), (union Lisp_Fwd *)o_fwd); } void defvar_lisp (struct Lisp_Objfwd *o_fwd, const char *namestring, Lisp_Object *address) { defvar_lisp_nopro (o_fwd, namestring, address); staticpro (address); } #+END_SRC ***** staticpro: garbage collection [[bookmark:staticpro%20(Lisp_Object%20*varaddress)][Bookmark: staticpro (Lisp_Object *varaddress)]] #+BEGIN_SRC C /*********************************************************************** Protection from GC ***********************************************************************/ /* Put an entry in staticvec, pointing at the variable with address VARADDRESS. */ void staticpro (Lisp_Object *varaddress) { if (staticidx >= NSTATICS) fatal ("NSTATICS too small; try increasing and recompiling Emacs."); staticvec[staticidx++] = varaddress; } #+END_SRC ***** SET_SYMBOL_FWD [[bookmark:SET_SYMBOL_FWD%20(struct%20Lisp_Symbol%20*sym,%20union%20Lisp_Fwd%20*v)][Bookmark: SET_SYMBOL_FWD (struct Lisp_Symbol *sym, union Lisp_Fwd *v)]] #+BEGIN_SRC C INLINE void SET_SYMBOL_FWD (struct Lisp_Symbol *sym, union Lisp_Fwd *v) { eassert (sym->redirect == SYMBOL_FORWARDED); sym->val.fwd = v; } #+END_SRC ***** SYMBOL_FORWARDED 影響到的函數 ****** specbind 特重要!!! lisp 中的這些函數也需要它: let, 以及因為實質上使用了 let 而以 C 改寫的函數 ****** 其它 ******* buffer_local_value_1 #+BEGIN_SRC C /* Return the value of VARIABLE in BUFFER. If VARIABLE does not have a buffer-local binding in BUFFER, the value is the default binding of the variable. */ #+END_SRC ******* store_frame_param ******* mark_object ***** staticvec: 與 GC 有關,不重要 **** 兼論 expand-file-name [[bookmark:EMACSLOADPATH=$(CURDIR)/../lisp][Bookmark: EMACSLOADPATH=$(CURDIR)/../lisp]] #+BEGIN_SRC C DEFUN ("expand-file-name", Fexpand_file_name, Sexpand_file_name, 1, 2, 0, #+END_SRC **** 小結: 復刻 emacs 初始化行為 *** C / Lisp 兩用函數的宣告/定義及登錄: DEFUN (& defsubr) @syms_of_XXX() 以 if 這個函數來作為範例,它的宣告/定義是 DEFUN ("if", Fif, Sif, 2, UNEVALLED, 0 … 前面三個參數是最重要的, "if" 是它在 lisp 程式中的名稱, Fif 是在 C 程式碼中的名稱, 至於 Sif 可以看做是紀錄相關資訊的結構 (struct) 的名稱。 [[bookmark:#define%20DEFUN(lname,%20fnname,%20sname,%20minargs,%20maxargs,%20intspec,%20doc)][Bookmark: #define DEFUN(lname, fnname, sname, minargs, maxargs, intspec, doc)]] #+BEGIN_SRC C # define DEFUN_FUNCTION_INIT(fnname, maxargs) .a ## maxargs = fnname #define DEFUN(lname, fnname, sname, minargs, maxargs, intspec, doc) \ Lisp_Object fnname DEFUN_ARGS_ ## maxargs ; \ static struct Lisp_Subr alignas (GCALIGNMENT) sname = \ { { PVEC_SUBR << PSEUDOVECTOR_AREA_BITS }, \ { DEFUN_FUNCTION_INIT (fnname, maxargs) }, \ minargs, maxargs, lname, intspec, 0}; \ Lisp_Object fnname #+END_SRC sname 此一結構的 an 成員 (0<=n<=9) 即為 FXXX 形式的函數(指標?) 至於定義後要如何被 lisp 的程式碼找到並呼叫呢? 這就需要以 defsubr 來登錄 Sif 結構了。 **** defsubr (&SXXX); 將名稱加到 Vobarray 中、登錄 SXXX 結構 [[bookmark:defsubr][Bookmark: defsubr]] ***** intern_c_string 此函數首先呼叫了 intern_c_string 以登錄函數名稱為符號,並加到 Vobarray 中 [[bookmark:intern_c_string%20(const%20char%20*str)][Bookmark: intern_c_string (const char *str)]] [[bookmark:intern_c_string_1%20(const%20char%20*str,%20ptrdiff_t%20len)][Bookmark: intern_c_string_1 (const char *str, ptrdiff_t len)]] #+BEGIN_SRC C /* Intern the C string STR: return a symbol with that name, interned in the current Vobarray. */ typedef EMACS_INT Lisp_Object; #+END_SRC ***** XSETSUBR (tem, sname) #+BEGIN_SRC C #define XSETSUBR(a, b) (XSETPSEUDOVECTOR (a, b, PVEC_SUBR)) #+END_SRC ***** set_symbol_function 然後呼叫 set_symbol_function 把符號和函數連結起來 [[bookmark:set_symbol_function%20(Lisp_Object%20sym,%20Lisp_Object%20function)][Bookmark: set_symbol_function (Lisp_Object sym, Lisp_Object function)]] *** 為何沒有DEFCONST?DEFSYM的作用? https://www.gnu.org/software/emacs/manual/html_node/elisp/Creating-Symbols.html#Creating-Symbols **** func, subst, alias, macro **** var, const, custom *** REPL 在 main 函數中,呼叫了 Frecursive-edit ,這是事件處理迴圈的入口 [[bookmark:Frecursive_edit%20();][Bookmark: Frecursive_edit ();]] [[bookmark:DEFUN%20("recursive-edit",%20Frecursive_edit,%20Srecursive_edit,%200,%200,%20"",][DEFUN ("recursive-edit", Frecursive_edit, Srecursive_edit, 0, 0, "",]] [[bookmark:command_loop%20(void)][Bookmark: command_loop (void)]] **** load "loadup.el" first thing 在進入 Frecursive-edit 之前,已經塞了指令 load loadup.el 給最上層的環境: [[bookmark:Vtop_level%20=%20list2%20(intern_c_string%20("load"),][Bookmark: Vtop_level = list2 (intern_c_string ("load"),]] 因此先討論整個 eval 的入口 [[bookmark:internal_catch%20(Qtop_level,%20top_level_1,%20Qnil);][Bookmark: internal_catch (Qtop_level, top_level_1, Qnil);]] [[bookmark:top_level_1%20(Lisp_Object%20ignore)][Bookmark: top_level_1 (Lisp_Object ignore)]] [[bookmark:internal_condition_case%20(top_level_2,%20Qerror,%20cmd_error);][Bookmark: internal_condition_case (top_level_2, Qerror, cmd_error);]] [[bookmark:top_level_2%20(void)][Bookmark: top_level_2 (void)]] [[bookmark:return%20Feval%20(Vtop_level,%20Qnil);][Bookmark: return Feval (Vtop_level, Qnil);]] [[bookmark:DEFUN%20("eval",%20Feval,%20Seval,%201,%202,%200,][Bookmark: DEFUN ("eval", Feval, Seval, 1, 2, 0,]] ***** eval_sub [[bookmark:return%20unbind_to%20(count,%20eval_sub%20(form));][Bookmark: return unbind_to (count, eval_sub (form));]] [[bookmark:eval_sub%20(Lisp_Object%20form)][Bookmark: eval_sub (Lisp_Object form)]] 此處呼叫了 list 的函數以進行求值 #+BEGIN_SRC C switch (i) { case 0: val = (XSUBR (fun)->function.a0 ()); break; case 1: val = (XSUBR (fun)->function.a1 (argvals[0])); break; case 2: val = (XSUBR (fun)->function.a2 (argvals[0], argvals[1])); break; case 3: val = (XSUBR (fun)->function.a3 (argvals[0], argvals[1], argvals[2])); break; case 4: val = (XSUBR (fun)->function.a4 (argvals[0], argvals[1], argvals[2], argvals[3])); break; case 5: val = (XSUBR (fun)->function.a5 (argvals[0], argvals[1], argvals[2], argvals[3], argvals[4])); break; case 6: val = (XSUBR (fun)->function.a6 (argvals[0], argvals[1], argvals[2], argvals[3], argvals[4], argvals[5])); break; case 7: val = (XSUBR (fun)->function.a7 (argvals[0], argvals[1], argvals[2], argvals[3], argvals[4], argvals[5], argvals[6])); break; case 8: val = (XSUBR (fun)->function.a8 (argvals[0], argvals[1], argvals[2], argvals[3], argvals[4], argvals[5], argvals[6], argvals[7])); break; default: /* Someone has created a subr that takes more arguments than is supported by this code. We need to either rewrite the subr to use a different argument protocol, or add more cases to this switch. */ emacs_abort (); } #+END_SRC ***** Fload(): Execute a file of Lisp code named FILE. Fload() 雖然不是初級的語法元素,但是了解它的流程對測試有相當的幫助,因此以下說明它的執行流程 [[bookmark:DEFUN%20("load",%20Fload,%20Sload,%201,%205,%200,][Bookmark: DEFUN ("load", Fload, Sload, 1, 5, 0,]] ****** readevalloop [[bookmark:readevalloop%20(Qget_file_char,%20stream,%20hist_file_name,][Bookmark: readevalloop (Qget_file_char, stream, hist_file_name,]] [[bookmark:readevalloop%20(Lisp_Object%20readcharfun,][Bookmark: readevalloop (Lisp_Object readcharfun,]] 即然名為 ReadEval(Print)Loop = RE(P)L ,應該就會有個迴圈,進行讀取->求值。 參數 stream 為所讀取檔案的 handle , 若為 nil 時表示由 stdin 讀取。 迴圈中以 READCHAR 來預讀一個字元,以判斷接下來的語法元素,並調用對應的函式 除了 read_list 以外,其它函數都還滿 trivial 的,因此以下集中討論 read_list ******* READCHAR [[bookmark:c%20=%20READCHAR;][Bookmark: c = READCHAR;]] #define READCHAR readchar (readcharfun, NULL) [[bookmark:static%20int%20readchar%20(Lisp_Object%20readcharfun,%20bool%20*multibyte)][Bookmark: static int readchar (Lisp_Object readcharfun, bool *multibyte)]] /* When READCHARFUN is Qget_file_char, Qget_emacs_mule_file_char, Qlambda, or a cons, we use this to keep an unread character because a file stream can't handle multibyte-char unreading. The value -1 means that there's no unread character. */ ******* read_list [[bookmark:val%20=%20read_list%20(0,%20readcharfun);][Bookmark: val = read_list (0, readcharfun);]] [[bookmark:static%20Lisp_Object%20read_list%20(bool%20flag,%20Lisp_Object%20readcharfun)][Bookmark: static Lisp_Object read_list (bool flag, Lisp_Object readcharfun)]] ******** read1 [[bookmark:elt%20=%20read1%20(readcharfun,%20&ch,%20first_in_list);][Bookmark: elt = read1 (readcharfun, &ch, first_in_list);]] [[bookmark:static%20Lisp_Object%20read1%20(Lisp_Object%20readcharfun,%20int%20*pch,%20bool%20first_in_list)][Bookmark: static Lisp_Object read1 (Lisp_Object readcharfun, int *pch, bool first_in_list)]] ******** Fintern [[bookmark:result%20=%20(uninterned_symbol%20?%20Fmake_symbol%20(name)%20:%20Fintern%20(name,%20Qnil));][Bookmark: result = (uninterned_symbol ? Fmake_symbol (name) : Fintern (name, Qnil));]] [[bookmark:DEFUN%20("intern",%20Fintern,%20Sintern,%201,%202,%200,][Bookmark: DEFUN ("intern", Fintern, Sintern, 1, 2, 0,]] /* Return the canonical symbol whose name is STRING. If there is none, one is created by this function and returned. A second optional argument specifies the obarray to use; it defaults to the value of `Vobarray'. */ 此一函數做的事基本上就跟 intern_c_string_1/intern_c_string 一樣,查詢此一字串是否已定義為符號,並傳回之 ******* DEFUN ("macroexpand", Fmacroexpand, Smacroexpand, 1, 2, 0, [[bookmark:DEFUN%20("macroexpand",%20Fmacroexpand,%20Smacroexpand,%201,%202,%200,][Bookmark: DEFUN ("macroexpand", Fmacroexpand, Smacroexpand, 1, 2, 0,]] 這個部分我滿懷疑是不是寫錯了,似乎永遠不會執行到?? ******* eval_sub [[bookmark:val%20=%20eval_sub%20(val);][Bookmark: val = eval_sub (val);]] 此後由於 read_list 已經進行了實質上的 scanning ,而 emacs lisp 實質上就是 AST ,所以不需要 parsing 因此可以直接進行求值。 ***** emacs lisp key syntax elements in eval.c [[bookmark:syms_of_eval%20(void)][Bookmark: syms_of_eval (void)]] ****** conditionals defsubr (&Sor); defsubr (&Sand); defsubr (&Sif); defsubr (&Scond); ****** blocks defsubr (&Sprogn); defsubr (&Sprog1); defsubr (&Sprog2); ****** var defsubr (&Ssetq); defsubr (&Squote); defsubr (&Sfunction); defsubr (&Sdefault_toplevel_value); defsubr (&Sset_default_toplevel_value); defsubr (&Sdefvar); defsubr (&Sdefvaralias); defsubr (&Sdefconst); defsubr (&Smake_var_non_special); defsubr (&Slet); defsubr (&SletX); ****** macro defsubr (&Swhile); defsubr (&Smacroexpand); ****** exception defsubr (&Scatch); defsubr (&Sthrow); defsubr (&Sunwind_protect); ****** flow defsubr (&Scondition_case); defsubr (&Ssignal); defsubr (&Scommandp); defsubr (&Sautoload); defsubr (&Sautoload_do_load); defsubr (&Seval); defsubr (&Sapply); defsubr (&Sfuncall); ****** misc defsubr (&Srun_hooks); defsubr (&Srun_hook_with_args); defsubr (&Srun_hook_with_args_until_success); defsubr (&Srun_hook_with_args_until_failure); defsubr (&Srun_hook_wrapped); defsubr (&Sfetch_bytecode); defsubr (&Sbacktrace_debug); defsubr (&Sbacktrace); defsubr (&Sbacktrace_frame); defsubr (&Sbacktrace_eval); defsubr (&Sbacktrace__locals); defsubr (&Sspecial_variable_p); defsubr (&Sfunctionp); **** UI 以下部分屬於使用者互動,在 repl 的層級暫不討論 [[bookmark:command_loop_2%20(Lisp_Object%20ignore)][Bookmark: command_loop_2 (Lisp_Object ignore)]] [[bookmark:internal_condition_case%20(command_loop_1,%20Qerror,%20cmd_error);][Bookmark: internal_condition_case (command_loop_1, Qerror, cmd_error);]] * 從 mal 方找尋 emacs 方的對應元素 ** repl_env => Vobarray mal 在初始化的過程中,把 core.ns 的內容複製到了 repl_env 當中,因此 repl_env 才是真正意義上存放所有 symboe 的物件,core.ns 則類似於 initial_obarray ** equal 嘗試載入 loadup.el 的過程中,第一個遇到的問題就是找不到這個算子。這個問題倒是好解決,因為 mal 本身已經定義了 "=" 算子;不過這倒是提供我們再一次審視 scanning / parsing 的流程 *** emacs implementation fns.c 中有如下定義: [[bookmark:DEFUN%20("equal",%20Fequal,%20Sequal,%202,%202,%200,][Bookmark: DEFUN ("equal", Fequal, Sequal, 2, 2, 0,]] [[bookmark:%20%20defsubr%20(&Sequal);][Bookmark: defsubr (&Sequal);]] *** mal implementation core.js 中有如下定義: [[bookmark:var%20ns%20=%20{'type':%20types._obj_type,%20'=':%20types._equal_Q,][Bookmark: var ns = {'type': types._obj_type, '=': types._equal_Q,]] 因此符號的代表字串 '=' ,以及對應的函數 _equal_Q ,做為 hash 的一筆資料而存在於 ns 中 stepA_mal.js 中有如下定義: [[bookmark:for%20(var%20n%20in%20core.ns)%20{%20repl_env.set(types._symbol(n),%20core.ns%5Bn%5D);%20}][Bookmark: for (var n in core.ns) { repl_env.set(types._symbol(n), core.ns{n}); }]] ns 的資料再被逐筆的複製到 repl_env 中 *** 小結: DEFUN/defsubr -> core.ns ** let* / let Note that there are plenty of ways to make (GLOBAL) bindings: ‘defconst’, ‘defun’, ‘defvar’, 'let', ‘flet’, ‘labels’, ‘prog’, etc. 根據 [[https://www.emacswiki.org/emacs/DynamicBindingVsLexicalBinding][此文]] , emacs 只有 dynamic binding ,也就是說所有符號共用一個資料結構來存放 , 使得內層的函數/迴圈可以影響外層符號,不管這個符號背後是變數或函數。 根據這樣的邏輯,dynamic binding (ie emacs lisp) 在局部變數宣告時, 會先查找環境是否有同名的符號,若然則直接使用它,不在自己的 scope 再定義自己的符號; lexical binding (scheme?) 則無論如何都直接在自己的環境中, 為局部變數定義符號,並且優先查找。 let* 與 let 的不同之處在於,let 的 value 會在與 key 作 binding 前先求值, 因此當 local / global 有同名變數時, 會優先取 global 的值。 更精確的來說, let 會比 let* 多一個迴圈,先對所有的 value 進行 eval ; 後面的部分就都差不多 *** emacs: **** let* [[bookmark:DEFUN%20("let*",%20FletX,%20SletX,%201,%20UNEVALLED,%200,][Bookmark: DEFUN ("let*", FletX, SletX, 1, UNEVALLED, 0,]] **** let [[bookmark:DEFUN%20("let",%20Flet,%20Slet,%201,%20UNEVALLED,%200,][Bookmark: DEFUN ("let", Flet, Slet, 1, UNEVALLED, 0,]] *** mal: 最外層的環境為 repl_env ,也就是 DEFUN / DEFVAR / DEFCONST 的作用域 *** 小結: DEFVAR => init_XXXX 共 738 個, 其初始化常發生在 init_XXXX 函數中,必需忠實呈現在 js 端 ** load-path 的初始化 *** emacs [[bookmark:init_lread%20(void)][Bookmark: init_lread (void)]] Vpurify_flag 在 loadup.el 執行期間,其值為真; EMACSLOADPATH 則通常未指定,暫時忽略它 [[bookmark:load_path_default%20(void)][Bookmark: load_path_default (void)]] [[bookmark:EMACSLOADPATH=$(CURDIR)/../lisp][Bookmark: EMACSLOADPATH=$(CURDIR)/../lisp]] 因此目前暫時將此變數初始化為 ../lisp *** mal ** or 這部分 mal 有實作,而且是以 self-hosting 的方式,省下寫 js 的工夫 *** mal implementation [[bookmark:case%20"or":][Bookmark: case "or":]] *** emacs implementation [[bookmark:DEFUN%20("or",%20For,%20Sor,%200,%20UNEVALLED,%200,][Bookmark: DEFUN ("or", For, Sor, 0, UNEVALLED, 0,]] [[bookmark:defsubr%20(&Sor);][Bookmark: defsubr (&Sor);]] ** def! / set / setq ** if *** mal implementation [[bookmark:case%20"if":][Bookmark: case "if":]] *** emacs implementation [[bookmark:DEFUN%20("if",%20Fif,%20Sif,%202,%20UNEVALLED,%200,][Bookmark: DEFUN ("if", Fif, Sif, 2, UNEVALLED, 0,]] [[bookmark:defsubr%20(&Sif);][Bookmark: defsubr (&Sif);]] ** macroexpand macroexpand 是一個特殊的算子 mal 實作如下: [[bookmark:mal/macroexpand][mal/macroexpand]] #+BEGIN_SRC C case 'macroexpand': return macroexpand(a1, env); #+END_SRC [[bookmark:mal/function%20macroexpand][mal/function macroexpand]] #+BEGIN_SRC C function macroexpand(ast, env) { while (is_macro_call(ast, env)) { var mac = env.get(ast[0]); ast = mac.apply(mac, ast.slice(1)); } return ast; } #+END_SRC emacs 的實作如下: [[bookmark:eval/macroexpand][Bookmark: eval/macroexpand]] #+BEGIN_SRC C DEFUN ("macroexpand", Fmacroexpand, Smacroexpand, 1, 2, 0, doc: /* Return result of expanding macros at top level of FORM. If FORM is not a macro call, it is returned unchanged. Otherwise, the macro is expanded and the expansion is considered in place of FORM. When a non-macro-call results, it is returned. The second optional arg ENVIRONMENT specifies an environment of macro definitions to shadow the loaded ones for use in file byte-compilation. */) (Lisp_Object form, Lisp_Object environment) { /* With cleanups from Hallvard Furuseth. */ register Lisp_Object expander, sym, def, tem; while (1) { /* Come back here each time we expand a macro call, in case it expands into another macro call. */ if (!CONSP (form)) break; /* Set SYM, give DEF and TEM right values in case SYM is not a symbol. */ def = sym = XCAR (form); tem = Qnil; /* Trace symbols aliases to other symbols until we get a symbol that is not an alias. */ while (SYMBOLP (def)) { QUIT; sym = def; tem = Fassq (sym, environment); if (NILP (tem)) { def = XSYMBOL (sym)->function; if (!NILP (def)) continue; } break; } /* Right now TEM is the result from SYM in ENVIRONMENT, and if TEM is nil then DEF is SYM's function definition. */ if (NILP (tem)) { /* SYM is not mentioned in ENVIRONMENT. Look at its function definition. */ struct gcpro gcpro1; GCPRO1 (form); def = Fautoload_do_load (def, sym, Qmacro); UNGCPRO; if (!CONSP (def)) /* Not defined or definition not suitable. */ break; if (!EQ (XCAR (def), Qmacro)) break; else expander = XCDR (def); } else { expander = XCDR (tem); if (NILP (expander)) break; } { Lisp_Object newform = apply1 (expander, XCDR (form)); if (EQ (form, newform)) break; else form = newform; } } return form; } #+END_SRC ** try*/catch* catch 是一個特殊的算子, mal 中使用另一個名字: try*/catch* mal 實作如下: [[bookmark:try%20in%20mal][Bookmark: try in mal]] #+BEGIN_SRC C case "try*": try { return EVAL(a1, env); } catch (exc) { if (a2 && a2[0].value === "catch*") { if (exc instanceof Error) { exc = exc.message; } return EVAL(a2[2], new Env(env, [a2[1]], [exc])); } else { throw exc; } } #+END_SRC emacs 的實作如下: [[bookmark:eval.c/catch][Bookmark: eval.c/catch]] #+BEGIN_SRC C DEFUN ("catch", Fcatch, Scatch, 1, UNEVALLED, 0, doc: /* Eval BODY allowing nonlocal exits using `throw'. TAG is evalled to get the tag to use; it must not be nil. Then the BODY is executed. Within BODY, a call to `throw' with the same TAG exits BODY and this `catch'. If no throw happens, `catch' returns the value of the last BODY form. If a throw happens, it specifies the value to return from `catch'. usage: (catch TAG BODY...) */) (Lisp_Object args) { register Lisp_Object tag; struct gcpro gcpro1; GCPRO1 (args); tag = eval_sub (XCAR (args)); UNGCPRO; return internal_catch (tag, Fprogn, XCDR (args)); } #+END_SRC * $(RUN_TEMACS) --batch --load loadup bootstrap ** set-buffer [[bookmark:DEFUN%20("set-buffer",%20Fset_buffer,%20Sset_buffer,%201,%201,%200,][Bookmark: DEFUN ("set-buffer", Fset_buffer, Sset_buffer, 1, 1, 0,]] ** load [[bookmark:DEFUN%20("load",%20Fload,%20Sload,%201,%205,%200,][Bookmark: DEFUN ("load", Fload, Sload, 1, 5, 0,]] *** Execute a file of Lisp code named FILE. *** `load-history' Loading a file records its definitions, and its `provide' and `require' calls, in an element of `load-history' whose car is the file name loaded. See `load-history'. *** `load-in-progress' / `load-file-name' While the file is in the process of being loaded, the variable `load-in-progress' is non-nil and the variable `load-file-name' is bound to the file's name. Check if we're stuck in a recursive load cycle. Vloads_in_progress = Fcons (found, Vloads_in_progress); /* Get the name for load-history. */ hist_file_name = (! NILP (Vpurify_flag) ? concat2 (Ffile_name_directory (file), Ffile_name_nondirectory (found)) : found) ; version = -1; *** return Return t if the file exists and loads successfully. * 推估應預載模組 (load "emacs-lisp/byte-run") (load "emacs-lisp/backquote") (load "emacs-lisp/map-ynp") (load "emacs-lisp/macroexp") (load "emacs-lisp/pcase")) (load "emacs-lisp/macroexp")) (load "emacs-lisp/nadvice") (load "emacs-lisp/syntax") (load "emacs-lisp/timer") (load "emacs-lisp/lisp") (load "emacs-lisp/lisp-mode") (load "emacs-lisp/tabulated-list") (load "emacs-lisp/regexp-opt") (load "emacs-lisp/float-sup")
About
yet another emacs lisp compiler / interpreter written in javascript / nodejs
Resources
License
Stars
Watchers
Forks
Releases
No releases published
Packages 0
No packages published