Skip to content

Add Pp_ast ppxlib to this repo #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
.merlin
node_modules
_esy
_release
*.byte
*.native
*.install
*.native
_build
.esy
.merlin
_opam
_release
dist
node_modules
ast_explorer_refmt.bc.js
1 change: 1 addition & 0 deletions .ocamlformat
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
profile=default
62 changes: 62 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
project_name = astexplorer-refmt

DUNE = opam exec -- dune
opam_file = $(project_name).opam

.PHONY: help
help:
@echo "";
@echo "List of available make commands";
@echo "";
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}';
@echo "";

.PHONY: build-dev
dev:
$(DUNE) build -w

.PHONY: build-prod
prod:
yarn install
$(DUNE) build
mkdir -p dist
chmod -R 777 dist
yarn build:prod

.PHONY: clean
clean: ## Clean artifacts
$(DUNE) clean

.PHONY: exec
exec: ## Run the project
$(DUNE) exec $(demo)

.PHONY: format
format:
DUNE_CONFIG__GLOBAL_LOCK=disabled $(DUNE) build @fmt --auto-promote

.PHONY: format-check
format-check:
$(DUNE) build @fmt

.PHONY: create-switch
create-switch: ## Create opam switch
opam switch create . 5.2.1 --deps-only --with-dev-setup -y

.PHONY: install
install:
yarn install
$(DUNE) build @install
opam install . --deps-only --with-test

.PHONY: init
init: create-switch install

.PHONY: demo
demo:
yarn install
$(DUNE) build
mkdir -p src/example/dist
chmod -R 777 src/example/dist
cp _build/default/src/ast_explorer_refmt.bc.js ./src/example/dist/ast_explorer_refmt.bc.js
yarn serve -p 3030 src/example
46 changes: 24 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -4,46 +4,48 @@ JavaScript wrapper for [refmt](https://github.com/facebook/reason/tree/master/sr

Not intended to be used as a library.

## Conversion from OCaml to JavaScript for astexplorer

- Record -> Object
- Tuple -> Array
- Variant -> Object with property `type` the name of the constructor, and then other properties with names that help understand the function of each variant argument

## Development

The project requires esy to be built, you can install it using [npm](https://nodejs.org/en/download/):

% npm install -g esy
The project requires `opam` and `yarn` to build, and it uses `make` to run the project.

Install the project dependencies using:
```
make init
```

% esy install
You can run the watch mode to automatically build the project when you make changes:

Build the project dependencies along with the project itself:
```
make dev
```

% esy build
To build the project as production mode:
(The output bundle will be stored in the `./dist` folder)

To test the compiled JS executable, open `index.html` in your browser.
```
make prod
```

To generate the production build (without sourcemaps, and minified) run:
To run an example, you can use the `demo` command:

% yarn run build:prod
```
make demo
```

The output bundle will be stored in the `./dist` folder.

### Running with astexplorer

- `yarn link` in the project root folder
- Clone [`astexplorer`](https://github.com/fkling/astexplorer/) locally.
- In `website` folder of `astexplorer`, call `yarn link astexplorer-refmt`.
- In `website` folder of `astexplorer`, call `yarn link <path-to-astexplorer-refmt>`.

### Running without astexplorer

Add some logging in `AstExplorerRefmt.re`, for example:
Edit the code variable in `src/example/example.js` to parse some code.
```js
let code = "let x = 1";

```reason
log("parse", parseReason("let f = a => \"1\"; /* Comment */ let a = 2;"));
let ast = window.parseReason(code);
window.document.querySelector("#app").innerHTML = ast;
```

Then open `src/index.html` to see the parsed JavaScript object in the console.
Then run `make demo` and open `http://localhost:3030/` in your browser.
40 changes: 40 additions & 0 deletions astexplorer-refmt.opam
100755 → 100644
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# This file is generated by dune, edit dune-project instead
opam-version: "2.0"
synopsis: "OCaml AST explorer for Reason and OCaml"
maintainer: [
"Javier Chávarri <javier.chavarri@gmail.com>"
"Pedro Lisboa <pedrobslisboa@gmail.com>"
]
authors: ["Javier Chávarri <javier.chavarri@gmail.com>"]
license: "ISC"
homepage: "https://github.com/jchavarri/astexplorer-refmt"
bug-reports: "https://github.com/jchavarri/astexplorer-refmt/issues"
depends: [
"dune" {>= "3.8"}
"ocaml" {>= "5.0.0"}
"reason" {>= "3.14.0"}
"js_of_ocaml-ppx" {>= "5.9.0"}
"js_of_ocaml" {>= "5.9.0"}
"ppxlib" {> "0.23.0"}
"ocamlformat" {= "0.26.2" & with-dev-setup}
"ocaml-lsp-server" {with-dev-setup}
"odoc" {with-doc}
]
build: [
["dune" "subst"] {dev}
[
"dune"
"build"
"-p"
name
"-j"
jobs
"@install"
"@runtest" {with-test}
"@doc" {with-doc}
]
]
dev-repo: "git+https://github.com/jchavarri/astexplorer-refmt.git"
pin-depends: [
["ppxlib.dev" "git+https://github.com/ocaml-ppx/ppxlib.git#a60d33c91ef1b87056d97482fda568a6f1c849fc"]
]
3 changes: 3 additions & 0 deletions astexplorer-refmt.opam.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pin-depends: [
["ppxlib.dev" "git+https://github.com/ocaml-ppx/ppxlib.git#a60d33c91ef1b87056d97482fda568a6f1c849fc"]
]
1 change: 1 addition & 0 deletions dune
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
(dirs src)
43 changes: 42 additions & 1 deletion dune-project
Original file line number Diff line number Diff line change
@@ -1 +1,42 @@
(lang dune 1.0)
(lang dune 3.8)

(using melange 0.1)

(generate_opam_files true)

(implicit_transitive_deps false)

(name astexplorer-refmt)

(source
(github jchavarri/astexplorer-refmt))

(license ISC)

(maintainers "Javier Chávarri <javier.chavarri@gmail.com>" "Pedro Lisboa <pedrobslisboa@gmail.com>")

(authors "Javier Chávarri <javier.chavarri@gmail.com>")

(package
(name astexplorer-refmt)
(synopsis "OCaml AST explorer for Reason and OCaml")
(allow_empty)
(depends
(ocaml
(>= 5.0.0))
(reason
(>= 3.14.0))
(js_of_ocaml-ppx
(>= 5.9.0))
; Library dependencies
(js_of_ocaml
(>= 5.9.0))
(ppxlib
(> 0.23.0))
; Dev dependencies
(ocamlformat
(and
(= 0.26.2)
:with-dev-setup))
; We use ocamlformat on the tests
(ocaml-lsp-server :with-dev-setup)))
36 changes: 0 additions & 36 deletions esy.json

This file was deleted.

7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -19,8 +19,11 @@
},
"homepage": "https://github.com/jchavarri/astexplorer-refmt",
"license": "MIT",
"main": "dist/AstExplorerRefmt.bc.js",
"main": "dist/ast_explorer_refmt.bc.js",
"scripts": {
"build:prod": "esy build:prod && esy cp '#{self.target_dir}/default/src/AstExplorerRefmt.bc.js' ./dist"
"build:prod": "mkdir -p dist && cp _build/default/src/ast_explorer_refmt.bc.js ./dist/ast_explorer_refmt.bc.js"
},
"devDependencies": {
"serve": "^14.2.4"
}
}
123 changes: 0 additions & 123 deletions src/AstExplorerRefmt.re

This file was deleted.

83 changes: 0 additions & 83 deletions src/ReAsttypes.re

This file was deleted.

11 changes: 0 additions & 11 deletions src/ReLexing.re

This file was deleted.

10 changes: 0 additions & 10 deletions src/ReLocation.re

This file was deleted.

26 changes: 0 additions & 26 deletions src/ReLongident.re

This file was deleted.

1,950 changes: 0 additions & 1,950 deletions src/ReParsetree.re

This file was deleted.

10 changes: 0 additions & 10 deletions src/Utils.re

This file was deleted.

123 changes: 123 additions & 0 deletions src/ast_explorer_refmt.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
(*
* Note: This file is currently broken, since Reason removed
* Reason_syntax_util.Error in favor of Reerror's `Printexc.to_string e`
*)

open Js_of_ocaml
module RE = Reason_toolchain.RE
module ML = Reason_toolchain.ML

let syntaxerr_error_to_string = function
| Syntaxerr.Unclosed _ -> "Unclosed"
| Expecting _ -> "Expecting"
| Not_expecting _ -> "Not expecting"
| Variable_in_scope _ -> "Variable in scope"
| Ill_formed_ast _ -> "Ill formed ast"
| Invalid_package_type _ -> "Invalid package type"
| Removed_string_set _ -> "Removed string set"
| Applicative_path _ -> "Applicative path"
| _ -> "Unknown error"

let reason_error_to_string = function
| Reason_errors.Lexing_error (Illegal_character _e) -> "Illegal character"
| Lexing_error (Illegal_escape _) -> "Illegal escape"
| Lexing_error (Unterminated_comment _) -> "Unterminated comment"
| Lexing_error Unterminated_string -> "Unterminated string"
| Lexing_error (Unterminated_string_in_comment _) ->
"Unterminated string in comment"
| Lexing_error (Keyword_as_label _) -> "Keyword as label"
| Lexing_error (Invalid_literal _) -> "Invalid literal"
| Parsing_error _ -> "Parsing error"
| Ast_error (Not_expecting _) -> "Not expecting"
| Ast_error (Other_syntax_error _) -> "Other syntax error"
| Ast_error (Variable_in_scope _) -> "Variable in scope"
| Ast_error (Applicative_path _) -> "Applicative path"

let locationToJsObj (loc : Astlib.Location.t) =
let _file, start_line, start_char = Location.get_pos_info loc.loc_start in
let _, end_line, end_char = Location.get_pos_info loc.loc_end in
(* The right way of handling ocaml syntax error locations. Do do this at home
copied over from
https://github.com/BuckleScript/bucklescript/blob/2ad2310f18567aa13030cdf32adb007d297ee717/jscomp/super_errors/super_location.ml#L73
*)
let normalizedRange =
if start_char == -1 || end_char == -1 then
(* happens sometimes. Syntax error for example *)
None
else if start_line = end_line && start_char >= end_char then
(* in some errors, starting char and ending char can be the same. But
since ending char was supposed to be exclusive, here it might end up
smaller than the starting char if we naively did start_char + 1 to
just the starting char and forget ending char *)
let same_char = start_char + 1 in
Some ((start_line, same_char), (end_line, same_char))
else
(* again: end_char is exclusive, so +1-1=0 *)
Some ((start_line, start_char + 1), (end_line, end_char))
in
match normalizedRange with
| None -> Js.undefined
| Some ((start_line, start_line_start_char), (end_line, end_line_end_char)) ->
let intToJsFloatToAny i =
i |> float_of_int |> Js.number_of_float |> Js.Unsafe.inject
in
Js.def
(Js.Unsafe.obj
[|
("startLine", intToJsFloatToAny start_line);
("startLineStartChar", intToJsFloatToAny start_line_start_char);
("endLine", intToJsFloatToAny end_line);
("endLineEndChar", intToJsFloatToAny end_line_end_char);
|])

let warningToJsObj (warning : Location.t) =
Astlib.Location.
{
loc_start = warning.loc_start;
loc_end = warning.loc_end;
loc_ghost = warning.loc_ghost;
}

let parseWith f code =
let throwAnything = Js.Unsafe.js_expr "function(a) {throw a}" in
try code |> Lexing.from_string |> f with
(* from ocaml and reason *)
| Syntaxerr.Error err ->
let location : Location.t = Syntaxerr.location_of_error err in
let jsLocation = locationToJsObj (warningToJsObj location) in
let errorString = syntaxerr_error_to_string err in
let jsError =
Js.Unsafe.obj
[|
("message", Js.Unsafe.inject (Js.string errorString));
("location", Js.Unsafe.inject jsLocation);
|]
in
Js.Unsafe.fun_call throwAnything [| Js.Unsafe.inject jsError |]
| Reason_errors.Reason_error (err, loc) ->
let jsLocation = locationToJsObj loc in
let errorString = reason_error_to_string err in
let jsError =
Js.Unsafe.obj
[|
("message", Js.Unsafe.inject (Js.string errorString));
("location", Js.Unsafe.inject jsLocation);
|]
in
Js.Unsafe.fun_call throwAnything [| Js.Unsafe.inject jsError |]

let ast_string exp =
let config =
Ppxlib.Pp_ast.Config.make ~json:true ~show_attrs:true ~show_locs:true
~loc_mode:`Full ()
in
Format.asprintf "%a" (Ppxlib.Pp_ast.structure ~config) exp

let parse f code =
let structure, _ = parseWith f code in
ast_string structure

let parse_reason code = parse RE.implementation_with_comments code
let parse_ocaml code = parse ML.implementation_with_comments code
let _ = Js.export "parseReason" parse_reason
let _ = Js.export "parseOcaml" parse_ocaml
8 changes: 3 additions & 5 deletions src/dune
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
(executable
(name AstExplorerRefmt)
(public_name AstExplorerRefmt.exe)
(libraries reason)
(preprocess (pps js_of_ocaml-ppx))
)
(name ast_explorer_refmt)
(libraries reason ppxlib js_of_ocaml ppxlib.astlib compiler-libs.common)
(modes js))
4 changes: 4 additions & 0 deletions src/example/example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
let code = "let x = 1";

let ast = window.parseReason(code);
window.document.querySelector("#app").innerHTML = ast;
8 changes: 6 additions & 2 deletions src/index.html → src/example/index.html
Original file line number Diff line number Diff line change
@@ -7,10 +7,14 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script
type="text/javascript"
src="../_esy/default/build/default/src/AstExplorerRefmt.bc.js"
src="../dist/ast_explorer_refmt.bc.js"
></script>
</head>
<body>
<div id="app"></div>
<pre id="app"></pre>
</body>
<script
type="text/javascript"
src="./example.js"
></script>
</html>
784 changes: 782 additions & 2 deletions yarn.lock

Large diffs are not rendered by default.