Skip to content

yet another emacs lisp compiler / interpreter written in javascript / nodejs

License

Notifications You must be signed in to change notification settings

jawatech/elisp4js

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

19 Commits
 
 
 
 
 
 
 
 
 
 

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

No packages published