Skip to content

Commit

Permalink
feat: add sample
Browse files Browse the repository at this point in the history
  • Loading branch information
pedrobslisboa committed Dec 11, 2024
1 parent e76368c commit f8881f4
Show file tree
Hide file tree
Showing 9 changed files with 169 additions and 97 deletions.
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
Expand Up @@ -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.
5 changes: 4 additions & 1 deletion astexplorer-refmt.opam
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# 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 <[email protected]>"]
maintainer: [
"Javier Chávarri <[email protected]>"
"Pedro Lisboa <[email protected]>"
]
authors: ["Javier Chávarri <[email protected]>"]
license: "ISC"
homepage: "https://github.com/jchavarri/astexplorer-refmt"
Expand Down
2 changes: 1 addition & 1 deletion dune
Original file line number Diff line number Diff line change
@@ -1 +1 @@
(dirs src)
(dirs src)
2 changes: 1 addition & 1 deletion dune-project
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

(license ISC)

(maintainers "Javier Chávarri <[email protected]>")
(maintainers "Javier Chávarri <[email protected]>" "Pedro Lisboa <[email protected]>")

(authors "Javier Chávarri <[email protected]>")

Expand Down
133 changes: 65 additions & 68 deletions src/ast_explorer_refmt.ml
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
(*
* 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"
Expand All @@ -21,26 +19,26 @@ let syntaxerr_error_to_string = function
| _ -> "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"
| 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"
| 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"
| 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
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
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
Expand All @@ -60,67 +58,66 @@ let locationToJsObj (loc: Astlib.Location.t) =
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 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
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|]
| 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 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 () in
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 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
let _ = Js.export "parseOcaml" parse_ocaml
4 changes: 2 additions & 2 deletions src/dune
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
(executable
(name ast_explorer_refmt)
(libraries reason ppxlib js_of_ocaml ppxlib.astlib compiler-libs.bytecomp)
(modes js))
(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
Expand Up @@ -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>

0 comments on commit f8881f4

Please sign in to comment.