Compare commits

...

4 Commits

21 changed files with 156 additions and 26 deletions

1
.envrc Normal file
View File

@@ -0,0 +1 @@
use flake

2
.gitignore vendored
View File

@@ -1,2 +1,4 @@
_build _build
*~ *~
.direnv
result

View File

@@ -1,6 +1,5 @@
(executable (executable
(name main) (name inter)
(public_name main) (public_name ollisp-inter)
(libraries str lisp unix)) (libraries str unix interpreter)
(include_subdirs unqualified) (package ollisp))

View File

@@ -1,11 +1,10 @@
open Lisp.Ast;; open Interpreter.Ast;;
open Printf;; open Printf;;
open Lisp;; open Interpreter;;
open Env;; open Env;;
open Eval;; open Eval;;
open Read;;
let () = InterpreterStdlib.init_default_env () let () = Stdlib.init_default_env ()
let rec repl env c = let rec repl env c =
let () = printf ">>> "; Out_channel.flush Out_channel.stdout; in let () = printf ">>> "; Out_channel.flush Out_channel.stdout; in
@@ -14,7 +13,7 @@ let rec repl env c =
| Some "exit" -> () | Some "exit" -> ()
| Some l -> | Some l ->
try try
let vals = (parse_str l) in let vals = (read_from_str l) in
(* dbg_print_all vals; *) (* dbg_print_all vals; *)
pretty_print_all (eval_all env vals); pretty_print_all (eval_all env vals);
Out_channel.flush Out_channel.stdout; Out_channel.flush Out_channel.stdout;
@@ -23,7 +22,7 @@ let rec repl env c =
| Invalid_argument s -> | Invalid_argument s ->
printf "%s\nResuming repl\n" s; printf "%s\nResuming repl\n" s;
repl env c repl env c
| Parser.Error -> | Parser.Parse.Error ->
printf "Expression '%s' couldn't be parsed, try again\n" l; printf "Expression '%s' couldn't be parsed, try again\n" l;
repl env c repl env c
;; ;;

View File

@@ -1,2 +1,5 @@
(lang dune 3.7) (lang dune 3.7)
(using menhir 2.1) (using menhir 2.1)
(package
(name ollisp))

27
flake.lock generated Normal file
View File

@@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1764950072,
"narHash": "sha256-BmPWzogsG2GsXZtlT+MTcAWeDK5hkbGRZTeZNW42fwA=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "f61125a668a320878494449750330ca58b78c557",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

27
flake.nix Normal file
View File

@@ -0,0 +1,27 @@
{
description = "a lisp interpreter/compiler in ocaml";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
};
outputs = {self, nixpkgs}:
let
pkgs = nixpkgs.legacyPackages.x86_64-linux;
in
{
packages.x86_64-linux.default = pkgs.ocamlPackages.buildDunePackage {
pname = "ollisp";
version = "0.0.1";
src = pkgs.lib.cleanSource ./.;
preBuildPhase = "ls -R";
nativeBuildInputs = with pkgs; [
ocamlPackages.menhir
];
};
devShells.x86_64-linux.default = pkgs.mkShell {
nativeBuildInputs = with pkgs.ocamlPackages; [
menhir merlin dune_3
];
};
};
}

View File

@@ -0,0 +1,25 @@
open Parser.Ast;;
(* This type represents an intermediate step between the AST and opcodes in our
compiler. We need this extra step to resolve addresses, e.g. how do you know
what exact address an if expression needs to jump to before you compile it?
you don't, you just keep a symbolic label there, resolve later.
*)
type intermediate_opcode =
| ISelect of string * string
| ILDF of string
| ILD of int (* an index into the constant table *)
| INil
| IRet
| IAdd
| IJoin
| ILabel of string (* does not emit any byte code *)
(* TODO: Complete *)
let (compile : lisp_ast -> intermediate_opcode list) = function
| LInt x -> [ILD x]
| _ -> [];;

3
lib/compiler/dune Normal file
View File

@@ -0,0 +1,3 @@
(library
(name compiler)
(libraries parser))

View File

@@ -1,7 +0,0 @@
(library
(name lisp))
(include_subdirs unqualified)
(menhir (modules parser))
(ocamllex lexer)

View File

@@ -1,3 +1,17 @@
(* This is different from the lisp_ast data returned by the parser!
We will first need to translate that into this in order to use it.
This representation includes things that can only occur during runtime,
like the various kinds of functions and macros.
Additionally, since this is an interpreter, macros tend to be a little
awkward in that they behave exactly like the macro gets expanded just
before the result gets executed. This is different from the compiled
behaviour where the macro is evaluated at compile time.
Though of course, with the dynamic nature of lisp, and its capability
to compile more code at runtime, there will naturally be complications.
*)
type lisp_val = type lisp_val =
| LInt of int | LInt of int
| LDouble of float | LDouble of float
@@ -16,8 +30,8 @@ type lisp_val =
| LFunction of string * environment * lisp_val * lisp_val | LFunction of string * environment * lisp_val * lisp_val
| LLambda of environment * lisp_val * lisp_val | LLambda of environment * lisp_val * lisp_val
(* a macro is exactly the same as a function, with the distinction (* a macro is exactly the same as a function, with the distinction
that it receives all of its arguments completely unevaluated that it receives all of its arguments completely unevaluated
in a compiled lisp this would probably make more of a difference *) *)
| LMacro of string * environment * lisp_val * lisp_val | LMacro of string * environment * lisp_val * lisp_val
| LUnnamedMacro of environment * lisp_val * lisp_val | LUnnamedMacro of environment * lisp_val * lisp_val
| LQuoted of lisp_val | LQuoted of lisp_val
@@ -113,3 +127,16 @@ let pretty_print_all vs =
let dbg_print_all vs = let dbg_print_all vs =
let pr v = Printf.printf "%s\n" (dbg_print_one v) in let pr v = Printf.printf "%s\n" (dbg_print_one v) in
List.iter pr vs List.iter pr vs
let rec convert_one = function
| Parser.Ast.LInt x -> LInt x
| Parser.Ast.LDouble x -> LDouble x
| Parser.Ast.LNil -> LNil
| Parser.Ast.LString s -> LString s
| Parser.Ast.LSymbol s -> LSymbol s
| Parser.Ast.LCons (a, b) -> LCons (convert_one a, convert_one b)
let read_from_str s =
List.map convert_one (Parser.parse_str s)

4
lib/interpreter/dune Normal file
View File

@@ -0,0 +1,4 @@
(library
(name interpreter)
(libraries parser)
(package ollisp))

View File

@@ -200,5 +200,5 @@ let init_default_env () =
Idea: maybe put this in a file instead of putting Idea: maybe put this in a file instead of putting
literally the entire standard library in a constant string literally the entire standard library in a constant string
*) *)
ignore (Eval.eval_all default_env (Read.parse_str init_script)); ignore (Eval.eval_all default_env (read_from_str init_script));
() ()

10
lib/parser/ast.ml Normal file
View File

@@ -0,0 +1,10 @@
type lisp_ast =
| LInt of int
| LDouble of float
| LSymbol of string
| LString of string
| LNil
| LCons of lisp_ast * lisp_ast

7
lib/parser/dune Normal file
View File

@@ -0,0 +1,7 @@
(library
(name parser)
(modules parser lex parse ast)
(package ollisp))
(menhir (modules parse))
(ocamllex lex)

View File

@@ -1,6 +1,6 @@
{ {
open Lexing open Lexing
open Parser open Parse
exception SyntaxError of string exception SyntaxError of string
let strip_quotes s = String.sub s 1 (String.length s - 2);; let strip_quotes s = String.sub s 1 (String.length s - 2);;

View File

@@ -12,7 +12,7 @@
%token DOT %token DOT
%token EOF %token EOF
%start <Ast.lisp_val option> prog %start <lisp_ast option> prog
%% %%
prog: prog:
@@ -22,7 +22,7 @@ prog:
expr: expr:
| i = INT { LInt i } | i = INT { LInt i }
| d = DOUBLE {LDouble d} | d = DOUBLE { LDouble d}
| s = SYM { LSymbol s } | s = SYM { LSymbol s }
| s = STR { LString (String.uppercase_ascii s) } | s = STR { LString (String.uppercase_ascii s) }
| LPAREN; l = lisp_list_rest { l } | LPAREN; l = lisp_list_rest { l }

View File

@@ -1,4 +1,4 @@
let parse_one lb = Parser.prog (Lexer.read) lb let parse_one lb = Parse.prog (Lex.read) lb
let parse lb = let parse lb =
let rec helper () = let rec helper () =
@@ -11,3 +11,6 @@ let parse lb =
let parse_str s = let parse_str s =
parse (Lexing.from_string s) parse (Lexing.from_string s)
module Ast = Ast
module Parse = Parse

View File