SSブログ

OCaml で書く Tcl インタプリタ 3 [OCaml]

Haskell で 250 行で書かれた Tcl インタプリタ [1] というのがあった(行数の数えかたはあやしいが)ので思い出して自作 OCaml 製 Tcl インタプリタ [2] をまたいじってみた。今回は proc, global, upvar に対応してできることの幅がだいぶ広がった。結果として230行に。

open ExtList
open ExtString
module StringMap = Map.Make(String)
exception Unknown_command of string
exception Wrong_number_of_args

let string_of_char = String.make 1

module TclInterp = struct
  type frame_entry = Value of string | Global | Upvar of (string * int)
  type frame = frame_entry StringMap.t

  type interp = {
    result : string;
    g : string StringMap.t;
    frames : frame list;
    procs : (string * proc) list;
  }
  and proc = interp -> string list -> interp

  let initial_interp = {
    result = "";
    g = StringMap.empty;
    frames = [];
    procs = [];
  }

  let uplevel = List.drop
  let global name interp = StringMap.find name interp.g
  let local name frame = StringMap.find name frame

  let rec var_lookup name interp =
    match interp.frames with
      | [] -> global name interp
      | frame::_ as frames ->
        match (local name frame) with
          | Value v -> v
          | Global -> global name interp
          | Upvar (name, level) ->
            var_lookup name {interp with frames = uplevel level frames}

  let set_global name value interp =
    {interp with g = StringMap.add name value interp.g}
  let set_local name value frame = StringMap.add name (Value value) frame

  let rec var_update name value interp =
    match interp.frames with
      | [] -> set_global name value interp
      | frame::xs as frames ->
        if StringMap.mem name frame then
          match (local name frame) with
            | Value _ ->
              {interp with frames = (set_local name value frame)::xs}
            | Global -> set_global name value interp
            | Upvar (name, level) ->
              let intermediate= List.take level frames in
              let upframe = uplevel level frames in
              let interp' =
                var_update name value {interp with frames = upframe} in
              {interp' with frames = intermediate @ interp'.frames}
        else
          {interp with frames = (set_local name value frame)::xs}
end

module TclParserState = struct
  type state = {
      openBraces : int;
      argv : string list;
      buf : string;
      nest : bool;
  }
  let initial_state = {openBraces = 0; argv = []; buf=""; nest = false;}
  let argv_of_state state =
      let argv =
        if state.buf = "" then state.argv else state.buf::state.argv in
      List.rev argv
  let add_arg state =
      {state with argv = state.buf::state.argv; buf = ""}
  let ($^) state c =
      {state with buf = state.buf ^ string_of_char c}
  let ($^^) state s =
      {state with buf = state.buf ^ s}
  let flush state =
      {state with argv = []; buf = ""}
  let incrbrace state =
      {state with openBraces = state.openBraces + 1}
  let decrbrace state =
      {state with openBraces = state.openBraces - 1}
end

open TclInterp
open TclParserState

let invoke interp = function
  | name::args ->
    if List.mem_assoc name interp.procs then
      let proc = List.assoc name interp.procs in
      proc interp args
    else
      raise (Unknown_command name)
  | _ -> raise (Unknown_command "")

let backslash = parser
  | [< ''\n' >] -> ' '
  | [< ''n'  >] -> '\n'
  | [< ''t'  >] -> '\t'
  | [< 'c    >] -> c
  | [<       >] -> '\\'

let rec varsub interp name = parser
  | [< ''A'..'Z' | 'a'..'z' | '_' | '0'..'9' as c; strm>]
          -> varsub interp (name ^ string_of_char c) strm
  | [< >] -> if name = "" then "$" else var_lookup name interp

let rec eval interp state = parser
  | [< '' '|'\t'|'\r'|'\n'|';'; strm >] -> eval interp state strm
  | [< ''#'; strm >] -> comment interp state strm
  | [< strm >] -> command interp state strm
and comment interp state = parser
  | [< ''\n'; strm >] -> eval interp state strm
  | [< ''\\'; strm >] -> ignore (backslash strm); comment interp state strm
  | [< '_;    strm >] -> comment interp state strm
  | [<        strm >] -> interp
and command interp state = parser
  | [< '']' when state.nest; >] -> invoke interp (argv_of_state state)
  | [< '';'|'\n'; strm >] ->
      eval (invoke interp (argv_of_state state)) (flush state) strm
  | [< ''\\'; strm >] ->
      command interp (state $^ backslash strm) strm
  | [< '' '|'\t'|'\r' when state.buf = ""; strm >] ->
      command interp state strm
  | [< '' '|'\t'|'\r'; strm >] -> command interp (add_arg state) strm
  | [< ''{'  when state.buf = ""; strm >] ->
      brace interp {state with openBraces = 1} strm
  | [< ''\"' when state.buf = ""; strm >] ->
      quote interp state strm
  | [< ''$'; strm >] ->
      command interp (state $^^ varsub interp "" strm) strm
  | [< ''['; strm >] ->
      let interp = cmdsub interp strm state in
      command interp (state $^^ interp.result) strm
  | [< 'c; strm >] -> command interp (state $^ c) strm
  | [< >] ->
      let argv = argv_of_state state in
      if argv = [] then
          interp
      else
          invoke interp argv
and brace interp state = parser
  | [< ''\\' as c; strm >] -> brace interp (state $^ c) strm
  | [< ''{'  as c; strm >] -> brace interp (incrbrace (state $^c)) strm
  | [< ''}'  as c; strm >] ->
      if state.openBraces = 1 then
          command interp (decrbrace (add_arg state)) strm
      else
          brace interp (decrbrace (state $^ c)) strm
  | [< 'c; strm >] -> brace interp (state $^ c) strm
  | [<     strm >] -> command interp state strm
and quote interp state = parser
  | [< ''\\'; strm >] -> quote interp (state $^ backslash strm) strm
  | [< ''\"'; strm >] -> command interp (add_arg state) strm
  | [< ''$';  strm >] -> quote interp (state $^^ varsub interp "" strm) strm
  | [< ''[';  strm >] -> let interp = cmdsub interp strm state in
                         quote interp (state $^^ interp.result) strm
  | [< 'c;    strm >] -> quote interp (state $^ c) strm
  | [<        strm >] -> command interp state strm
and cmdsub interp strm state =
  eval interp {openBraces = 0; argv = []; buf= "" ; nest = true;} strm

(* quick hack !! *)
let parse_as_list = function
  | "" -> []
  | s -> String.nsplit s " "

let register_builtins interp =
  let puts interp = function
    | str::[] -> (Printf.printf "%s\n" str; interp)
    | _ -> raise Wrong_number_of_args
  in
  let set interp = function
    | [name] -> {interp with result = var_lookup name interp}
    | [name; value] -> {var_update name value interp with result = value}
    | _ -> raise Wrong_number_of_args
  in
  let proc interp = function
    | name::formals::body::[] ->
      let formals' = parse_as_list formals in
      let proc = fun interp args ->
       let pairs = List.combine formals' args in
        let locals = List.fold_left (fun e (k, v) -> StringMap.add k (Value v) e) StringMap.empty pairs
        in
        let interp' = eval
          {interp with frames = locals::interp.frames}
          initial_state
          (Stream.of_string body)
        in
        {interp' with frames = uplevel 1 interp'.frames}
      in
      {interp with procs = (name, proc)::interp.procs}
    | _ -> raise Wrong_number_of_args
  in
  let global interp names =
    match interp.frames with
      | [] -> interp
      | frame::xs ->
        let frame' = List.fold_left (fun e k -> StringMap.add k Global e) frame names in
        {interp with frames = frame'::xs}
  in
  let upvar interp = function
    | [] -> raise Wrong_number_of_args
    | level::pairs ->
      let level' = int_of_string level in
      let rec upvar' level frame = function
        | other::my::xs ->
          let frame' = StringMap.add my (Upvar (other, level)) frame in
          upvar' level frame' xs
        | _ -> frame
      in
      let frame::xs = interp.frames in
      let frame' = upvar' level' frame pairs in
      {interp with frames = frame'::xs}
  in
  let procs = ["puts", puts; "set", set; "proc", proc; "global", global; "upvar", upvar] in
  {interp with procs = procs}

let inch = open_in Sys.argv.(1);;
let instr = Stream.of_channel inch;;
let interp = register_builtins initial_interp;;
eval interp initial_state instr

[1] http://code.google.com/p/hiccup/
[2] http://blog.so-net.ne.jp/rainyday/2006-10-21


nice!(0)  コメント(0)  トラックバック(0) 
共通テーマ:パソコン・インターネット

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

真偽値の大小比較"Programming in Hask.. ブログトップ

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。