From 7867b946b92e7953ff71f41a53176fabcc36cc71 Mon Sep 17 00:00:00 2001 From: Darin Morrison Date: Thu, 22 Dec 2016 18:03:18 -0700 Subject: [PATCH] Add support for Reason (#3336) --- .gitmodules | 4 +- grammars.yml | 3 + lib/linguist/languages.yml | 14 + samples/C++/bug1163046.--skeleton.re | 46 + samples/C++/cnokw.re | 239 +++++ samples/C++/cvsignore.re | 63 ++ samples/C++/simple.re | 13 + samples/Reason/JSX.re | 483 ++++++++++ samples/Reason/Layout.re | 1326 ++++++++++++++++++++++++++ samples/Reason/Machine.re | 344 +++++++ samples/Reason/SuperMerlin.re | 308 ++++++ samples/Reason/Syntax.re | 989 +++++++++++++++++++ vendor/grammars/reason | 1 + vendor/licenses/grammar/reason.txt | 35 + 14 files changed, 3867 insertions(+), 1 deletion(-) create mode 100644 samples/C++/bug1163046.--skeleton.re create mode 100644 samples/C++/cnokw.re create mode 100644 samples/C++/cvsignore.re create mode 100644 samples/C++/simple.re create mode 100644 samples/Reason/JSX.re create mode 100644 samples/Reason/Layout.re create mode 100644 samples/Reason/Machine.re create mode 100644 samples/Reason/SuperMerlin.re create mode 100644 samples/Reason/Syntax.re create mode 160000 vendor/grammars/reason create mode 100644 vendor/licenses/grammar/reason.txt diff --git a/.gitmodules b/.gitmodules index 8a4ae9fe..c268e964 100644 --- a/.gitmodules +++ b/.gitmodules @@ -809,4 +809,6 @@ [submodule "vendor/grammars/rascal-syntax-highlighting"] path = vendor/grammars/rascal-syntax-highlighting url = https://github.com/usethesource/rascal-syntax-highlighting - +[submodule "vendor/grammars/reason"] + path = vendor/grammars/reason + url = https://github.com/facebook/reason diff --git a/grammars.yml b/grammars.yml index 030961fb..097989ba 100755 --- a/grammars.yml +++ b/grammars.yml @@ -541,6 +541,9 @@ vendor/grammars/r.tmbundle: - text.tex.latex.rd vendor/grammars/rascal-syntax-highlighting: - source.rascal +vendor/grammars/reason: +- source.reason +- source.reason.hover.type vendor/grammars/ruby-slim.tmbundle: - text.slim vendor/grammars/ruby.tmbundle: diff --git a/lib/linguist/languages.yml b/lib/linguist/languages.yml index 096c7b10..19d30ed2 100755 --- a/lib/linguist/languages.yml +++ b/lib/linguist/languages.yml @@ -499,6 +499,7 @@ C++: - ".inc" - ".inl" - ".ipp" + - ".re" - ".tcc" - ".tpp" language_id: 43 @@ -3548,6 +3549,19 @@ Raw token data: tm_scope: none ace_mode: text language_id: 318 +Reason: + type: programming + group: OCaml + ace_mode: rust + codemirror_mode: rust + codemirror_mime_type: text/x-rustsrc + extensions: + - ".re" + - ".rei" + interpreters: + - ocaml + tm_scope: source.reason + language_id: 869538413 Rebol: type: programming color: "#358a5b" diff --git a/samples/C++/bug1163046.--skeleton.re b/samples/C++/bug1163046.--skeleton.re new file mode 100644 index 00000000..fee9bd6c --- /dev/null +++ b/samples/C++/bug1163046.--skeleton.re @@ -0,0 +1,46 @@ +#include + +#define YYCTYPE unsigned char +#define YYCURSOR cursor +#define YYLIMIT cursor +#define YYMARKER marker +#define YYFILL(n) + +bool scan(const char *text) +{ + YYCTYPE *start = (YYCTYPE *)text; + YYCTYPE *cursor = (YYCTYPE *)text; + YYCTYPE *marker = (YYCTYPE *)text; +next: + YYCTYPE *token = cursor; +/*!re2c +'(This file must be converted with BinHex 4.0)' + { + if (token == start || *(token - 1) == '\n') + return true; else goto next; + } +[\001-\377] + { goto next; } +[\000] + { return false; } +*/ + return false; +} + +#define do_scan(str, expect) \ + res = scan(str) == expect ? 0 : 1; \ + std::cerr << str << "\t-\t" << (res ? "fail" : "ok") << std::endl; \ + result += res + +/*!max:re2c */ + +int main(int,void**) +{ + int res, result = 0; + do_scan("(This file must be converted with BinHex 4.0)", 1); + do_scan("x(This file must be converted with BinHex 4.0)", 0); + do_scan("(This file must be converted with BinHex 4.0)x", 1); + do_scan("x(This file must be converted with BinHex 4.0)x", 0); + + return result; +} diff --git a/samples/C++/cnokw.re b/samples/C++/cnokw.re new file mode 100644 index 00000000..bdc12793 --- /dev/null +++ b/samples/C++/cnokw.re @@ -0,0 +1,239 @@ +#include +#include +#include + +#define ADDEQ 257 +#define ANDAND 258 +#define ANDEQ 259 +#define ARRAY 260 +#define ASM 261 +#define AUTO 262 +#define BREAK 263 +#define CASE 264 +#define CHAR 265 +#define CONST 266 +#define CONTINUE 267 +#define DECR 268 +#define DEFAULT 269 +#define DEREF 270 +#define DIVEQ 271 +#define DO 272 +#define DOUBLE 273 +#define ELLIPSIS 274 +#define ELSE 275 +#define ENUM 276 +#define EQL 277 +#define EXTERN 278 +#define FCON 279 +#define FLOAT 280 +#define FOR 281 +#define FUNCTION 282 +#define GEQ 283 +#define GOTO 284 +#define ICON 285 +#define ID 286 +#define IF 287 +#define INCR 288 +#define INT 289 +#define LEQ 290 +#define LONG 291 +#define LSHIFT 292 +#define LSHIFTEQ 293 +#define MODEQ 294 +#define MULEQ 295 +#define NEQ 296 +#define OREQ 297 +#define OROR 298 +#define POINTER 299 +#define REGISTER 300 +#define RETURN 301 +#define RSHIFT 302 +#define RSHIFTEQ 303 +#define SCON 304 +#define SHORT 305 +#define SIGNED 306 +#define SIZEOF 307 +#define STATIC 308 +#define STRUCT 309 +#define SUBEQ 310 +#define SWITCH 311 +#define TYPEDEF 312 +#define UNION 313 +#define UNSIGNED 314 +#define VOID 315 +#define VOLATILE 316 +#define WHILE 317 +#define XOREQ 318 +#define EOI 319 + +typedef unsigned int uint; +typedef unsigned char uchar; + +#define BSIZE 8192 + +#define YYCTYPE uchar +#define YYCURSOR cursor +#define YYLIMIT s->lim +#define YYMARKER s->ptr +#define YYFILL(n) {cursor = fill(s, cursor);} + +#define RET(i) {s->cur = cursor; return i;} + +typedef struct Scanner { + int fd; + uchar *bot, *tok, *ptr, *cur, *pos, *lim, *top, *eof; + uint line; +} Scanner; + +uchar *fill(Scanner *s, uchar *cursor){ + if(!s->eof){ + uint cnt = s->tok - s->bot; + if(cnt){ + memcpy(s->bot, s->tok, s->lim - s->tok); + s->tok = s->bot; + s->ptr -= cnt; + cursor -= cnt; + s->pos -= cnt; + s->lim -= cnt; + } + if((s->top - s->lim) < BSIZE){ + uchar *buf = (uchar*) malloc(((s->lim - s->bot) + BSIZE)*sizeof(uchar)); + memcpy(buf, s->tok, s->lim - s->tok); + s->tok = buf; + s->ptr = &buf[s->ptr - s->bot]; + cursor = &buf[cursor - s->bot]; + s->pos = &buf[s->pos - s->bot]; + s->lim = &buf[s->lim - s->bot]; + s->top = &s->lim[BSIZE]; + free(s->bot); + s->bot = buf; + } + if((cnt = read(s->fd, (char*) s->lim, BSIZE)) != BSIZE){ + s->eof = &s->lim[cnt]; *(s->eof)++ = '\n'; + } + s->lim += cnt; + } + return cursor; +} + +int scan(Scanner *s){ + uchar *cursor = s->cur; +std: + s->tok = cursor; +/*!re2c +any = [\000-\377]; +O = [0-7]; +D = [0-9]; +L = [a-zA-Z_]; +H = [a-fA-F0-9]; +E = [Ee] [+-]? D+; +FS = [fFlL]; +IS = [uUlL]*; +ESC = [\\] ([abfnrtv?'"\\] | "x" H+ | O+); +*/ + +/*!re2c + "/*" { goto comment; } + + L (L|D)* { RET(ID); } + + ("0" [xX] H+ IS?) | ("0" D+ IS?) | (D+ IS?) | + (['] (ESC|any\[\n\\'])* [']) + { RET(ICON); } + + (D+ E FS?) | (D* "." D+ E? FS?) | (D+ "." D* E? FS?) + { RET(FCON); } + + (["] (ESC|any\[\n\\"])* ["]) + { RET(SCON); } + + "..." { RET(ELLIPSIS); } + ">>=" { RET(RSHIFTEQ); } + "<<=" { RET(LSHIFTEQ); } + "+=" { RET(ADDEQ); } + "-=" { RET(SUBEQ); } + "*=" { RET(MULEQ); } + "/=" { RET(DIVEQ); } + "%=" { RET(MODEQ); } + "&=" { RET(ANDEQ); } + "^=" { RET(XOREQ); } + "|=" { RET(OREQ); } + ">>" { RET(RSHIFT); } + "<<" { RET(LSHIFT); } + "++" { RET(INCR); } + "--" { RET(DECR); } + "->" { RET(DEREF); } + "&&" { RET(ANDAND); } + "||" { RET(OROR); } + "<=" { RET(LEQ); } + ">=" { RET(GEQ); } + "==" { RET(EQL); } + "!=" { RET(NEQ); } + ";" { RET(';'); } + "{" { RET('{'); } + "}" { RET('}'); } + "," { RET(','); } + ":" { RET(':'); } + "=" { RET('='); } + "(" { RET('('); } + ")" { RET(')'); } + "[" { RET('['); } + "]" { RET(']'); } + "." { RET('.'); } + "&" { RET('&'); } + "!" { RET('!'); } + "~" { RET('~'); } + "-" { RET('-'); } + "+" { RET('+'); } + "*" { RET('*'); } + "/" { RET('/'); } + "%" { RET('%'); } + "<" { RET('<'); } + ">" { RET('>'); } + "^" { RET('^'); } + "|" { RET('|'); } + "?" { RET('?'); } + + + [ \t\v\f]+ { goto std; } + + "\n" + { + if(cursor == s->eof) RET(EOI); + s->pos = cursor; s->line++; + goto std; + } + + any + { + printf("unexpected character: %c\n", *s->tok); + goto std; + } +*/ + +comment: +/*!re2c + "*/" { goto std; } + "\n" + { + if(cursor == s->eof) RET(EOI); + s->tok = s->pos = cursor; s->line++; + goto comment; + } + any { goto comment; } +*/ +} + +main(){ + Scanner in; + int t; + memset((char*) &in, 0, sizeof(in)); + in.fd = 0; + while((t = scan(&in)) != EOI){ +/* + printf("%d\t%.*s\n", t, in.cur - in.tok, in.tok); + printf("%d\n", t); +*/ + } + close(in.fd); +} diff --git a/samples/C++/cvsignore.re b/samples/C++/cvsignore.re new file mode 100644 index 00000000..1de9e16a --- /dev/null +++ b/samples/C++/cvsignore.re @@ -0,0 +1,63 @@ + +#define YYFILL(n) if (cursor >= limit) break; +#define YYCTYPE char +#define YYCURSOR cursor +#define YYLIMIT limit +#define YYMARKER marker + +/*!re2c +any = (.|"\n"); +value = (":" (.\"$")+)?; +cvsdat = "Date"; +cvsid = "Id"; +cvslog = "Log"; +cvsrev = "Revision"; +cvssrc = "Source"; +*/ + +#define APPEND(text) \ + append(output, outsize, text, sizeof(text) - sizeof(YYCTYPE)) + +inline void append(YYCTYPE *output, size_t & outsize, const YYCTYPE * text, size_t len) +{ + memcpy(output + outsize, text, len); + outsize += (len / sizeof(YYCTYPE)); +} + +void scan(YYCTYPE *pText, size_t *pSize, int *pbChanged) +{ + // rule + // scan lines + // find $ in lines + // compact $: .. $ to $$ + + YYCTYPE *output; + const YYCTYPE *cursor, *limit, *marker; + + cursor = marker = output = *pText; + + size_t insize = *pSize; + size_t outsize = 0; + + limit = cursor + insize; + + while(1) { +loop: +/*!re2c + +"$" cvsdat value "$" { APPEND(L"$" L"Date$"); goto loop; } +"$" cvsid value "$" { APPEND(L"$" L"Id$"); goto loop; } +"$" cvslog value "$" { APPEND(L"$" L"Log$"); goto loop; } +"$" cvsrev value "$" { APPEND(L"$" L"Revision$"); goto loop; } +"$" cvssrc value "$" { APPEND(L"$" L"Source$"); goto loop; } +any { output[outsize++] = cursor[-1]; if (cursor >= limit) break; goto loop; } + +*/ + } + output[outsize] = '\0'; + + // set the new size + *pSize = outsize; + + *pbChanged = (insize == outsize) ? 0 : 1; +} diff --git a/samples/C++/simple.re b/samples/C++/simple.re new file mode 100644 index 00000000..5fd8891f --- /dev/null +++ b/samples/C++/simple.re @@ -0,0 +1,13 @@ +#define NULL ((char*) 0) +char *scan(char *p){ +char *q; +#define YYCTYPE char +#define YYCURSOR p +#define YYLIMIT p +#define YYMARKER q +#define YYFILL(n) +/*!re2c + [0-9]+ {return YYCURSOR;} + [\000-\377] {return NULL;} +*/ +} diff --git a/samples/Reason/JSX.re b/samples/Reason/JSX.re new file mode 100644 index 00000000..ad2871d7 --- /dev/null +++ b/samples/Reason/JSX.re @@ -0,0 +1,483 @@ +type component = {displayName: string}; + +let module Bar = { + let createElement c::c=? children => { + displayName: "test" + }; +}; + +let module Nesting = { + let createElement children => { + displayName: "test" + }; +}; + +let module Much = { + let createElement children => { + displayName: "test" + }; +}; + +let module Foo = { + let createElement a::a=? b::b=? children => { + displayName: "test" + }; +}; + +let module One = { + let createElement + test::test=? + foo::foo=? + children => { + displayName: "test" + }; + let createElementobvioustypo + test::test + children => { + displayName: "test" + }; +}; + +let module Two = { + let createElement foo::foo=? children => { + displayName: "test" + }; +}; + +let module Sibling = { + let createElement + foo::foo=? + (children: list component) => { + displayName: "test" + }; +}; + +let module Test = { + let createElement yo::yo=? children => { + displayName: "test" + }; +}; + +let module So = { + let createElement children => { + displayName: "test" + }; +}; + +let module Foo2 = { + let createElement children => { + displayName: "test" + }; +}; + +let module Text = { + let createElement children => { + displayName: "test" + }; +}; + +let module Exp = { + let createElement children => { + displayName: "test" + }; +}; + +let module Pun = { + let createElement intended::intended=? children => { + displayName: "test" + }; +}; + +let module Namespace = { + let module Foo = { + let createElement + intended::intended=? + anotherOptional::x=100 + children => { + displayName: "test" + }; + }; +}; + +let module LotsOfArguments = { + let createElement + argument1::argument1=? + argument2::argument2=? + argument3::argument3=? + argument4::argument4=? + argument5::argument5=? + argument6::argument6=? + children => { + displayName: "test" + }; +}; + +let div argument1::argument1=? children => { + displayName: "test" +}; + +let module List1 = { + let createElement children => { + displayName: "test" + }; +}; + +let module List2 = { + let createElement children => { + displayName: "test" + }; +}; + +let module List3 = { + let createElement children => { + displayName: "test" + }; +}; + +let (/><) a b => a + b; + +let (><) a b => a + b; + +let (/>) a b => a + b; + +let (><\/) a b => a + b; + +let tag1 = 5 />< 6; + +let tag2 = 5 >< 7; + +let tag3 = 5 /> 7; + +let tag4 = 5 ><\/ 7; + +let b = 2; + +let selfClosing = ; + +let selfClosing2 = ; + +let selfClosing3 = + ; + +let a = a + 2) /> ; + +let a3 = ; + +let a4 = + + + + ; + +let a5 = "testing a string here" ; + +let a6 = + + "testing a string here" + + "another string" + + (2 + 4) + ; + +let intended = true; + +let punning = ; + +let namespace = ; + +let c = ; + +let d = ; + +let spaceBefore = + ; + +let spaceBefore2 = ; + +let siblingNotSpaced = + ; + +let jsxInList = []; + +let jsxInList2 = []; + +let jsxInListA = []; + +let jsxInListB = []; + +let jsxInListC = []; + +let jsxInListD = []; + +let jsxInList3 = [, , ]; + +let jsxInList4 = [, , ]; + +let jsxInList5 = [, ]; + +let jsxInList6 = [, ]; + +let jsxInList7 = [, ]; + +let jsxInList8 = [, ]; + +let testFunc b => b; + +let jsxInFnCall = testFunc ; + +let lotsOfArguments = + + + ; + +let lowerCase =
; + +let b = 0; + +let d = 0; + +/* + * Should pun the first example: + */ +let a = 5 ; + +let a = 5 ; + +let a = 5 ; + +let a = 0.55 ; + +let a = Foo.createElement "" [@JSX]; + +let ident = a ; + +let fragment1 = <> ; + +let fragment2 = <> ; + +let fragment3 = <> ; + +let fragment4 = <> ; + +let fragment5 = <> ; + +let fragment6 = <> ; + +let fragment7 = <> ; + +let fragment8 = <> ; + +let fragment9 = <> 2 2 2 2 ; + +let fragment10 = <> 2.2 3.2 4.6 1.2 ; + +let fragment11 = <> "str" ; + +let fragment12 = <> (6 + 2) (6 + 2) (6 + 2) ; + +let fragment13 = <> fragment11 fragment11 ; + +let listOfItems1 = 1 2 3 4 5 ; + +let listOfItems2 = + 1.0 2.8 3.8 4.0 5.1 ; + +let listOfItems3 = + fragment11 fragment11 ; + +/* + * Several sequential simple jsx expressions must be separated with a space. + */ +let thisIsRight a b => (); + +let tagOne children => (); + +let tagTwo children => (); + +/* thisIsWrong ; */ +thisIsRight ; + +/* thisIsWrong ; */ +thisIsRight ; + +let a children => (); + +let b children => (); + +let thisIsOkay = + ; + +let thisIsAlsoOkay = + ; + +/* Doesn't make any sense, but suppose you defined an + infix operator to compare jsx */ + < ; + + > ; + + < ; + + > ; + +let listOfListOfJsx = [<> ]; + +let listOfListOfJsx = [<> ]; + +let listOfListOfJsx = [ + <> , + <> +]; + +let listOfListOfJsx = [ + <> , + <> , + ...listOfListOfJsx +]; + +let sameButWithSpaces = [<> ]; + +let sameButWithSpaces = [<> ]; + +let sameButWithSpaces = [ + <> , + <> +]; + +let sameButWithSpaces = [ + <> , + <> , + ...sameButWithSpaces +]; + +/* + * Test named tag right next to an open bracket. + */ +let listOfJsx = []; + +let listOfJsx = []; + +let listOfJsx = [, ]; + +let listOfJsx = [, , ...listOfJsx]; + +let sameButWithSpaces = []; + +let sameButWithSpaces = []; + +let sameButWithSpaces = [, ]; + +let sameButWithSpaces = [ + , + , + ...sameButWithSpaces +]; + + +/** + * Test no conflict with polymorphic variant types. + */ +type thisType = [ | `Foo | `Bar]; + +type t 'a = [< thisType] as 'a; + +let asd = + "a" "b" [@foo]; + +let asd2 = + One.createElementobvioustypo + test::false + ["a", "b"] + [@JSX] + [@foo]; + +let span + test::(test: bool) + foo::(foo: int) + children => 1; + +let asd = + "a" "b" [@foo]; + +/* "video" call doesn't end with a list, so the expression isn't converted to JSX */ +let video test::(test: bool) children => children; + +let asd2 = video test::false 10 [@JSX] [@foo]; + +let div children => 1; + +((fun () => div) ()) [] [@JSX]; + +let myFun () => + <> + + + + + + + + + + + + ; + +let myFun () => <> ; + +let myFun () => + <> + + + + + + + + + + + + ; + + +/** + * Children should wrap without forcing attributes to. + */ + + + + + +; +/** + * Failing test cases: + */ +/* let res = ) > */ +/* */ +/* ; */ +/* let res = ) />; */ diff --git a/samples/Reason/Layout.re b/samples/Reason/Layout.re new file mode 100644 index 00000000..f9d81388 --- /dev/null +++ b/samples/Reason/Layout.re @@ -0,0 +1,1326 @@ +/** + * Copyright (c) 2014, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +/* + * From css-layout comments: + * The spec describes four different layout modes: "fill available", "max + * content", "min content", and "fit content". Of these, we don't use + * "min content" because we don't support default minimum main sizes (see + * above for details). Each of our measure modes maps to a layout mode + * from the spec (https://www.w3.org/TR/css3-sizing/#terms): + * + * -.CssMeasureModeUndefined: `max-content` + * -.CssMeasureModeExactly: `fill-available` + * -.CssMeasureModeAtMost: `fit-content` + * If infinite space available in that axis, then `max-content.` + * Else, `min(max-content size, max(min-content size, fill-available size))` + * (Although, we don't support min-content) + */ +open LayoutTypes; + +open LayoutValue; + +open LayoutSupport; + +let gCurrentGenerationCount = ref 0; + +let gDepth = ref 0; + +let gPrintTree = {contents: false}; + +let gPrintChanges = {contents: false}; + +let gPrintSkips = {contents: false}; + +let measureString = "measure"; + +let stretchString = "stretch"; + +let absMeasureString = "abs-measure"; + +let absLayoutString = "abs-layout"; + +let initialString = "initial"; + +let flexString = "flex"; + +let spacer = " "; + +let getSpacer level => { + let spacerLen = String.length spacer; + let lvl = level > spacerLen ? level : spacerLen; + String.sub spacer lvl (String.length spacer) +}; + +let getModeName (mode, isLayoutInsteadOfMeasure) => + switch mode { + | CSS_MEASURE_MODE_NEGATIVE_ONE_WHATEVER_THAT_MEANS => + isLayoutInsteadOfMeasure ? + "CSS_MEASURE_MODE_NEGATIVE_ONE_WHATEVER_THAT_MEANS" : + "CSS_MEASURE_MODE_NEGATIVE_ONE_WHATEVER_THAT_MEANS" + | CssMeasureModeUndefined => isLayoutInsteadOfMeasure ? "LAY_UNDEFINED" : "UNDEFINED" + | CssMeasureModeExactly => isLayoutInsteadOfMeasure ? "LAY_EXACTLY" : "EXACTLY" + | CssMeasureModeAtMost => isLayoutInsteadOfMeasure ? "LAY_AT_MOST" : "AT_MOST" + }; + +let canUseCachedMeasurement + ( + availableWidth, + availableHeight, + marginRow, + marginColumn, + widthMeasureMode, + heightMeasureMode, + cachedLayout + ) => + if ( + cachedLayout.availableWidth == availableWidth && + cachedLayout.availableHeight == availableHeight && + cachedLayout.widthMeasureMode == widthMeasureMode && cachedLayout.heightMeasureMode == heightMeasureMode + ) { + true + } else if + /* Is it an exact match?*/ + /* If the width is an exact match, try a fuzzy match on the height.*/ + ( + cachedLayout.widthMeasureMode == widthMeasureMode && + cachedLayout.availableWidth == availableWidth && + heightMeasureMode === CssMeasureModeExactly && + availableHeight -. marginColumn == cachedLayout.computedHeight + ) { + true + } else if + /* If the height is an exact match, try a fuzzy match on the width.*/ + ( + cachedLayout.heightMeasureMode == heightMeasureMode && + cachedLayout.availableHeight == availableHeight && + widthMeasureMode === CssMeasureModeExactly && availableWidth -. marginRow == cachedLayout.computedWidth + ) { + true + } else { + false + }; + +let cachedMeasurementAt layout i => + switch i { + | 0 => layout.cachedMeasurement1 + | 1 => layout.cachedMeasurement2 + | 2 => layout.cachedMeasurement3 + | 3 => layout.cachedMeasurement4 + | 4 => layout.cachedMeasurement5 + | 5 => layout.cachedMeasurement6 + | _ => raise (Invalid_argument ("No cached measurement at " ^ string_of_int i)) + }; + + +/** + * This is a wrapper around the layoutNodeImpl function. It determines + * whether the layout request is redundant and can be skipped. + * + * Parameters: + * Input parameters are the same as layoutNodeImpl (see above) + * Return parameter is true if layout was performed, false if skipped + */ +let rec layoutNodeInternal + node + availableWidth + availableHeight + parentDirection + widthMeasureMode + heightMeasureMode + performLayout + reason => { + let layout = node.layout; + gDepth.contents = gDepth.contents + 1; + let needToVisitNode = + node.isDirty node.context && layout.generationCount != gCurrentGenerationCount.contents || + layout.lastParentDirection != parentDirection; + if needToVisitNode { + /* Invalidate the cached results.*/ + layout.nextCachedMeasurementsIndex = 0; + layout.cachedLayout.widthMeasureMode = CSS_MEASURE_MODE_NEGATIVE_ONE_WHATEVER_THAT_MEANS; + layout.cachedLayout.heightMeasureMode = CSS_MEASURE_MODE_NEGATIVE_ONE_WHATEVER_THAT_MEANS + }; + let cachedResults = ref None; + /* Determine whether the results are already cached. We maintain a separate*/ + /* cache for layouts and measurements. A layout operation modifies the positions*/ + /* and dimensions for nodes in the subtree. The algorithm assumes that each node*/ + /* gets layed out a maximum of one time per tree layout, but multiple measurements*/ + /* may be required to resolve all of the flex dimensions.*/ + /* We handle nodes with measure functions specially here because they are the most + * expensive to measure, so it's worth avoiding redundant measurements if at all possible.*/ + if (node.measure !== dummyMeasure && node.childrenCount === 0) { + let marginAxisRow = getMarginAxis node CssFlexDirectionRow; + let marginAxisColumn = getMarginAxis node CssFlexDirectionColumn; + /* First, try to use the layout cache.*/ + if ( + canUseCachedMeasurement ( + availableWidth, + availableHeight, + marginAxisRow, + marginAxisColumn, + widthMeasureMode, + heightMeasureMode, + layout.cachedLayout + ) + ) { + cachedResults.contents = Some layout.cachedLayout + } else { + /* Try to use the measurement cache.*/ + let foundCached = {contents: false}; + for i in 0 to (layout.nextCachedMeasurementsIndex - 1) { + /* This is basically the "break" */ + if (not foundCached.contents) { + let cachedMeasurementAtIndex = cachedMeasurementAt layout i; + if ( + canUseCachedMeasurement ( + availableWidth, + availableHeight, + marginAxisRow, + marginAxisColumn, + widthMeasureMode, + heightMeasureMode, + cachedMeasurementAtIndex + ) + ) { + cachedResults.contents = Some cachedMeasurementAtIndex; + foundCached.contents = true + } + } + } + } + } else if performLayout { + if ( + layout.cachedLayout.availableWidth == availableWidth && + layout.cachedLayout.availableHeight == availableHeight && + layout.cachedLayout.widthMeasureMode == widthMeasureMode && + layout.cachedLayout.heightMeasureMode == heightMeasureMode + ) { + cachedResults.contents = Some layout.cachedLayout + } + } else { + let foundCached = {contents: false}; + for i in 0 to (layout.nextCachedMeasurementsIndex - 1) { + /* This is basically the "break" */ + if (not foundCached.contents) { + let cachedMeasurementAtIndex = cachedMeasurementAt layout i; + if ( + cachedMeasurementAtIndex.availableWidth == availableWidth && + cachedMeasurementAtIndex.availableHeight == availableHeight && + cachedMeasurementAtIndex.widthMeasureMode == widthMeasureMode && + cachedMeasurementAtIndex.heightMeasureMode == heightMeasureMode + ) { + cachedResults.contents = Some cachedMeasurementAtIndex; + foundCached.contents = true + } + } + } + }; + if (not needToVisitNode && cachedResults.contents != None) { + let cachedResults_ = + switch cachedResults.contents { + | None => raise (Invalid_argument "Not possible") + | Some cr => cr + }; + layout.measuredWidth = cachedResults_.computedWidth; + layout.measuredHeight = cachedResults_.computedHeight; + if (gPrintChanges.contents && gPrintSkips.contents) { + Printf.printf "%s%d.{[skipped] " (getSpacer gDepth.contents) gDepth.contents; + switch node.print { + | None => () + | Some printer => printer node.context + }; + Printf.printf + "wm: %s, hm: %s, aw: %s ah: %s => d: (%s, %s) %s\n" + (getModeName (widthMeasureMode, performLayout)) + (getModeName (heightMeasureMode, performLayout)) + (scalarToString availableWidth) + (scalarToString availableHeight) + (scalarToString cachedResults_.computedWidth) + (scalarToString cachedResults_.computedHeight) + reason + } + } else { + if gPrintChanges.contents { + Printf.printf "%s%d.{%s" (getSpacer gDepth.contents) gDepth.contents (needToVisitNode ? "*" : ""); + switch node.print { + | None => () + | Some printer => printer node.context + }; + Printf.printf + "wm: %s, hm: %s, aw: %s ah: %s %s\n" + (getModeName (widthMeasureMode, performLayout)) + (getModeName (heightMeasureMode, performLayout)) + (scalarToString availableWidth) + (scalarToString availableHeight) + reason + }; + layoutNodeImpl ( + node, + availableWidth, + availableHeight, + parentDirection, + widthMeasureMode, + heightMeasureMode, + performLayout + ); + if gPrintChanges.contents { + Printf.printf "%s%d.}%s" (getSpacer gDepth.contents) gDepth.contents (needToVisitNode ? "*" : ""); + switch node.print { + | None => () + | Some printer => printer node.context + }; + Printf.printf + "wm: %s, hm: %s, d: (%s, %s) %s\n" + (getModeName (widthMeasureMode, performLayout)) + (getModeName (heightMeasureMode, performLayout)) + (scalarToString layout.measuredWidth) + (scalarToString layout.measuredHeight) + reason + }; + layout.lastParentDirection = parentDirection; + if (cachedResults.contents === None) { + if (layout.nextCachedMeasurementsIndex == css_max_cached_result_count) { + if gPrintChanges.contents { + Printf.printf "Out of cache entries!\n" + }; + layout.nextCachedMeasurementsIndex = 0 + }; + let newCacheEntry = + performLayout ? + /* Use the single layout cache entry.*/ + layout.cachedLayout : + { + /* Allocate a new measurement cache entry.*/ + let newCacheEntry_ = cachedMeasurementAt layout layout.nextCachedMeasurementsIndex; + layout.nextCachedMeasurementsIndex = layout.nextCachedMeasurementsIndex + 1; + newCacheEntry_ + }; + newCacheEntry.availableWidth = availableWidth; + newCacheEntry.availableHeight = availableHeight; + newCacheEntry.widthMeasureMode = widthMeasureMode; + newCacheEntry.heightMeasureMode = heightMeasureMode; + newCacheEntry.computedWidth = layout.measuredWidth; + newCacheEntry.computedHeight = layout.measuredHeight + } + }; + if performLayout { + node.layout.width = node.layout.measuredWidth; + node.layout.height = node.layout.measuredHeight; + layout.hasNewLayout = true + }; + gDepth.contents = gDepth.contents - 1; + layout.generationCount = gCurrentGenerationCount.contents; + needToVisitNode || cachedResults.contents === None +} +and computeChildFlexBasis node child width widthMode height heightMode direction => { + let mainAxis = resolveAxis node.style.flexDirection direction; + let isMainAxisRow = isRowDirection mainAxis; + let childWidth = {contents: zero}; + let childHeight = {contents: zero}; + let childWidthMeasureMode = {contents: CssMeasureModeUndefined}; + let childHeightMeasureMode = {contents: CssMeasureModeUndefined}; + if (isMainAxisRow && isStyleDimDefined child.contents CssFlexDirectionRow) { + child.contents.layout.computedFlexBasis = + fmaxf child.contents.style.width (getPaddingAndBorderAxis child.contents CssFlexDirectionRow) + } else if ( + not isMainAxisRow && isStyleDimDefined child.contents CssFlexDirectionColumn + ) { + child.contents.layout.computedFlexBasis = + fmaxf child.contents.style.height (getPaddingAndBorderAxis child.contents CssFlexDirectionColumn) + } else if ( + not (isUndefined child.contents.style.flexBasis) + ) { + if (isUndefined child.contents.layout.computedFlexBasis) { + child.contents.layout.computedFlexBasis = + fmaxf child.contents.style.flexBasis (getPaddingAndBorderAxis child.contents mainAxis) + } + } else { + childWidth.contents = cssUndefined; + childHeight.contents = cssUndefined; + childWidthMeasureMode.contents = CssMeasureModeUndefined; + childHeightMeasureMode.contents = CssMeasureModeUndefined; + if (isStyleDimDefined child.contents CssFlexDirectionRow) { + childWidth.contents = child.contents.style.width +. getMarginAxis child.contents CssFlexDirectionRow; + childWidthMeasureMode.contents = CssMeasureModeExactly + }; + + /** + * Why can't this just be inlined to .height !== cssUndefined. + */ + if (isStyleDimDefined child.contents CssFlexDirectionColumn) { + childHeight.contents = + child.contents.style.height +. getMarginAxis child.contents CssFlexDirectionColumn; + childHeightMeasureMode.contents = CssMeasureModeExactly + }; + if (not isMainAxisRow && node.style.overflow === Scroll || node.style.overflow !== Scroll) { + if (isUndefined childWidth.contents && not (isUndefined width)) { + childWidth.contents = width; + childWidthMeasureMode.contents = CssMeasureModeAtMost + } + }; + if (isMainAxisRow && node.style.overflow === Scroll || node.style.overflow !== Scroll) { + if (isUndefined childHeight.contents && not (isUndefined height)) { + childHeight.contents = height; + childHeightMeasureMode.contents = CssMeasureModeAtMost + } + }; + /* + * If child has no defined size in the cross axis and is set to + * stretch, set the cross axis to be measured exactly with the + * available inner width. + */ + if ( + not isMainAxisRow && + not (isUndefined width) && + not (isStyleDimDefined child.contents CssFlexDirectionRow) && + widthMode === CssMeasureModeExactly && getAlignItem node child.contents === CssAlignStretch + ) { + childWidth.contents = width; + childWidthMeasureMode.contents = CssMeasureModeExactly + }; + if ( + isMainAxisRow && + not (isUndefined height) && + not (isStyleDimDefined child.contents CssFlexDirectionColumn) && + heightMode === CssMeasureModeExactly && getAlignItem node child.contents === CssAlignStretch + ) { + childHeight.contents = height; + childHeightMeasureMode.contents = CssMeasureModeExactly + }; + let _ = + layoutNodeInternal + child.contents + childWidth.contents + childHeight.contents + direction + childWidthMeasureMode.contents + childHeightMeasureMode.contents + false + measureString; + child.contents.layout.computedFlexBasis = + fmaxf + (isMainAxisRow ? child.contents.layout.measuredWidth : child.contents.layout.measuredHeight) + (getPaddingAndBorderAxis child.contents mainAxis) + } +} +/** + * By default, mathematical operations are floating point. + */ +and layoutNodeImpl + ( + node, + availableWidth, + availableHeight, + parentDirection, + widthMeasureMode, + heightMeasureMode, + performLayout + ) => { + + /** START_GENERATED **/ + /* re_assert */ + /* (isUndefined availableWidth ? widthMeasureMode === CssMeasureModeUndefined : true) */ + /* "availableWidth is indefinite so widthMeasureMode must be CssMeasureModeUndefined"; */ + /* re_assert */ + /* (isUndefined availableHeight ? heightMeasureMode === CssMeasureModeUndefined : true) */ + /* "availableHeight is indefinite so heightMeasureMode must be CssMeasureModeUndefined"; */ + let paddingAndBorderAxisRow = getPaddingAndBorderAxis node CssFlexDirectionRow; + let paddingAndBorderAxisColumn = getPaddingAndBorderAxis node CssFlexDirectionColumn; + let marginAxisRow = getMarginAxis node CssFlexDirectionRow; + let marginAxisColumn = getMarginAxis node CssFlexDirectionColumn; + let direction = resolveDirection node parentDirection; + node.layout.direction = direction; + /* For content (text) nodes, determine the dimensions based on the text + contents. */ + if (node.measure !== dummyMeasure && node.childrenCount === 0) { + let innerWidth = availableWidth -. marginAxisRow -. paddingAndBorderAxisRow; + let innerHeight = availableHeight -. marginAxisColumn -. paddingAndBorderAxisColumn; + if (widthMeasureMode === CssMeasureModeExactly && heightMeasureMode === CssMeasureModeExactly) { + node.layout.measuredWidth = boundAxis node CssFlexDirectionRow (availableWidth -. marginAxisRow); + node.layout.measuredHeight = + boundAxis node CssFlexDirectionColumn (availableHeight -. marginAxisColumn) + } else if ( + not (isUndefined innerWidth) && innerWidth <= zero || + not (isUndefined innerHeight) && innerHeight <= zero + ) { + node.layout.measuredWidth = boundAxis node CssFlexDirectionRow zero; + node.layout.measuredHeight = boundAxis node CssFlexDirectionColumn zero + } else { + let measureDim = node.measure node innerWidth widthMeasureMode innerHeight heightMeasureMode; + node.layout.measuredWidth = + boundAxis + node + CssFlexDirectionRow + ( + widthMeasureMode === CssMeasureModeUndefined || widthMeasureMode === CssMeasureModeAtMost ? + measureDim.width +. paddingAndBorderAxisRow : availableWidth -. marginAxisRow + ); + node.layout.measuredHeight = + boundAxis + node + CssFlexDirectionColumn + ( + heightMeasureMode === CssMeasureModeUndefined || heightMeasureMode === CssMeasureModeAtMost ? + measureDim.height +. paddingAndBorderAxisColumn : availableHeight -. marginAxisColumn + ) + } + } else { + let childCount = Array.length node.children; + if (childCount === 0) { + node.layout.measuredWidth = + boundAxis + node + CssFlexDirectionRow + ( + widthMeasureMode === CssMeasureModeUndefined || widthMeasureMode === CssMeasureModeAtMost ? + paddingAndBorderAxisRow : availableWidth -. marginAxisRow + ); + node.layout.measuredHeight = + boundAxis + node + CssFlexDirectionColumn + ( + heightMeasureMode === CssMeasureModeUndefined || heightMeasureMode === CssMeasureModeAtMost ? + paddingAndBorderAxisColumn : availableHeight -. marginAxisColumn + ) + } else { + let shouldContinue = {contents: true}; + if (not performLayout) { + if ( + ( + ( + widthMeasureMode === CssMeasureModeAtMost && + not (isUndefined availableWidth) && availableWidth <= zero + ) && + heightMeasureMode === CssMeasureModeAtMost + ) && + not (isUndefined availableHeight) && availableHeight <= zero + ) { + node.layout.measuredWidth = boundAxis node CssFlexDirectionRow zero; + node.layout.measuredHeight = boundAxis node CssFlexDirectionColumn zero; + shouldContinue.contents = false + } else if ( + widthMeasureMode === CssMeasureModeAtMost && + not (isUndefined availableWidth) && availableWidth <= zero + ) { + node.layout.measuredWidth = boundAxis node CssFlexDirectionRow zero; + node.layout.measuredHeight = + boundAxis + node + CssFlexDirectionColumn + (isUndefined availableHeight ? zero : availableHeight -. marginAxisColumn); + shouldContinue.contents = false + } else if ( + heightMeasureMode === CssMeasureModeAtMost && + not (isUndefined availableHeight) && availableHeight <= zero + ) { + node.layout.measuredWidth = + boundAxis + node CssFlexDirectionRow (isUndefined availableWidth ? zero : availableWidth -. marginAxisRow); + node.layout.measuredHeight = boundAxis node CssFlexDirectionColumn zero; + shouldContinue.contents = false + } else if ( + widthMeasureMode === CssMeasureModeExactly && heightMeasureMode === CssMeasureModeExactly + ) { + node.layout.measuredWidth = boundAxis node CssFlexDirectionRow (availableWidth -. marginAxisRow); + node.layout.measuredHeight = + boundAxis node CssFlexDirectionColumn (availableHeight -. marginAxisColumn); + shouldContinue.contents = false + } + }; + if shouldContinue.contents { + let mainAxis = resolveAxis node.style.flexDirection direction; + let crossAxis = getCrossFlexDirection mainAxis direction; + let isMainAxisRow = isRowDirection mainAxis; + let justifyContent = node.style.justifyContent; + let isNodeFlexWrap = node.style.flexWrap === CssWrap; + let firstAbsoluteChild = {contents: theNullNode}; + let currentAbsoluteChild = {contents: theNullNode}; + let leadingPaddingAndBorderMain = getLeadingPaddingAndBorder node mainAxis; + let trailingPaddingAndBorderMain = getTrailingPaddingAndBorder node mainAxis; + let leadingPaddingAndBorderCross = getLeadingPaddingAndBorder node crossAxis; + let paddingAndBorderAxisMain = getPaddingAndBorderAxis node mainAxis; + let paddingAndBorderAxisCross = getPaddingAndBorderAxis node crossAxis; + let measureModeMainDim = isMainAxisRow ? widthMeasureMode : heightMeasureMode; + let measureModeCrossDim = isMainAxisRow ? heightMeasureMode : widthMeasureMode; + let availableInnerWidth = availableWidth -. marginAxisRow -. paddingAndBorderAxisRow; + let availableInnerHeight = availableHeight -. marginAxisColumn -. paddingAndBorderAxisColumn; + let availableInnerMainDim = isMainAxisRow ? availableInnerWidth : availableInnerHeight; + let availableInnerCrossDim = isMainAxisRow ? availableInnerHeight : availableInnerWidth; + let child = {contents: theNullNode}; + /* let i = 0; */ + /* STEP 3: DETERMINE FLEX BASIS FOR EACH ITEM */ + for i in 0 to (childCount - 1) { + child.contents = node.children.(i); + if performLayout { + let childDirection = resolveDirection child.contents direction; + setPosition child.contents childDirection + }; + if (child.contents.style.positionType === CssPositionAbsolute) { + if (firstAbsoluteChild.contents === theNullNode) { + firstAbsoluteChild.contents = child.contents + }; + if (currentAbsoluteChild.contents !== theNullNode) { + currentAbsoluteChild.contents.nextChild = child.contents + }; + currentAbsoluteChild.contents = child.contents; + child.contents.nextChild = theNullNode + } else { + computeChildFlexBasis + node + child + availableInnerWidth + widthMeasureMode + availableInnerHeight + heightMeasureMode + direction + } + }; + /* STEP 4: COLLECT FLEX ITEMS INTO FLEX LINES */ + let startOfLineIndex = {contents: 0}; + let endOfLineIndex = {contents: 0}; + let lineCount = {contents: 0}; + let totalLineCrossDim = {contents: zero}; + let maxLineMainDim = {contents: zero}; + while (endOfLineIndex.contents < childCount) { + let itemsOnLine = {contents: 0}; + let sizeConsumedOnCurrentLine = {contents: zero}; + let totalFlexGrowFactors = {contents: zero}; + let totalFlexShrinkScaledFactors = {contents: zero}; + let curIndex = {contents: startOfLineIndex.contents}; + let firstRelativeChild = {contents: theNullNode}; + let currentRelativeChild = {contents: theNullNode}; + let shouldContinue = {contents: true}; + while (curIndex.contents < childCount && shouldContinue.contents) { + child.contents = node.children.(curIndex.contents); + child.contents.lineIndex = lineCount.contents; + if (child.contents.style.positionType !== CssPositionAbsolute) { + let outerFlexBasis = + child.contents.layout.computedFlexBasis +. getMarginAxis child.contents mainAxis; + if ( + ( + sizeConsumedOnCurrentLine.contents +. outerFlexBasis > availableInnerMainDim && isNodeFlexWrap + ) && + itemsOnLine.contents > 0 + ) { + shouldContinue.contents = false + } else { + sizeConsumedOnCurrentLine.contents = sizeConsumedOnCurrentLine.contents +. outerFlexBasis; + itemsOnLine.contents = itemsOnLine.contents + 1; + if (isFlex child.contents) { + totalFlexGrowFactors.contents = + totalFlexGrowFactors.contents +. child.contents.style.flexGrow; + totalFlexShrinkScaledFactors.contents = + totalFlexShrinkScaledFactors.contents +. + -. child.contents.style.flexShrink *. child.contents.layout.computedFlexBasis + }; + if (firstRelativeChild.contents === theNullNode) { + firstRelativeChild.contents = child.contents + }; + if (currentRelativeChild.contents !== theNullNode) { + currentRelativeChild.contents.nextChild = child.contents + }; + currentRelativeChild.contents = child.contents; + child.contents.nextChild = theNullNode; + curIndex.contents = curIndex.contents + 1; + endOfLineIndex.contents = endOfLineIndex.contents + 1 + } + } else { + curIndex.contents = curIndex.contents + 1; + endOfLineIndex.contents = endOfLineIndex.contents + 1 + } + }; + let canSkipFlex = not performLayout && measureModeCrossDim === CssMeasureModeExactly; + let leadingMainDim = {contents: zero}; + let betweenMainDim = {contents: zero}; + let remainingFreeSpace = {contents: zero}; + if (not (isUndefined availableInnerMainDim)) { + remainingFreeSpace.contents = availableInnerMainDim -. sizeConsumedOnCurrentLine.contents + } else if ( + sizeConsumedOnCurrentLine.contents < zero + ) { + remainingFreeSpace.contents = -. sizeConsumedOnCurrentLine.contents + }; + let originalRemainingFreeSpace = remainingFreeSpace.contents; + let deltaFreeSpace = {contents: zero}; + if (not canSkipFlex) { + let childFlexBasis = {contents: zero}; + let flexShrinkScaledFactor = {contents: zero}; + let flexGrowFactor = {contents: zero}; + let baseMainSize = {contents: zero}; + let boundMainSize = {contents: zero}; + let deltaFlexShrinkScaledFactors = {contents: zero}; + let deltaFlexGrowFactors = {contents: zero}; + currentRelativeChild.contents = firstRelativeChild.contents; + while (currentRelativeChild.contents !== theNullNode) { + childFlexBasis.contents = currentRelativeChild.contents.layout.computedFlexBasis; + if (remainingFreeSpace.contents < zero) { + flexShrinkScaledFactor.contents = + -. currentRelativeChild.contents.style.flexShrink *. childFlexBasis.contents; + if (flexShrinkScaledFactor.contents != zero) { + baseMainSize.contents = + childFlexBasis.contents +. + /* + * Important to first scale, then divide - to support fixed + * point encoding. + */ + flexShrinkScaledFactor.contents *. remainingFreeSpace.contents /. + totalFlexShrinkScaledFactors.contents; + boundMainSize.contents = + boundAxis currentRelativeChild.contents mainAxis baseMainSize.contents; + if (baseMainSize.contents != boundMainSize.contents) { + deltaFreeSpace.contents = + deltaFreeSpace.contents -. (boundMainSize.contents -. childFlexBasis.contents); + deltaFlexShrinkScaledFactors.contents = + deltaFlexShrinkScaledFactors.contents -. flexShrinkScaledFactor.contents + } + } + } else if ( + remainingFreeSpace.contents > zero + ) { + flexGrowFactor.contents = currentRelativeChild.contents.style.flexGrow; + if (flexGrowFactor.contents != zero) { + baseMainSize.contents = + childFlexBasis.contents +. + /* + * Important to first scale, then divide - to support fixed + * point encoding. + */ + flexGrowFactor.contents *. remainingFreeSpace.contents /. totalFlexGrowFactors.contents; + boundMainSize.contents = + boundAxis currentRelativeChild.contents mainAxis baseMainSize.contents; + if (baseMainSize.contents != boundMainSize.contents) { + deltaFreeSpace.contents = + deltaFreeSpace.contents -. (boundMainSize.contents -. childFlexBasis.contents); + deltaFlexGrowFactors.contents = deltaFlexGrowFactors.contents -. flexGrowFactor.contents + } + } + }; + currentRelativeChild.contents = currentRelativeChild.contents.nextChild + }; + totalFlexShrinkScaledFactors.contents = + totalFlexShrinkScaledFactors.contents +. deltaFlexShrinkScaledFactors.contents; + totalFlexGrowFactors.contents = totalFlexGrowFactors.contents +. deltaFlexGrowFactors.contents; + remainingFreeSpace.contents = remainingFreeSpace.contents +. deltaFreeSpace.contents; + deltaFreeSpace.contents = zero; + currentRelativeChild.contents = firstRelativeChild.contents; + while (currentRelativeChild.contents !== theNullNode) { + childFlexBasis.contents = currentRelativeChild.contents.layout.computedFlexBasis; + let updatedMainSize = {contents: childFlexBasis.contents}; + if (remainingFreeSpace.contents < zero) { + flexShrinkScaledFactor.contents = + -. currentRelativeChild.contents.style.flexShrink *. childFlexBasis.contents; + if (flexShrinkScaledFactor.contents != zero) { + updatedMainSize.contents = + boundAxis + currentRelativeChild.contents + mainAxis + ( + childFlexBasis.contents +. + /* + * Important to first scale, then divide - to support + * fixed point encoding. + */ + flexShrinkScaledFactor.contents *. remainingFreeSpace.contents /. + totalFlexShrinkScaledFactors.contents + ) + } + } else if ( + remainingFreeSpace.contents > zero + ) { + flexGrowFactor.contents = currentRelativeChild.contents.style.flexGrow; + if (flexGrowFactor.contents != zero) { + updatedMainSize.contents = + boundAxis + currentRelativeChild.contents + mainAxis + ( + childFlexBasis.contents +. + /* + * Important to first scale, then divide - to support + * fixed point encoding. + */ + flexGrowFactor.contents *. remainingFreeSpace.contents /. + totalFlexGrowFactors.contents + ) + } + }; + deltaFreeSpace.contents = + deltaFreeSpace.contents -. (updatedMainSize.contents -. childFlexBasis.contents); + let childWidth = {contents: zero}; + let childHeight = {contents: zero}; + let childWidthMeasureMode = {contents: CssMeasureModeUndefined}; + let childHeightMeasureMode = {contents: CssMeasureModeUndefined}; + if isMainAxisRow { + childWidth.contents = + updatedMainSize.contents +. + getMarginAxis currentRelativeChild.contents CssFlexDirectionRow; + childWidthMeasureMode.contents = CssMeasureModeExactly; + if ( + not (isUndefined availableInnerCrossDim) && + not (isStyleDimDefined currentRelativeChild.contents CssFlexDirectionColumn) && + heightMeasureMode === CssMeasureModeExactly && + getAlignItem node currentRelativeChild.contents === CssAlignStretch + ) { + childHeight.contents = availableInnerCrossDim; + childHeightMeasureMode.contents = CssMeasureModeExactly + } else if ( + not (isStyleDimDefined currentRelativeChild.contents CssFlexDirectionColumn) + ) { + childHeight.contents = availableInnerCrossDim; + childHeightMeasureMode.contents = + isUndefined childHeight.contents ? CssMeasureModeUndefined : CssMeasureModeAtMost + } else { + childHeight.contents = + currentRelativeChild.contents.style.height +. + getMarginAxis currentRelativeChild.contents CssFlexDirectionColumn; + childHeightMeasureMode.contents = CssMeasureModeExactly + } + } else { + childHeight.contents = + updatedMainSize.contents +. + getMarginAxis currentRelativeChild.contents CssFlexDirectionColumn; + childHeightMeasureMode.contents = CssMeasureModeExactly; + if ( + not (isUndefined availableInnerCrossDim) && + not (isStyleDimDefined currentRelativeChild.contents CssFlexDirectionRow) && + widthMeasureMode === CssMeasureModeExactly && + getAlignItem node currentRelativeChild.contents === CssAlignStretch + ) { + childWidth.contents = availableInnerCrossDim; + childWidthMeasureMode.contents = CssMeasureModeExactly + } else if ( + not (isStyleDimDefined currentRelativeChild.contents CssFlexDirectionRow) + ) { + childWidth.contents = availableInnerCrossDim; + childWidthMeasureMode.contents = + isUndefined childWidth.contents ? CssMeasureModeUndefined : CssMeasureModeAtMost + } else { + childWidth.contents = + currentRelativeChild.contents.style.width +. + getMarginAxis currentRelativeChild.contents CssFlexDirectionRow; + childWidthMeasureMode.contents = CssMeasureModeExactly + } + }; + let requiresStretchLayout = + not (isStyleDimDefined currentRelativeChild.contents crossAxis) && + getAlignItem node currentRelativeChild.contents === CssAlignStretch; + let _ = + layoutNodeInternal + currentRelativeChild.contents + childWidth.contents + childHeight.contents + direction + childWidthMeasureMode.contents + childHeightMeasureMode.contents + (performLayout && not requiresStretchLayout) + flexString; + currentRelativeChild.contents = currentRelativeChild.contents.nextChild + } + }; + remainingFreeSpace.contents = originalRemainingFreeSpace +. deltaFreeSpace.contents; + /* If we are using "at most" rules in the main axis. Calculate the remaining space when + constraint by the min size defined for the main axis. */ + if (measureModeMainDim === CssMeasureModeAtMost) { + let minDim = styleMinDimensionForAxis node mainAxis; + if (not (isUndefined minDim) && minDim >= 0) { + remainingFreeSpace.contents = + fmaxf 0 (minDim - (availableInnerMainDim -. remainingFreeSpace.contents)) + } else { + remainingFreeSpace.contents = zero + } + }; + switch justifyContent { + | CssJustifyCenter => leadingMainDim.contents = divideScalarByInt remainingFreeSpace.contents 2 + | CssJustifyFlexEnd => leadingMainDim.contents = remainingFreeSpace.contents + | CssJustifySpaceBetween => + if (itemsOnLine.contents > 1) { + betweenMainDim.contents = + divideScalarByInt (fmaxf remainingFreeSpace.contents zero) (itemsOnLine.contents - 1) + } else { + betweenMainDim.contents = zero + } + | CssJustifySpaceAround => + betweenMainDim.contents = divideScalarByInt remainingFreeSpace.contents itemsOnLine.contents; + leadingMainDim.contents = divideScalarByInt betweenMainDim.contents 2 + | CssJustifyFlexStart => () + }; + let mainDim = {contents: leadingPaddingAndBorderMain +. leadingMainDim.contents}; + let crossDim = {contents: zero}; + for i in startOfLineIndex.contents to (endOfLineIndex.contents - 1) { + child.contents = node.children.(i); + if ( + child.contents.style.positionType === CssPositionAbsolute && + isLeadingPosDefinedWithFallback child.contents mainAxis + ) { + if performLayout { + setLayoutLeadingPositionForAxis + child.contents + mainAxis + ( + getLeadingPositionWithFallback child.contents mainAxis +. getLeadingBorder node mainAxis +. + getLeadingMargin child.contents mainAxis + ) + } + } else { + if performLayout { + setLayoutLeadingPositionForAxis + child.contents + mainAxis + (layoutPosPositionForAxis child.contents mainAxis +. mainDim.contents) + }; + if (child.contents.style.positionType === CssPositionRelative) { + if canSkipFlex { + mainDim.contents = + mainDim.contents +. betweenMainDim.contents +. getMarginAxis child.contents mainAxis +. + child.contents.layout.computedFlexBasis; + crossDim.contents = availableInnerCrossDim + } else { + mainDim.contents = + mainDim.contents +. betweenMainDim.contents +. getDimWithMargin child.contents mainAxis; + crossDim.contents = fmaxf crossDim.contents (getDimWithMargin child.contents crossAxis) + } + } + } + }; + mainDim.contents = mainDim.contents +. trailingPaddingAndBorderMain; + let containerCrossAxis = {contents: availableInnerCrossDim}; + if ( + measureModeCrossDim === CssMeasureModeUndefined || measureModeCrossDim === CssMeasureModeAtMost + ) { + containerCrossAxis.contents = + boundAxis node crossAxis (crossDim.contents +. paddingAndBorderAxisCross) -. paddingAndBorderAxisCross; + if (measureModeCrossDim === CssMeasureModeAtMost) { + containerCrossAxis.contents = fminf containerCrossAxis.contents availableInnerCrossDim + } + }; + if (not isNodeFlexWrap && measureModeCrossDim === CssMeasureModeExactly) { + crossDim.contents = availableInnerCrossDim + }; + crossDim.contents = + boundAxis node crossAxis (crossDim.contents +. paddingAndBorderAxisCross) -. paddingAndBorderAxisCross; + /* + * STEP 7: CROSS-AXIS ALIGNMENT We can skip child alignment if we're + * just measuring the container. + */ + if performLayout { + for i in startOfLineIndex.contents to (endOfLineIndex.contents - 1) { + child.contents = node.children.(i); + if (child.contents.style.positionType === CssPositionAbsolute) { + if (isLeadingPosDefinedWithFallback child.contents crossAxis) { + setLayoutLeadingPositionForAxis + child.contents + crossAxis + ( + getLeadingPositionWithFallback child.contents crossAxis +. + getLeadingBorder node crossAxis +. + getLeadingMargin child.contents crossAxis + ) + } else { + setLayoutLeadingPositionForAxis + child.contents + crossAxis + (leadingPaddingAndBorderCross +. getLeadingMargin child.contents crossAxis) + } + } else { + let leadingCrossDim = {contents: leadingPaddingAndBorderCross}; + let alignItem = getAlignItem node child.contents; + if (alignItem === CssAlignStretch) { + let childWidth = {contents: zero}; + let childHeight = {contents: zero}; + let childWidthMeasureMode = {contents: CssMeasureModeUndefined}; + let childHeightMeasureMode = {contents: CssMeasureModeUndefined}; + childWidth.contents = + child.contents.layout.measuredWidth +. getMarginAxis child.contents CssFlexDirectionRow; + childHeight.contents = + child.contents.layout.measuredHeight +. + getMarginAxis child.contents CssFlexDirectionColumn; + let isCrossSizeDefinite = {contents: false}; + if isMainAxisRow { + isCrossSizeDefinite.contents = isStyleDimDefined child.contents CssFlexDirectionColumn; + childHeight.contents = crossDim.contents + } else { + isCrossSizeDefinite.contents = isStyleDimDefined child.contents CssFlexDirectionRow; + childWidth.contents = crossDim.contents + }; + if (not isCrossSizeDefinite.contents) { + childWidthMeasureMode.contents = + isUndefined childWidth.contents ? CssMeasureModeUndefined : CssMeasureModeExactly; + childHeightMeasureMode.contents = + isUndefined childHeight.contents ? CssMeasureModeUndefined : CssMeasureModeExactly; + let _ = + layoutNodeInternal + child.contents + childWidth.contents + childHeight.contents + direction + childWidthMeasureMode.contents + childHeightMeasureMode.contents + true + stretchString; + () + } + } else if ( + alignItem !== CssAlignFlexStart + ) { + let remainingCrossDim = + containerCrossAxis.contents -. getDimWithMargin child.contents crossAxis; + if (alignItem === CssAlignCenter) { + leadingCrossDim.contents = + leadingCrossDim.contents +. divideScalarByInt remainingCrossDim 2 + } else { + leadingCrossDim.contents = leadingCrossDim.contents +. remainingCrossDim + } + }; + setLayoutLeadingPositionForAxis + child.contents + crossAxis + ( + layoutPosPositionForAxis child.contents crossAxis +. totalLineCrossDim.contents +. + leadingCrossDim.contents + ) + } + } + }; + totalLineCrossDim.contents = totalLineCrossDim.contents +. crossDim.contents; + maxLineMainDim.contents = fmaxf maxLineMainDim.contents mainDim.contents; + lineCount.contents = lineCount.contents + 1; + startOfLineIndex.contents = endOfLineIndex.contents + }; + if (lineCount.contents > 1 && performLayout && not (isUndefined availableInnerCrossDim)) { + let remainingAlignContentDim = availableInnerCrossDim -. totalLineCrossDim.contents; + let crossDimLead = {contents: zero}; + let currentLead = {contents: leadingPaddingAndBorderCross}; + let alignContent = node.style.alignContent; + if (alignContent === CssAlignFlexEnd) { + currentLead.contents = currentLead.contents +. remainingAlignContentDim + } else if ( + alignContent === CssAlignCenter + ) { + currentLead.contents = currentLead.contents +. divideScalarByInt remainingAlignContentDim 2 + } else if ( + alignContent === CssAlignStretch + ) { + if (availableInnerCrossDim > totalLineCrossDim.contents) { + crossDimLead.contents = divideScalarByInt remainingAlignContentDim lineCount.contents + } + }; + let endIndex = {contents: 0}; + for i in 0 to (lineCount.contents - 1) { + let startIndex = endIndex.contents; + let j = {contents: startIndex}; + let lineHeight = {contents: zero}; + let shouldContinue = {contents: false}; + while (j.contents < childCount && shouldContinue.contents) { + child.contents = node.children.(j.contents); + if (child.contents.style.positionType === CssPositionRelative) { + if (child.contents.lineIndex !== i) { + shouldContinue.contents = false + } else if ( + isLayoutDimDefined child.contents crossAxis + ) { + lineHeight.contents = + fmaxf + lineHeight.contents + ( + layoutMeasuredDimensionForAxis child.contents crossAxis +. + getMarginAxis child.contents crossAxis + ) + } + }; + j.contents = j.contents + 1 + }; + endIndex.contents = j.contents; + lineHeight.contents = lineHeight.contents +. crossDimLead.contents; + if performLayout { + for j in startIndex to (endIndex.contents - 1) { + child.contents = node.children.(j); + if (child.contents.style.positionType === CssPositionRelative) { + switch (getAlignItem node child.contents) { + | CssAlignFlexStart => + setLayoutLeadingPositionForAxis + child.contents + crossAxis + (currentLead.contents +. getLeadingMargin child.contents crossAxis) + | CssAlignFlexEnd => + setLayoutLeadingPositionForAxis + child.contents + crossAxis + ( + currentLead.contents +. lineHeight.contents -. + getTrailingMargin child.contents crossAxis -. + layoutMeasuredDimensionForAxis child.contents crossAxis + ) + | CssAlignCenter => + let childHeight = layoutMeasuredDimensionForAxis child.contents crossAxis; + setLayoutLeadingPositionForAxis + child.contents + crossAxis + (currentLead.contents +. divideScalarByInt (lineHeight.contents -. childHeight) 2) + | CssAlignStretch => + setLayoutLeadingPositionForAxis + child.contents + crossAxis + (currentLead.contents +. getLeadingMargin child.contents crossAxis) + | CssAlignAuto => raise (Invalid_argument "getAlignItem should never return auto") + } + } + } + }; + currentLead.contents = currentLead.contents +. lineHeight.contents + } + }; + /* STEP 9: COMPUTING FINAL DIMENSIONS */ + node.layout.measuredWidth = boundAxis node CssFlexDirectionRow (availableWidth -. marginAxisRow); + node.layout.measuredHeight = + boundAxis node CssFlexDirectionColumn (availableHeight -. marginAxisColumn); + /* If the user didn't specify a width or height for the node, set the + * dimensions based on the children. */ + if (measureModeMainDim === CssMeasureModeUndefined) { + setLayoutMeasuredDimensionForAxis node mainAxis (boundAxis node mainAxis maxLineMainDim.contents) + } else if ( + measureModeMainDim === CssMeasureModeAtMost + ) { + setLayoutMeasuredDimensionForAxis + node + mainAxis + ( + fmaxf + ( + fminf + (availableInnerMainDim +. paddingAndBorderAxisMain) + (boundAxisWithinMinAndMax node mainAxis maxLineMainDim.contents) + ) + paddingAndBorderAxisMain + ) + }; + if (measureModeCrossDim === CssMeasureModeUndefined) { + setLayoutMeasuredDimensionForAxis + node + crossAxis + (boundAxis node crossAxis (totalLineCrossDim.contents +. paddingAndBorderAxisCross)) + } else if ( + measureModeCrossDim === CssMeasureModeAtMost + ) { + setLayoutMeasuredDimensionForAxis + node + crossAxis + ( + fmaxf + ( + fminf + (availableInnerCrossDim +. paddingAndBorderAxisCross) + ( + boundAxisWithinMinAndMax + node crossAxis (totalLineCrossDim.contents +. paddingAndBorderAxisCross) + ) + ) + paddingAndBorderAxisCross + ) + }; + currentAbsoluteChild.contents = firstAbsoluteChild.contents; + while (currentAbsoluteChild.contents !== theNullNode) { + if performLayout { + let childWidth = {contents: cssUndefined}; + let childHeight = {contents: cssUndefined}; + let childWidthMeasureMode = {contents: CssMeasureModeUndefined}; + let childHeightMeasureMode = {contents: CssMeasureModeUndefined}; + if (isStyleDimDefined currentAbsoluteChild.contents CssFlexDirectionRow) { + childWidth.contents = + currentAbsoluteChild.contents.style.width +. + getMarginAxis currentAbsoluteChild.contents CssFlexDirectionRow + } else if ( + isLeadingPosDefinedWithFallback currentAbsoluteChild.contents CssFlexDirectionRow && + isTrailingPosDefinedWithFallback currentAbsoluteChild.contents CssFlexDirectionRow + ) { + childWidth.contents = + node.layout.measuredWidth -. ( + getLeadingBorder node CssFlexDirectionRow +. getTrailingBorder node CssFlexDirectionRow + ) -. ( + getLeadingPositionWithFallback currentAbsoluteChild.contents CssFlexDirectionRow +. + getTrailingPositionWithFallback currentAbsoluteChild.contents CssFlexDirectionRow + ); + childWidth.contents = + boundAxis currentAbsoluteChild.contents CssFlexDirectionRow childWidth.contents + }; + if (isStyleDimDefined currentAbsoluteChild.contents CssFlexDirectionColumn) { + childHeight.contents = + currentAbsoluteChild.contents.style.height +. + getMarginAxis currentAbsoluteChild.contents CssFlexDirectionColumn + } else if ( + /* If the child doesn't have a specified height, compute the height based on the top/bottom offsets if they're defined. */ + isLeadingPosDefinedWithFallback currentAbsoluteChild.contents CssFlexDirectionColumn && + isTrailingPosDefinedWithFallback currentAbsoluteChild.contents CssFlexDirectionColumn + ) { + childHeight.contents = + node.layout.measuredHeight -. ( + getLeadingBorder node CssFlexDirectionColumn +. + getTrailingBorder node CssFlexDirectionColumn + ) -. ( + getLeadingPositionWithFallback currentAbsoluteChild.contents CssFlexDirectionColumn +. + getTrailingPositionWithFallback currentAbsoluteChild.contents CssFlexDirectionColumn + ); + childHeight.contents = + boundAxis currentAbsoluteChild.contents CssFlexDirectionColumn childHeight.contents + }; + if (isUndefined childWidth.contents || isUndefined childHeight.contents) { + childWidthMeasureMode.contents = + isUndefined childWidth.contents ? CssMeasureModeUndefined : CssMeasureModeExactly; + childHeightMeasureMode.contents = + isUndefined childHeight.contents ? CssMeasureModeUndefined : CssMeasureModeExactly; + /* + * According to the spec, if the main size is not definite and the + * child's inline axis is parallel to the main axis (i.e. it's + * horizontal), the child should be sized using "UNDEFINED" in + * the main size. Otherwise use "AT_MOST" in the cross axis. + */ + if ( + (not isMainAxisRow && isUndefined childWidth.contents) && + not (isUndefined availableInnerWidth) + ) { + childWidth.contents = availableInnerWidth; + childWidthMeasureMode.contents = CssMeasureModeAtMost + }; + /* + * If child has no defined size in the cross axis and is set to stretch, set the cross + * axis to be measured exactly with the available inner width + */ + let _ = + layoutNodeInternal + currentAbsoluteChild.contents + childWidth.contents + childHeight.contents + direction + childWidthMeasureMode.contents + childHeightMeasureMode.contents + false + absMeasureString; + childWidth.contents = + currentAbsoluteChild.contents.layout.measuredWidth +. + getMarginAxis currentAbsoluteChild.contents CssFlexDirectionRow; + childHeight.contents = + currentAbsoluteChild.contents.layout.measuredHeight +. + getMarginAxis currentAbsoluteChild.contents CssFlexDirectionColumn + }; + let _ = + layoutNodeInternal + currentAbsoluteChild.contents + childWidth.contents + childHeight.contents + direction + CssMeasureModeExactly + CssMeasureModeExactly + true + absLayoutString; + if ( + isTrailingPosDefinedWithFallback currentAbsoluteChild.contents mainAxis && + not (isLeadingPosDefinedWithFallback currentAbsoluteChild.contents mainAxis) + ) { + setLayoutLeadingPositionForAxis + currentAbsoluteChild.contents + mainAxis + ( + layoutMeasuredDimensionForAxis node mainAxis -. + layoutMeasuredDimensionForAxis currentAbsoluteChild.contents mainAxis -. + getTrailingPositionWithFallback currentAbsoluteChild.contents mainAxis + ) + }; + if ( + isTrailingPosDefinedWithFallback currentAbsoluteChild.contents crossAxis && + not (isLeadingPosDefinedWithFallback currentAbsoluteChild.contents crossAxis) + ) { + setLayoutLeadingPositionForAxis + currentAbsoluteChild.contents + crossAxis + ( + layoutMeasuredDimensionForAxis node crossAxis -. + layoutMeasuredDimensionForAxis currentAbsoluteChild.contents crossAxis -. + getTrailingPositionWithFallback currentAbsoluteChild.contents crossAxis + ) + } + }; + currentAbsoluteChild.contents = currentAbsoluteChild.contents.nextChild + }; + /* STEP 11: SETTING TRAILING POSITIONS FOR CHILDREN */ + if performLayout { + let needsMainTrailingPos = + mainAxis == CssFlexDirectionRowReverse || mainAxis == CssFlexDirectionColumnReverse; + let needsCrossTrailingPos = + crossAxis == CssFlexDirectionRowReverse || crossAxis == CssFlexDirectionColumnReverse; + /* Set trailing position if necessary. */ + if (needsMainTrailingPos || needsCrossTrailingPos) { + for i in 0 to (childCount - 1) { + let child = node.children.(i); + if needsMainTrailingPos { + setTrailingPosition node child mainAxis + }; + if needsCrossTrailingPos { + setTrailingPosition node child crossAxis + } + } + } + } + } + } + } + /** END_GENERATED **/ +}; + +let layoutNode node availableWidth availableHeight parentDirection => { + /* Increment the generation count. This will force the recursive routine to visit*/ + /* all dirty nodes at least once. Subsequent visits will be skipped if the input*/ + /* parameters don't change.*/ + gCurrentGenerationCount.contents = gCurrentGenerationCount.contents + 1; + /* If the caller didn't specify a height/width, use the dimensions*/ + /* specified in the style.*/ + let (availableWidth, widthMeasureMode) = + if (not (isUndefined availableWidth)) { + (availableWidth, CssMeasureModeExactly) + } else if ( + isStyleDimDefined node CssFlexDirectionRow + ) { + (node.style.width +. getMarginAxis node CssFlexDirectionRow, CssMeasureModeExactly) + } else if ( + node.style.maxWidth >= zero + ) { + (node.style.maxWidth, CssMeasureModeAtMost) + } else { + (availableWidth, CssMeasureModeUndefined) + }; + let (availableHeight, heightMeasureMode) = + if (not (isUndefined availableHeight)) { + (availableHeight, CssMeasureModeExactly) + } else if ( + isStyleDimDefined node CssFlexDirectionColumn + ) { + (node.style.height +. getMarginAxis node CssFlexDirectionColumn, CssMeasureModeExactly) + } else if ( + node.style.maxHeight >= zero + ) { + (node.style.maxHeight, CssMeasureModeAtMost) + } else { + (availableHeight, CssMeasureModeUndefined) + }; + if ( + layoutNodeInternal + node + availableWidth + availableHeight + parentDirection + widthMeasureMode + heightMeasureMode + true + initialString + ) { + setPosition node node.layout.direction; + if gPrintTree.contents { + LayoutPrint.printCssNode (node, {printLayout: true, printChildren: true, printStyle: true}) + } + } +}; \ No newline at end of file diff --git a/samples/Reason/Machine.re b/samples/Reason/Machine.re new file mode 100644 index 00000000..11cb5743 --- /dev/null +++ b/samples/Reason/Machine.re @@ -0,0 +1,344 @@ +open Format; + +let module Endo = { + type t 'a = 'a => 'a; +}; + +let module Syntax = { + let module Var = { + type t = int; + }; + let module Term = { + type t = + | App t t + | Lam t + | Var Var.t + ; + }; + let module Sub = { + type t 'a = + | Cmp (t 'a) (t 'a) + | Dot 'a (t 'a) + | Id + | Shift + ; + + let map f sgm => { + let rec go = fun + | Cmp sgm0 sgm1 => Cmp (go sgm0) (go sgm1) + | Dot a sgm => Dot (f a) (go sgm) + | Id => Id + | Shift => Shift + ; + go sgm; + }; + + let rec apply sgm e => + switch (sgm, e) { + | (sgm, Term.App e0 e1) => Term.App (apply sgm e0) (apply sgm e1) + | (sgm, Term.Lam e) => Term.Lam (apply (Dot (Term.Var 0) (Cmp sgm Shift)) e) + | (Dot e _, Term.Var 0) => e + | (Dot _ sgm, Term.Var i) => apply sgm (Term.Var (i - 1)) + | (Id, Term.Var i) => Term.Var i + | (Shift, Term.Var i) => Term.Var (i + 1) + | (Cmp rho sgm, e) => apply sgm (apply rho e) + }; + }; +}; + +let module Zip = { + open Syntax; + type t 'a = + | App0 (t 'a) 'a + | App1 'a (t 'a) + | Halt + | Lam (t 'a) + ; + + let map f sgm => { + let rec go = fun + | App0 zip e1 => App0 (go zip) (f e1) + | App1 e0 zip => App1 (f e0) (go zip) + | Halt => Halt + | Lam zip => Lam (go zip) + ; + go sgm; + }; + + let rec apply zip acc => switch zip { + | App0 zip e1 => apply zip (Term.App acc e1) + | App1 e0 zip => apply zip (Term.App e0 acc) + | Halt => acc + | Lam zip => apply zip (Term.Lam acc) + }; +}; + +let module Clo = { + open Syntax; + type t = + | Clo Term.t (Sub.t t); + let rec from (Clo term sgm) => Sub.apply (Sub.map from sgm) term; +}; + +let module Pretty = { + let module Delim = { + type t = string; + let pp prev next fmt token => if (prev < next) { fprintf fmt "%s" token }; + }; + let module Prec = { + type t = int; + open Syntax.Term; + let calc = fun + | App _ _ => 1 + | Lam _ => 2 + | Var _ => 0 + ; + }; + let module Name = { + type t = string; + + let suffix = { + let script = fun + | 0 => "₀" + | 1 => "₁" + | 2 => "₂" + | 3 => "₃" + | 4 => "₄" + | 5 => "₅" + | 6 => "₆" + | 7 => "₇" + | 8 => "₈" + | 9 => "₉" + | _ => failwith "bad subscript"; + let rec go acc => fun + | 0 => acc + | n => go (script (n mod 10) ^ acc) (n / 10); + go "" + }; + + let gen = { + let offset = 97; + let width = 26; + fun () i => { + let code = i mod width + offset; + let char = Char.chr code; + let prime = i / width; + let suffix = suffix prime; + let name = Char.escaped char ^ suffix; + Some name; + } + }; + }; + + let module Env = { + type t = { + used: list Name.t, + rest: Stream.t Name.t, + }; + let mk () => { + let used = []; + let rest = Stream.from @@ Name.gen (); + { used, rest }; + }; + }; + + type printer 'a = Env.t => Prec.t => formatter => 'a => unit; + + let module Term = { + open Syntax.Term; + let rec pp ({ Env.used: used, rest } as env) prev fmt e => { + let next = Prec.calc e; + switch e { + | App e0 e1 => + fprintf fmt "@[%a%a@ %a%a@]" + (Delim.pp prev next) "(" + (pp env 1) e0 + (pp env 0) e1 + (Delim.pp prev next) ")" + | Lam e => + let name = Stream.next rest; + let env = { ...env, Env.used: [name, ...used] }; + fprintf fmt "%aλ%a.%a%a" + (Delim.pp prev next) "(" + (pp_print_string) name + (pp env next) e + (Delim.pp prev next) ")" + | Var index => + fprintf fmt "%s" @@ try (List.nth used index) { + | _ => "#" ^ string_of_int index + } + } + }; + }; + + let module Sub = { + open Syntax.Sub; + let rec pp pp_elem env prev fmt => fun + | Cmp sgm1 sgm0 => + fprintf fmt "@[%a;@ %a@]" + (pp pp_elem env prev) sgm1 + (pp pp_elem env prev) sgm0 + | Dot e sgm => + fprintf fmt "@[%a@ ·@ %a@]" + (pp_elem env prev) e + (pp pp_elem env prev) sgm + | Id => + fprintf fmt "ι" + | Shift => + fprintf fmt "↑" + ; + }; + + let module Clo = { + let rec pp env prev fmt (Clo.Clo e sgm) => { + let next = Prec.calc e; + fprintf fmt "@[%a%a%a[%a]@]" + (Delim.pp prev next) "(" + (Term.pp env next) e + (Delim.pp prev next) ")" + (Sub.pp pp env next) sgm + }; + }; + + let module Zip = { + open Zip; + let rec pp pp_elem env prev fmt => fun + | App0 zip elem => + fprintf fmt "inl@[⟨@,%a@,%a⟩@]" + (pp pp_elem env prev) zip + (pp_elem env prev) elem + | App1 elem zip => + fprintf fmt "inr@[⟨@,%a@,%a⟩@]" + (pp_elem env prev) elem + (pp pp_elem env prev) zip + | Halt => + fprintf fmt "halt" + | Lam zip => + fprintf fmt "lam@[⟨@,%a⟩@]" + (pp pp_elem env prev) zip + ; + }; +}; + +let module Machine = { + type t = { + clo: Clo.t, + ctx: Zip.t Clo.t, + }; + + let into e => { + open Clo; + open Syntax.Sub; + let clo = Clo e Id; + let ctx = Zip.Halt; + { clo, ctx } + }; + + let from { clo, ctx } => Zip.apply (Zip.map Clo.from ctx) (Clo.from clo); + + let pp fmt rule state => { + fprintf fmt "@[ctx ::@[@,%a@]@,clo ::@[@,%a@]@,rule ::@[@,%a@]@,term ::@[@,%a@]@]@." + (Pretty.Zip.pp Pretty.Clo.pp (Pretty.Env.mk ()) 2) state.ctx + (Pretty.Clo.pp (Pretty.Env.mk ()) 2) state.clo + (pp_print_string) rule + (Pretty.Term.pp (Pretty.Env.mk ()) 2) (from state) + }; + + let halted state => { + open Clo; + open Syntax.Sub; + open Syntax.Term; + switch state { + | { clo: Clo (Var _) Id, _ } => true + | _ => false + } [@warning "-4"]; + }; + + let step state => { + open Clo; + open Syntax.Sub; + open Syntax.Term; + let rule = ref ""; + let state = switch state { + /* left */ + | { clo: Clo (App e0 e1) sgm, ctx } => + let clo = Clo e0 sgm; + let ctx = Zip.App0 ctx (Clo e1 sgm); + rule := "LEFT"; + { clo, ctx }; + /* beta */ + | { clo: Clo (Lam e) sgm, ctx: Zip.App0 ctx c0 } => + let clo = Clo e (Cmp (Dot c0 sgm) Id); + rule := "BETA"; + { clo, ctx }; + /* lambda */ + | { clo: Clo (Lam e) sgm, ctx } => + let clo = Clo e (Cmp (Dot (Clo (Var 0) Id) (Cmp sgm Shift)) Id); + let ctx = Zip.Lam ctx; + rule := "LAMBDA"; + { clo, ctx }; + /* associate */ + | { clo: Clo (Var n) (Cmp (Cmp pi rho) sgm), ctx } => + let clo = Clo (Var n) (Cmp pi (Cmp rho sgm)); + rule := "ASSOCIATE"; + { clo, ctx }; + /* head */ + | { clo: Clo (Var 0) (Cmp (Dot (Clo e pi) _) sgm), ctx } => + let clo = Clo e (Cmp pi sgm); + rule := "HEAD"; + { clo, ctx }; + /* tail */ + | { clo: Clo (Var n) (Cmp (Dot (Clo _ _) rho) sgm), ctx } => + let clo = Clo (Var (n - 1)) (Cmp rho sgm); + rule := "TAIL"; + { clo, ctx }; + /* shift */ + | { clo: Clo (Var n) (Cmp Shift sgm), ctx } => + let clo = Clo (Var (n + 1)) sgm; + rule := "SHIFT"; + { clo, ctx }; + /* id */ + | { clo: Clo (Var n) (Cmp Id sgm), ctx } => + let clo = Clo (Var n) sgm; + rule := "ID"; + { clo, ctx }; + | _ => + pp std_formatter !rule state; + failwith "bad state"; + } [@warning "-4"]; + pp std_formatter !rule state; + state; + }; + + let norm e => { + let count = ref 0; + let state = ref (into e); + while (not (halted !state)) { + fprintf std_formatter "@\n--- step[%d] ---@\n" !count; + incr count; + state := step !state; + }; + from !state; + }; +}; + +let module Test = { + open Syntax.Term; + let l e => Lam e; + let ( *@ ) e0 e1 => App e0 e1; + let ff = l (l (Var 1)); + let tt = l (l (Var 0)); + let zero = l (l (Var 1)); + let succ = l (l (l (Var 0 *@ Var 2))); + let one = succ *@ zero; + let two = succ *@ one; + let three = succ *@ two; + let const = l (l (Var 1)); + let fix = l (l (Var 1 *@ (Var 0 *@ Var 0)) *@ l (Var 1 *@ (Var 0 *@ Var 0))); + let add = fix *@ l (l (l (Var 1 *@ Var 0 *@ l (succ *@ Var 3 *@ Var 0 *@ Var 1)))); + let init = l (l (Var 0) *@ l (l (Var 1))); +}; + +let module Run = { + let go () => Machine.norm Test.init; +}; \ No newline at end of file diff --git a/samples/Reason/SuperMerlin.re b/samples/Reason/SuperMerlin.re new file mode 100644 index 00000000..feec78bc --- /dev/null +++ b/samples/Reason/SuperMerlin.re @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + */ +let startedMerlin: ref (option Js.Unsafe.any) = {contents: None}; + +let fixedEnv = Js.Unsafe.js_expr "require('../lib/fixedEnv')"; + +/* This and the subsequent big js blocks are copied over from Nuclide. More convenient for now. */ +let findNearestMerlinFile' = Js.Unsafe.js_expr {| + function findNearestMerlinFile(beginAtFilePath) { + var path = require('path'); + var fs = require('fs'); + var fileDir = path.dirname(beginAtFilePath); + var currentPath = path.resolve(fileDir); + do { + var fileToFind = path.join(currentPath, '.merlin'); + var hasFile = fs.existsSync(fileToFind); + if (hasFile) { + return path.dirname(currentPath); + } + + if (path.dirname(currentPath) === currentPath) { + // Bail + return '.'; + } + currentPath = path.dirname(currentPath); + } while (true); + } +|}; + +let findNearestMerlinFile beginAtFilePath::path => { + let result = Js.Unsafe.fun_call findNearestMerlinFile' [|Js.Unsafe.inject (Js.string path)|]; + Js.to_string result +}; + +let createMerlinReaderFnOnce' = Js.Unsafe.js_expr {| + function(ocamlMerlinPath, ocamlMerlinFlags, dotMerlinDir, fixedEnv) { + var spawn = require('child_process').spawn; + // To split while stripping out any leading/trailing space, we match on all + // *non*-whitespace. + var items = ocamlMerlinFlags === '' ? [] : ocamlMerlinFlags.split(/\s+/); + var merlinProcess = spawn(ocamlMerlinPath, items, {cwd: dotMerlinDir}); + merlinProcess.stderr.on('data', function(d) { + console.error('Ocamlmerlin: something wrong happened:'); + console.error(d.toString()); + }); + + merlinProcess.stdout.on('close', function(d) { + console.error('Ocamlmerlin: closed.'); + }); + + var cmdQueue = []; + var hasStartedReading = false; + + var readline = require('readline'); + var reader = readline.createInterface({ + input: merlinProcess.stdout, + terminal: false, + }); + + return function(cmd, resolve, reject) { + cmdQueue.push([resolve, reject]); + + if (!hasStartedReading) { + hasStartedReading = true; + reader.on('line', function(line) { + var response; + try { + response = JSON.parse(line); + } catch (err) { + response = null; + } + var resolveReject = cmdQueue.shift(); + var resolve = resolveReject[0]; + var reject = resolveReject[1]; + + if (!response || !Array.isArray(response) || response.length !== 2) { + reject(new Error('Unexpected ocamlmerlin output format: ' + line)); + return; + } + + var status = response[0]; + var content = response[1]; + + var errorResponses = { + 'failure': true, + 'error': true, + 'exception': true, + }; + + if (errorResponses[status]) { + reject(new Error('Ocamlmerlin returned an error: ' + line)); + return; + } + + resolve(content); + }); + } + + merlinProcess.stdin.write(JSON.stringify(cmd)); + }; + } +|}; + +let createMerlinReaderFnOnce + pathToMerlin::pathToMerlin + merlinFlags::merlinFlags + dotMerlinPath::dotMerlinPath => + Js.Unsafe.fun_call + createMerlinReaderFnOnce' + [| + Js.Unsafe.inject (Js.string pathToMerlin), + Js.Unsafe.inject (Js.string merlinFlags), + Js.Unsafe.inject (Js.string dotMerlinPath), + Js.Unsafe.inject fixedEnv + |]; + +let startMerlinProcess path::path => + switch startedMerlin.contents { + | Some readerFn => () + | None => + let atomReasonPathToMerlin = Atom.Config.get "atom-reason.pathToMerlin"; + let atomReasonMerlinFlags = Atom.Config.get "atom-reason.merlinFlags"; + let atomReasonMerlinLogFile = Atom.Config.get "atom-reason.merlinLogFile"; + switch atomReasonMerlinLogFile { + | JsonString "" => () + | JsonString s => Atom.Env.setEnvVar "MERLIN_LOG" s + | _ => () + }; + let readerFn = + createMerlinReaderFnOnce + pathToMerlin::(Atom.JsonValue.unsafeExtractString atomReasonPathToMerlin) + merlinFlags::(Atom.JsonValue.unsafeExtractString atomReasonMerlinFlags) + dotMerlinPath::(findNearestMerlinFile beginAtFilePath::path); + startedMerlin.contents = Some readerFn + }; + +let readOneLine cmd::cmd resolve reject => + switch startedMerlin.contents { + | None => raise Not_found + | Some readerFn => + Js.Unsafe.fun_call + readerFn + [| + Js.Unsafe.inject cmd, + Js.Unsafe.inject (Js.wrap_callback resolve), + Js.Unsafe.inject (Js.wrap_callback reject) + |] + }; + +/* contextify is important for avoiding different buffers calling the backing merlin at the same time. */ +/* https://github.com/the-lambda-church/merlin/blob/d98a08d318ca14d9c702bbd6eeadbb762d325ce7/doc/dev/PROTOCOL.md#contextual-commands */ +let contextify query::query path::path => Js.Unsafe.obj [| + ("query", Js.Unsafe.inject query), + ("context", Js.Unsafe.inject (Js.array [|Js.string "auto", Js.string path|])) +|]; + +let prepareCommand text::text path::path query::query resolve reject => { + startMerlinProcess path; + /* These two commands should be run before every main command. */ + readOneLine + cmd::( + contextify + /* The protocol command tells Merlin which API version we want to use. (2 for us) */ + query::( + Js.array [| + Js.Unsafe.inject (Js.string "protocol"), + Js.Unsafe.inject (Js.string "version"), + Js.Unsafe.inject (Js.number_of_float 2.) + |] + ) + path::path + ) + ( + fun _ => + readOneLine + cmd::( + contextify + /* The tell command allows us to synchronize our text with Merlin's internal buffer. */ + query::( + Js.array [|Js.string "tell", Js.string "start", Js.string "end", Js.string text|] + ) + path::path + ) + (fun _ => readOneLine cmd::(contextify query::query path::path) resolve reject) + reject + ) + reject +}; + +let positionToJsMerlinPosition (line, col) => Js.Unsafe.obj [| + /* lines (rows) are 1-based for merlin, not 0-based, like for Atom */ + ("line", Js.Unsafe.inject (Js.number_of_float (float_of_int (line + 1)))), + ("col", Js.Unsafe.inject (Js.number_of_float (float_of_int col))) +|]; + +/* Actual merlin commands we'll use. */ +let getTypeHint path::path text::text position::position resolve reject => + prepareCommand + text::text + path::path + query::( + Js.array [| + Js.Unsafe.inject (Js.string "type"), + Js.Unsafe.inject (Js.string "enclosing"), + Js.Unsafe.inject (Js.string "at"), + Js.Unsafe.inject (positionToJsMerlinPosition position) + |] + ) + resolve + reject; + +let getAutoCompleteSuggestions + path::path + text::text + position::position + prefix::prefix + resolve + reject => + prepareCommand + text::text + path::path + query::( + Js.array [| + Js.Unsafe.inject (Js.string "complete"), + Js.Unsafe.inject (Js.string "prefix"), + Js.Unsafe.inject (Js.string prefix), + Js.Unsafe.inject (Js.string "at"), + Js.Unsafe.inject (positionToJsMerlinPosition position), + Js.Unsafe.inject (Js.string "with"), + Js.Unsafe.inject (Js.string "doc") + |] + ) + resolve + reject; + +let getDiagnostics path::path text::text resolve reject => + prepareCommand + text::text + path::path + query::(Js.array [|Js.Unsafe.inject (Js.string "errors")|]) + resolve + reject; + +let locate path::path text::text extension::extension position::position resolve reject => + prepareCommand + text::text + path::path + query::( + Js.array [| + Js.Unsafe.inject (Js.string "locate"), + Js.Unsafe.inject (Js.string ""), + Js.Unsafe.inject (Js.string extension), + Js.Unsafe.inject (Js.string "at"), + Js.Unsafe.inject (positionToJsMerlinPosition position) + |] + ) + resolve + reject; + +/* reject */ +let getOccurrences path::path text::text position::position resolve reject => + prepareCommand + text::text + path::path + query::( + Js.array [| + Js.Unsafe.inject (Js.string "occurrences"), + Js.Unsafe.inject (Js.string "ident"), + Js.Unsafe.inject (Js.string "at"), + Js.Unsafe.inject (positionToJsMerlinPosition position) + |] + ) + resolve + reject; + +let destruct + path::path + text::text + startPosition::startPosition + endPosition::endPosition + resolve + reject => + prepareCommand + text::text + path::path + query::( + Js.array [| + Js.Unsafe.inject (Js.string "case"), + Js.Unsafe.inject (Js.string "analysis"), + Js.Unsafe.inject (Js.string "from"), + Js.Unsafe.inject (positionToJsMerlinPosition startPosition), + Js.Unsafe.inject (Js.string "to"), + Js.Unsafe.inject (positionToJsMerlinPosition endPosition) + |] + ) + resolve + reject; + +let getOutline path::path text::text resolve reject => + prepareCommand + text::text + path::path + query::(Js.array [|Js.Unsafe.inject (Js.string "outline")|]) + resolve + reject; \ No newline at end of file diff --git a/samples/Reason/Syntax.re b/samples/Reason/Syntax.re new file mode 100644 index 00000000..bdda48b2 --- /dev/null +++ b/samples/Reason/Syntax.re @@ -0,0 +1,989 @@ +/* Copyright (c) 2015-present, Facebook, Inc. All rights reserved. */ +[@@@autoFormat let wrap = 80; let shift = 2]; + +Modules.run (); + +Polymorphism.run (); + +Variants.run (); + +BasicStructures.run (); + +TestUtils.printSection "General Syntax"; + +/* Won't work! */ +/* let matchingFunc a = match a with */ +/* `Thingy x => (print_string "matched thingy x"); x */ +/* | `Other x => (print_string "matched other x"); x;; */ +/* */ +let matchingFunc a => + switch a { + | `Thingy x => + print_string "matched thingy x"; + let zz = 10; + zz + | `Other x => + print_string "matched other x"; + x + }; + +type firstTwoShouldBeGroupedInParens = + (int => int) => int => int; + +type allParensCanBeRemoved = + int => int => int => int; + +type firstTwoShouldBeGroupedAndFirstThree = + ((int => int) => int) => int; + +/* Same thing now but with type constructors instead of each int */ +type firstTwoShouldBeGroupedInParens = + (list int => list int) => list int => list int; + +type allParensCanBeRemoved = + list int => list int => list int => list int; + +type firstTwoShouldBeGroupedAndFirstThree = + ((list int => list int) => list int) => + list int; + +type myRecordType = { + firstTwoShouldBeGroupedInParens: + (int => int) => int => int, + allParensCanBeRemoved: + int => int => int => int, + firstTwoShouldBeGroupedAndFirstThree: + ((int => int) => int) => int +}; + +type firstNamedArgShouldBeGroupedInParens = + first::(int => int) => second::int => int; + +type allParensCanBeRemoved = + first::int => second::int => third::int => int; + +type firstTwoShouldBeGroupedAndFirstThree = + first::((int => int) => int) => int; + +/* Same thing now, but with type constructors instead of int */ +type firstNamedArgShouldBeGroupedInParens = + first::(list int => list int) => + second::list int => + list int; + +type allParensCanBeRemoved = + first::list int => + second::list int => + third::list int => + list int; + +type firstTwoShouldBeGroupedAndFirstThree = + first::((list int => list int) => list int) => + list int; + +type firstNamedArgShouldBeGroupedInParens = + first::(int => int)? => + second::int list? => + int; + +/* The arrow necessitates parens around the next two args. The ? isn't what + * makes the parens necessary. */ +type firstNamedArgShouldBeGroupedInParensAndSecondNamedArg = + first::(int => int)? => + second::(int => int)? => + int; + +type allParensCanBeRemoved = + first::int? => + second::int? => + third::int? => + int; + +type firstTwoShouldBeGroupedAndFirstThree = + first::((int => int) => int) => int; + +type noParens = + one::int => int => int => two::int => int; + +type noParensNeeded = + one::int => int => int => two::int => int; + +type firstNamedArgNeedsParens = + one::(int => int => int) => two::int => int; + +/* Now, let's try type aliasing */ +/* Unless wrapped in parens, types between arrows may not be aliased, may not + * themselves be arrows. */ +type parensRequiredAroundFirstArg = + (list int as 'a) => int as 'a; + +type parensRequiredAroundReturnType = + (list int as 'a) => (int as 'a); + +type parensRequiredAroundReturnType = + (list int as 'a) => (int as 'a) as 'b; + +type noParensNeededWhenInTuple = + (list int as 'a, list int as 'b) as 'entireThing; + +type myTypeDef 'a = list 'a; + +type instatiatedTypeDef = myTypeDef int => int; + +/* Test a type attribute for good measure */ +/* We should clean up all of the attribute tagging eventually, but for now, + * let's make it super ugly to get out of the way of all the formatting/parsing + * implementations (fewer conflicts during parsing, fewer edge cases during + * printing). + */ +type something = ( + int, + int [@lookAtThisAttribute] +); + +type longWrappingTypeDefinitionExample = + M_RK__G.Types.instance + (TGRecognizer.tGFields unit unit) + (TGRecognizer.tGMethods unit unit); + +type semiLongWrappingTypeDefinitionExample = + M_RK__Gesture.Types.instance + TGRecognizerFinal.tGFields + TGRecognizerFinal.tGMethods; + +type semiLongWrappingTypeWithConstraint = + M_RK__Gesture.Types.instance + 'a + TGRecognizerFinal.tGFields + TGRecognizerFinal.tGMethods +constraint 'a = (unit, unit); + +type onelineConstrain = 'a constraint 'a = int; + +/* This must be in trunk but not in this branch of OCaml */ +/* type withNestedRecords = MyConstructor {myField: int} */ +type colors = + | Red int + | Black int + | Green int; + +/* Another approach is to require declared variants to wrap any record */ +/* type myRecord = MyRecord {name: int}; */ +/* let myValue = MyRecord {name: int}; */ +/* This would force importing of the module */ +/* This would also lend itself naturally to pattern matching - and avoid having + to use `.` operator at all since you normally destructure. */ +type nameBlahType = {nameBlah: int}; + +let myRecord = {nameBlah: 20}; + +let myRecordName = myRecord.nameBlah; + +let {nameBlah}: nameBlahType = {nameBlah: 20}; + +print_int nameBlah; + +let {nameBlah: aliasedToThisVar}: nameBlahType = { + nameBlah: 20 +}; + +print_int aliasedToThisVar; + +let desiredFormattingForWrappedLambda: + int => int => int => nameBlahType = + /* + + fun is + pre- /firstarg\ + fix /-coupled--\ + |-\ /-to-prefix--\ */ + fun curriedArg anotherArg lastArg => { + nameBlah: 10 + }; + +type longerInt = int; + +let desiredFormattingForWrappedLambdaWrappedArrow: + longerInt => + longerInt => + longerInt => + nameBlahType = + /* + + fun is + pre- /firstarg\ + fix /-coupled--\ + |-\ /-to-prefix--\ */ + fun curriedArg anotherArg lastArg => { + nameBlah: 10 + }; + +let desiredFormattingForWrappedLambdaReturnOnNewLine + /* + + fun is + pre- /firstarg\ + fix /-coupled--\ + |-\ /-to-prefix--\ */ + curriedArg + anotherArg + lastArg => { + nameBlah: 10 +}; + +/* + let is + pre- + fix /-function binding name---\ + |-\ / is coupled to prefix \ */ +let desiredFormattingForWrappedSugar + curriedArg + anotherArg + lastArg => { + nameBlah: 10 +}; + +/* + let is + pre- + fix /-function binding name---\ + |-\ / is coupled to prefix \ */ +let desiredFormattingForWrappedSugarReturnOnNewLine + curriedArg + anotherArg + lastArg => { + nameBlah: 10 +}; + +/* + let : type t1 t2. t1 * t2 list -> t1 = ... + let rec f : 't1 't2. 't1 * 't2 list -> 't1 = + fun (type t1) (type t2) -> (... : t1 * t2 list -> t1) + */ +type point = {x: int, y: int}; + +type point3D = {x: int, y: int, z: int}; + +let point2D = {x: 20, y: 30}; + +let point3D: point3D = { + x: 10, + y: 11, + z: 80 /* Optional Comma */ +}; + +let printPoint (p: point) => { + print_int p.x; + print_int p.y +}; + +let addPoints (p1: point, p2: point) => { + x: p1.x + p2.x, + y: p1.y + p2.y +}; + +let res1 = printPoint point2D; + +let res2 = + printPoint {x: point3D.x, y: point3D.y}; + +/* + When () were used to indicate sequences, the parser used seq_expr not only + for grouping sequences, but also to form standard precedences. + /------- sequence_expr ------\ + let res3 = printPoint (addPoints (point2D, point3D)); + + Interestingly, it knew that tuples aren't sequences. + + To move towards semi delimited, semi-terminated, braces-grouped sequences: + while allowing any non-sequence expression to be grouped on parens, we make + an explicit rule that allows one single non-semi ended expression to be + grouped in parens. + + Actually: We will allow an arbitrary number of semi-delimited expressions to + be wrapped in parens, but the braces grouped semi delimited (sequence) + expressions must *also* be terminated with a semicolon. + + This allows the parser to distinguish between + + let x = {a}; /* Record {a:a} */ + let x = {a;}; /* Single item sequence returning identifier {a} */ + */ +let res3 = + printPoint ( + addPoints ( + point2D, + {x: point3D.x, y: point3D.y} + ) + ); + +type person = {age: int, name: string}; + +type hiredPerson = { + age: string, + name: string, + dateHired: int +}; + +let o: person = {name: "bob", age: 10}; + +/* Parens needed? Nope! */ +let o: person = {name: "bob", age: 10}; + +let printPerson (p: person) => { + let q: person = p; + p.name ^ p.name +}; + +/* let dontParseMeBro x y:int = x = y;*/ +/* With this unification, anywhere eyou see `= fun` you can just ommit it */ +let blah a => a; /* Done */ + +let blah a => a; /* Done (almost) */ + +let blah a b => a; /* Done */ + +let blah a b => a; /* Done (almost) */ + +/* More than one consecutive pattern must have a single case */ +type blah = {blahBlah: int}; + +let blah a {blahBlah} => a; + +let blah a {blahBlah} => a; + +let module TryToExportTwice = { + let myVal = "hello"; +}; + +/* + Unifying top level module syntax with local module syntax is probably a bad + idea at the moment because it makes it more difficult to continue to support + `let .. in` bindings. We can distinguish local modules for `let..in` that + just happen to be defined at the top level (but not exported). + + let MyModule = {let myVal = 20;} in + MyModule.x + + Wait, where would this ever be valid, even if we continued to support + `let..in`? + */ +let onlyDoingThisTopLevelLetToBypassTopLevelSequence = { + let x = { + print_int 1; + print_int 20 /* Missing trailing SEMI */ + }; + let x = { + print_int 1; + print_int 20; /* Ensure missing middle SEMI reported well */ + print_int 20 + }; + let x = { + print_int 1; + print_int 20; + 10 + /* Comment in final position */ + }; /* Missing final SEMI */ + x + x +}; + +type hasA = {a: int}; + +let a = 10; + +let returnsASequenceExpressionWithASingleIdentifier + () => a; + +let thisReturnsA () => a; + +let thisReturnsAAsWell () => a; + +let recordVal: int = (thisReturnsARecord ()).a; + +Printf.printf + "\nproof that thisReturnsARecord: %n\n" + recordVal; + +Printf.printf + "\nproof that thisReturnsA: %n\n" + (thisReturnsA ()); + +/* Pattern matching */ +let blah arg => + switch arg { + /* Comment before Bar */ + | /* Comment between bar/pattern */ Red _ => 1 + /* Comment Before non-first bar */ + | /* Comment betwen bar/pattern */ Black _ => 0 + | Green _ => 0 + }; + +/* Any function that pattern matches a multicase match is interpretted as a + * single arg that is then matched on. Instead of the above `blah` example:*/ +let blah = + fun + | Red _ => 1 + | Black _ => 0 + | Green _ => 1; + +/* `fun a => a` is read as "a function that maps a to a". Then the */ +/* above example is read: "a function that 'either maps' Red to.. or maps .." */ +/* Thc00f564e first bar is read as "either maps" */ +/* Curried form is not supported: + let blah x | Red _ => 1 | Black _ => 0; + Theres no sugar rule for dropping => fun, only = fun + */ +/* let blahCurriedX x => fun /* See, nothing says we can drop the => fun */ */ +/* |(Red x | Black x | Green x) => 1 /* With some effort, we can ammend the sugar rule that would */ */ +/* | Black x => 0 /* Allow us to drop any => fun.. Just need to make pattern matching */ */ +/* | Green x => 0; /* Support that */ */ +/* */ +let blahCurriedX x => + fun + | Red x + | Black x + | Green x => + 1 /* With some effort, we can ammend the sugar rule that would */ + | Black x => 0 /* Allow us to drop any => fun.. Just need to make pattern matching */ + | Green x => 0; /* Support that */ + +let sameThingInLocal = { + let blahCurriedX x => + fun + | Red x + | Black x + | Green x => + 1 /* With some effort, we can ammend the sugar rule that would */ + | Black x => 0 /* Allow us to drop any => fun.. Just need to make pattern matching */ + | Green x => 0; /* Support that */ + blahCurriedX +}; + +/* This should be parsed/printed exactly as the previous */ +let blahCurriedX x => + fun + | Red x + | Black x + | Green x => 1 + | Black x => 0 + | Green x => 0; + +/* Any time there are multiple match cases we require a leading BAR */ +let v = Red 10; + +let Black x | Red x | Green x = v; /* So this NON-function still parses */ + +/* This doesn't parse, however (and it doesn't in OCaml either): + let | Black x | Red x | Green x = v; + */ +print_int x; + +/* Scoping: Let sequences. Familiar syntax for lexical ML style scope and + sequences. */ +let res = { + let a = "a starts out as"; + { + print_string a; + let a = 20; + print_int a + }; + print_string a +}; + +let res = { + let a = "first its a string"; + let a = 20; + print_int a; + print_int a; + print_int a +}; + +let res = { + let a = "a is always a string"; + print_string a; + let b = 30; + print_int b +}; + +/* let result = LyList.map (fun | [] => true | _ => false) []; */ +/* OTHERWISE: You cannot tell if a is the first match case falling through or + * a curried first arg */ +/* let blah = fun a | patt => 0 | anotherPatt => 1; */ +/* let blah a patt => 0 | anotherPatt => 1; */ +/*simple pattern EQUALGREATER expr */ +let blah a {blahBlah} => a; + +/* match_case */ +/* pattern EQUALGREATER expr */ +let blah = + fun + | Red _ => 1 + | Black _ => 0 + | Green _ => 0; + +/* Won't work! */ +/* let arrowFunc = fun a b => print_string "returning aplusb from arrow"; a + b;; */ +let arrowFunc a b => { + print_string "returning aplusb from arrow"; + a + b +}; + +let add a b => { + let extra = { + print_string "adding"; + 0 + }; + let anotherExtra = 0; + extra + a + b + anotherExtra +}; + +print_string (string_of_int (add 4 34)); + +let dummy _ => 10; + +dummy res1; + +dummy res2; + +dummy res3; + +/* Some edge cases */ +let myFun firstArg (Red x | Black x | Green x) => + firstArg + x; + +let matchesWithWhen a => + switch a { + | Red x when 1 > 0 => 10 + | Red _ => 10 + | Black x => 10 + | Green x => 10 + }; + +let matchesWithWhen = + fun + | Red x when 1 > 0 => 10 + | Red _ => 10 + | Black x => 10 + | Green x => 10; + +let matchesOne (`Red x) => 10; + +/* + Typical OCaml would make you *wrap the functions in parens*! This is because it + can't tell if a semicolon is a sequence operator. Even if we had records use + commas to separate fields, + */ +type adders = { + addTwoNumbers: int => int => int, + addThreeNumbers: int => int => int => int, + addThreeNumbersTupled: (int, int, int) => int +}; + +let myRecordWithFunctions = { + addTwoNumbers: fun a b => a + b, + addThreeNumbers: fun a b c => a + b + c, + addThreeNumbersTupled: fun (a, b, c) => + a + b + c +}; + +let result = + myRecordWithFunctions.addThreeNumbers 10 20 30; + +let result = + myRecordWithFunctions.addThreeNumbersTupled ( + 10, + 20, + 30 + ); + +let lookTuplesRequireParens = (1, 2); + +/* let thisDoesntParse = 1, 2; */ +let tupleInsideAParenSequence = { + print_string "look, a tuple inside a sequence"; + let x = 10; + (x, x) +}; + +let tupleInsideALetSequence = { + print_string "look, a tuple inside a sequence"; + let x = 10; + (x, x) +}; + +/* We *require* that function return types be wrapped in + parenthesis. In this example, there's no ambiguity */ +let makeIncrementer (delta: int) :(int => int) => + fun a => a + delta; + +/* We could even force that consistency with let bindings - it's allowed + currently but not forced. + */ +let myAnnotatedValBinding: int = 10; + +/* Class functions (constructors) and methods are unified in the same way */ +class classWithNoArg = { + method x = 0; + method y = 0; +}; + +/* This parses but doesn't type check + class myClass init => object + method x => init + method y => init + end; + */ +let myFunc (a: int) (b: int) :(int, int) => ( + a, + b +); + +let myFunc (a: int) (b: int) :list int => [1]; + +let myFunc (a: int) (b: int) :point => { + x: a, + y: b +}; + +let myFunc (a: int, b: int) :point => { + x: a, + y: b +}; + +type myThing = (int, int); + +type stillARecord = {name: string, age: int}; + +/* Rebase latest OCaml to get the following: And fixup + `generalized_constructor_arguments` according to master. */ +/* type ('a, 'b) myOtherThing = Leaf {first:'a, second: 'b} | Null; */ +type branch 'a 'b = {first: 'a, second: 'b}; + +type myOtherThing 'a 'b = + | Leaf (branch 'a 'b) + | Null; + +type yourThing = myOtherThing int int; + +/* Conveniently - this parses exactly how you would intend! No *need* to wrap + in an extra [], but it doesn't hurt */ +/* FIXME type lookAtThesePolyVariants = list [`Red] ; */ +/* FIXME type bracketsGroupMultipleParamsAndPrecedence = list (list (list [`Red])); */ +/* FIXME type youCanWrapExtraIfYouWant = (list [`Red]); */ +/* FIXME type hereAreMultiplePolyVariants = list [`Red | `Black]; */ +/* FIXME type hereAreMultiplePolyVariantsWithOptionalWrapping = list ([`Red | `Black]); */ +/* + /* Proposal: ES6 style lambdas: */ + + /* Currying */ + let lookES6Style = (`Red x) (`Black y) => { }; + let lookES6Style (`Red x) (`Black y) => { }; + + /* Matching the single argument */ + let lookES6Style = oneArg => match oneArg with + | `Red x => x + | `Black x => x; + + /* The "trick" to currying that we already have is basically the same - we just + * have to reword it a bit: + * From: + * "Any time you see [let x = fun ...] just replace it with [let x ...]" + * To: + * "Any time you see [let x = ... => ] just replace it with [let x ... => ]" + */ + let lookES6Style oneArg => match oneArg with + | `Red x => x + | `Black x => x; + + */ + +/** Current OCaml Named Arguments. Any aliasing is more than just aliasing! + OCaml allows full on pattern matching of named args. */ +/* + A: let named ~a ~b = aa + bb in + B: let namedAlias ~a:aa ~b:bb = aa + bb in + C: let namedAnnot ~(a:int) ~(b:int) = a + b in + D: let namedAliasAnnot ~a:(aa:int) ~b:(bb:int) = aa + bb in + E: let optional ?a ?b = 10 in + F: let optionalAlias ?a:aa ?b:bb = 10 in + G: let optionalAnnot ?(a:int option) ?(b:int option) = 10 in + H: let optionalAliasAnnot ?a:(aa:int option) ?b:(bb:int option) = 10 in + /* + Look! When a default is provided, annotation causes inferred type of argument + to not be "option" since it's automatically destructured (because we know it + will always be available one way or another.) + */ + I: let defOptional ?(a=10) ?(b=10) = 10 in + J: let defOptionalAlias ?a:(aa=10) ?b:(bb=10) = 10 in + K: let defOptionalAnnot ?(a:int=10) ?(b:int=10) = 10 in + \ \ + \label_let_pattern opt_default: no longer needed in SugarML + + L: let defOptionalAliasAnnot ?a:(aa:int=10) ?b:(bb:int=10) = 10 in + \ \ + \let_pattern: still a useful syntactic building block in SugarML + */ + +/** + * In Reason, the syntax for named args uses double semicolon, since + * the syntax for lists uses ES6 style [], freeing up the ::. + */ +let a = 10; + +let b = 20; + +/*A*/ +let named a::a b::b => a + b; + +type named = a::int => b::int => int; + +/*B*/ +let namedAlias a::aa b::bb => aa + bb; + +let namedAlias a::aa b::bb => aa + bb; + +type namedAlias = a::int => b::int => int; + +/*C*/ +let namedAnnot a::(a: int) b::(b: int) => 20; + +/*D*/ +let namedAliasAnnot a::(aa: int) b::(bb: int) => 20; + +/*E*/ +let myOptional a::a=? b::b=? () => 10; + +type named = a::int? => b::int? => unit => int; + +/*F*/ +let optionalAlias a::aa=? b::bb=? () => 10; + +/*G*/ +let optionalAnnot a::(a: int)=? b::(b: int)=? () => 10; + +/*H*/ +let optionalAliasAnnot + a::(aa: int)=? + b::(bb: int)=? + () => 10; + +/*I: */ +let defOptional a::a=10 b::b=10 () => 10; + +type named = a::int? => b::int? => unit => int; + +/*J*/ +let defOptionalAlias a::aa=10 b::bb=10 () => 10; + +/*K*/ +let defOptionalAnnot + a::(a: int)=10 + b::(b: int)=10 + () => 10; + +/*L*/ +let defOptionalAliasAnnot + a::(aa: int)=10 + b::(bb: int)=10 + () => 10; + +/*M: Invoking them - Punned */ +let resNotAnnotated = named a::a b::b; + +/*N:*/ +let resAnnotated: int = named a::a b::b; + +/*O: Invoking them */ +let resNotAnnotated = named a::a b::b; + +/*P: Invoking them */ +let resAnnotated: int = named a::a b::b; + +/*Q: Here's why "punning" doesn't work! */ +/* Is b:: punned with a final non-named arg, or is b:: supplied b as one named arg? */ +let b = 20; + +let resAnnotated = named a::a b::b; + +/*R: Proof that there are no ambiguities with return values being annotated */ +let resAnnotated: ty = named a::a b; + +/*S: Explicitly passed optionals are a nice way to say "use the default value"*/ +let explictlyPassed = + myOptional a::?None b::?None; + +/*T: Annotating the return value of the entire function call */ +let explictlyPassedAnnotated: int = + myOptional a::?None b::?None; + +/*U: Explicitly passing optional with identifier expression */ +let a = None; + +let explictlyPassed = myOptional a::?a b::?None; + +let explictlyPassedAnnotated: int = + myOptional a::?a b::?None; + +let nestedLet = { + let _ = 1; + () +}; + +let nestedLet = { + let _ = 1; + () +}; + +let nestedLet = { + let _ = 1; + () +}; + +let nestedLet = { + let _ = 1; + 2 +}; + +/* + * Showing many combinations of type annotations and named arguments. + */ +type typeWithNestedNamedArgs = + outerOne::( + innerOne::int => innerTwo::int => int + ) => + outerTwo::int => + int; + +type typeWithNestedOptionalNamedArgs = + outerOne:: + (innerOne::int => innerTwo::int => int)? => + outerTwo::int? => + int; + +type typeWithNestedOptionalNamedArgs = + outerOne::list string? => outerTwo::int? => int; + +let x = + callSomeFunction + withArg::10 andOtherArg::wrappedArg; + +let res = { + (constraintedSequenceItem: string); + (dontKnowWheYoudWantToActuallyDoThis: string) +}; + +let res = { + ( + butTheyWillBePrintedWithAppropriateSpacing: string + ); + (soAsToInstillBestDevelopmentPractices: string) +}; + +let x = [ + (eachItemInListCanBeAnnotated: int), + (typeConstraints: float), + ( + tupleConstraints: int, + andNotFunctionInvocations: int + ) +]; + +let x = [ + (butWeWillPrint: int), + (themAsSpaceSeparated: float), + (toInfluenceYour: int, developmentHabbits: int) +]; + +let newRecord = { + ...(annotatedSpreadRecord: someRec), + x: y +}; + +let newRecord = { + ...(annotatedSpreadRecord: someRec), + blah: 0, + foo: 1 +}; + +let newRecord = { + ...( + youCanEvenCallMethodsHereAndAnnotate them: someRec + ), + blah: 0, + foo: 1 +}; + +let newRecord = { + ...( + youCanEvenCallMethodsHereAndAnnotate + them named::10: someRec + ), + blah: 0, + foo: 1 +}; + +let something: thing blah = aTypeAnnotation; + +let something: thing blah = thisIsANamedArg; + +let something: thing blah = aTypeAnnotation; + +let something: blah = thisIsANamedArg thing; + +let something: blah = typeAnnotation thing; + +let newRecord = { + ...( + heresAFunctionWithNamedArgs argOne::i: annotatedResult + ), + soAsToInstill: 0, + developmentHabbits: 1 +}; + +[@@@thisIsAThing]; + +let x = 10; + +/* Ensure that the parenthesis are preserved here because they are + * important: + */ +let something = + fun + | None => ( + fun + | [] => "emptyList" + | [_, ..._] => "nonEmptyList" + ) + | Some _ => ( + fun + | [] => "emptyList" + | [_, ..._] => "nonEmptyList" + ); + +/* A | B = X; */ +let A | B = X; + +/* A | (B | C) = X; */ +let A | (B | C) = X; + +/* (A | B) | (C | D) = X; */ +let A | B | (C | D) = X; + +/* A | B | (C | D) = X; */ +let A | B | (C | D) = X; + +/* (A | B) | C = X; */ +let A | B | C = X; + +/* A | B | C = X; */ +let A | B | C = X; + + +/** External function declaration + * + */ +external f : int => int = "foo"; + +let x = {contents: 0}; + +let unitVal = x.contents = 210; diff --git a/vendor/grammars/reason b/vendor/grammars/reason new file mode 160000 index 00000000..97d91c61 --- /dev/null +++ b/vendor/grammars/reason @@ -0,0 +1 @@ +Subproject commit 97d91c61d1947631b89024830bc43dad2931fabe diff --git a/vendor/licenses/grammar/reason.txt b/vendor/licenses/grammar/reason.txt new file mode 100644 index 00000000..8534842d --- /dev/null +++ b/vendor/licenses/grammar/reason.txt @@ -0,0 +1,35 @@ +--- +type: grammar +name: reason +license: bsd-3-clause +--- +BSD License + +For Reason software + +Copyright (c) 2015-present, Facebook, Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name Facebook nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file