{"id":19434499,"url":"https://github.com/mlin/twt","last_synced_at":"2025-07-15T11:09:58.939Z","repository":{"id":1091667,"uuid":"946413","full_name":"mlin/twt","owner":"mlin","description":"The Whitespace Thing for OCaml","archived":false,"fork":false,"pushed_at":"2015-07-05T18:20:51.000Z","size":264,"stargazers_count":11,"open_issues_count":6,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-07-04T03:05:23.416Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"OCaml","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mlin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2010-09-28T19:20:18.000Z","updated_at":"2024-12-11T20:53:02.000Z","dependencies_parsed_at":"2022-07-06T06:32:28.115Z","dependency_job_id":null,"html_url":"https://github.com/mlin/twt","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/mlin/twt","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mlin%2Ftwt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mlin%2Ftwt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mlin%2Ftwt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mlin%2Ftwt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mlin","download_url":"https://codeload.github.com/mlin/twt/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mlin%2Ftwt/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265430478,"owners_count":23764003,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-10T14:46:37.796Z","updated_at":"2025-07-15T11:09:58.917Z","avatar_url":"https://github.com/mlin.png","language":"OCaml","funding_links":[],"categories":[],"sub_categories":[],"readme":"# \"The Whitespace Thing\" for OCaml\n\nhttp://github.com/mlin/twt\n\n**Maintainer: [Mike Lin](http://www.mlin.net/)**\n\n\"The Whitespace Thing\" for OCaml is a preprocessor (invoked as `ocaml+twt`) that\nuses indentation to auto-parenthesize multi-line expressions, like in Python and\nHaskell. Using natural indentation patterns, it eliminates:\n\n* the `;` operator for statement sequences and the `;;` top-level statement operator\n* multi-line parenthesization nested function applications\n* ambiguity involving nested let, if-else, and try-with expressions, and resulting parentheses\n* the parenthetical keywords `in`, `done`, `begin`, and `end`\n\nThe language syntax is otherwise the same as OCaml's, with a few restrictions.\n\nVersion 1 is implemented as a line-oriented preprocessor; this is something of\na hack. At some unspecified time in the future, it should be rewritten with a\nproper syntax tree parser, although the current approach honestly gets quite\nfar!\n\n### Code examples\n\n\u003ctable style=\"vertical-align: top\"\u003e\n\u003ctr\u003e\n\u003ctd\u003e\n\u003cstrong\u003eocaml\u003c/strong\u003e\n\u003c/td\u003e\n\u003ctd\u003e\n\u003cstrong\u003eocaml+twt\u003c/strong\u003e\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\n\u003csub\u003e\n\u003cpre\u003e\nlet rec main magic_number =\n Printf.printf \"Your guess? \";\n let guess = int_of_string (read_line ()) in\n  if guess \u003e magic_number then\n   (Printf.printf \"Too high!\\n\";\n    main magic_number)\n  else if guess \u003c magic_number then\n   (Printf.printf \"Too low!\\n\";\n    main magic_number)\n  else\n   (Printf.printf \"You win!\\n\";\n    exit 0);;\n\nRandom.self_init ();;\n\nmain (Random.int 100);;\n\u003c/pre\u003e\n\u003c/sub\u003e\n\u003c/td\u003e\n\u003ctd\u003e\n\u003csub\u003e\n\u003cpre\u003e\nlet rec main magic_number =\n Printf.printf \"Your guess? \"\n let guess = int_of_string (read_line ())\n if guess \u003e magic_number then\n  Printf.printf \"Too high!\\n\"\n  main magic_number\n else if guess \u003c magic_number then\n  Printf.printf \"Too low!\\n\"\n  main magic_number\n else\n  Printf.printf \"You win!\\n\"\n  exit 0\n\nRandom.self_init ()\n\nmain (Random.int 100)\n\u003c/pre\u003e\n\u003c/sub\u003e\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\n\u003csub\u003e\n\u003cpre\u003e\nlet list_out lst =\n (List.map\n  (function Some x -\u003e x)\n  (List.filter\n   (function Some x -\u003e true | None -\u003e false)\n   lst))\n\u003c/pre\u003e\n\u003c/sub\u003e\n\u003c/td\u003e\n\u003ctd\u003e\n\u003csub\u003e\n\u003cpre\u003e\nlet list_out lst =\n List.map\n  function Some x -\u003e x\n  List.filter\n   function Some x -\u003e true | None -\u003e false\n   lst\n\u003c/pre\u003e\n\u003c/sub\u003e\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\n\u003csub\u003e\n\u003cpre\u003e\nfor i = 1 to 10 do\n print_int i;\n print_newline ()\ndone;\nprint_string \"done\"\n\u003c/pre\u003e\n\u003c/sub\u003e\n\u003c/td\u003e\n\u003ctd valign=\"top\"\u003e\n\u003csub\u003e\n\u003cpre\u003e\nfor i = 1 to 10 do\n print_int i\n print_newline ()\nprint_string \"done\"\n\u003c/pre\u003e\n\u003c/sub\u003e\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\n\u003csub\u003e\n\u003cpre\u003e\nlet contrived = function\n   s when (String.length s) \u003e 0 -\u003e\n    begin\n     try\n      Some (float_of_string s)\n     with\n      Failure _ -\u003e Some nan\n    end\n | _ -\u003e None\n\u003c/pre\u003e\n\u003c/sub\u003e\n\u003c/td\u003e\n\u003ctd valign=\"top\"\u003e\n\u003csub\u003e\n\u003cpre\u003e\nlet contrived = function\n | s when (String.length s) \u003e 0 -\u003e\n    try\n     Some (float_of_string s)\n    with\n     | Failure _ -\u003e Some nan\n | _ -\u003e None\n\u003c/pre\u003e\n\u003c/sub\u003e\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\n### Language documentation\n\nSee the [quick reference](https://github.com/mlin/twt/raw/master/doc/quick_reference.pdf),\nand the longer [examples/](https://github.com/mlin/twt/tree/master/examples).\n\n### Installation\n\nocaml+twt is available in [OPAM](http://opam.ocamlpro.com):\n`opam install ocaml+twt`. Once installed, the ocaml+twt executable is\navailable in your path when you `eval $(opam config env)`.\n\nWithout OPAM, extract the source tarball and `make install`.  This installs\nthe executable to `PREFIX/bin` where `PREFIX=/usr/local`. You can override\nthis with `make install PREFIX=/home/alice`\n\n### Usage\n\nTo use the preprocessor, either manually invoke it using `ocaml+twt mycode.ml`\nand pipe the results to a file, or use the preprocessor flag to ocamlc:\n\n```ocamlc -pp ocaml+twt mycode.ml```\n\nThere are a few optional behaviors available for the preprocessor. They're\npretty self-explanatory by looking at the usage printed by invoking ocaml+twt.\n\nWith ocamlbuild, you can just add something like this to the `_tags` file in\nyour project directory:\n\n```\u003c**/*.ml\u003e or \u003c**/*.mli\u003e: pp(ocaml+twt)```\n\nIf you use OCamlMakefile, you can make the first line of your file\n`(*pp ocaml+twt *)`.\n\n### Tips and FAQs\n\n* **Parentheses:** The preprocessor completely ignores anything inside parentheses, including newlines. Thus, any sub-expressions also need to be parenthesized, regardless of how they're indented. Occasionally, parenthesization is useful to work around the preprocessor if it doesn't understand some obscure OCaml syntax.\n* **Performance:** The syntax transform does not add any expressions or statements, only parentheses. Thus, there should be no performance impact in the final product.\n* **Comments:** should not occur (or terminate) at the *beginning* of a line that also has code on it.\n* **ocamldoc:** You should be able to comment things as usual and run ocamldoc on the *postprocessed* code.\n* **Toplevel:** no support and none likely, sorry.\n* **Pattern matching:** If the consequence of a pattern is a sequence of statements, make sure to place them either all on one line (separated by ;) or entirely in their own block. That is:\n\u003ctable\u003e\n\u003ctr\u003e\n\u003ctd\u003e\ninstead of...\n\u003c/td\u003e\n\u003ctd\u003e\ndo...\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd valign=\"top\"\u003e\n\u003cpre\u003e\nmatch n with\n  | 1 -\u003e print_string \"one\"\n         print_endline ()\n\u003c/pre\u003e\n\u003c/td\u003e\n\u003ctd\u003e\n\u003cpre\u003e\nmatch n with\n  | 1 -\u003e\n     print_string \"one\"\n     print_endline ()\n\u003c/pre\u003e\n\u003c/td\u003e\n\u003c/tr\u003e \n\u003c/table\u003e\nOf course, if the consequent is just a single expression, you can place it on the same line. This restriction is actually true almost everywhere, such as let bodies and if-then consequents; see the quick reference. The rule of thumb: **if an expression spans multiple lines, it must begin on its own line.**\n* **Applications:** In multi-line function applications, if the function being applied is some complicated expression (rather than just an identifier), you must parenthesize it:\n\u003ctable\u003e\n\u003ctr\u003e\n\u003ctd\u003e\ninstead of...\n\u003c/td\u003e\n\u003ctd\u003e\ndo...\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd valign=\"top\"\u003e\n\u003cpre\u003e\nif b then (+) else (-)\n  x\n  y\n\u003c/pre\u003e\n\u003c/td\u003e\n\u003ctd\u003e\n\u003cpre\u003e\n(if b then (+) else (-))\n  x\n  y\n\u003c/pre\u003e\n\u003c/td\u003e\n\u003c/tr\u003e \n\u003ctr\u003e\n\u003ctd valign=\"top\"\u003e\n\u003cpre\u003e\nfunction\n  | x when x \u003e= 0 -\u003e (+)\n  | _ -\u003e (-)\n  x\n  y\n\u003c/pre\u003e\n\u003c/td\u003e\n\u003ctd valign=\"top\"\u003e\n\u003cpre\u003e\n(function\n  | x when x \u003e= 0 -\u003e (+)\n  | _ -\u003e (-))\n  x\n  y\n\u003c/pre\u003e\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\n\n### ppcompose utility\n\nBecause the -pp flag to ocamlc is somewhat limited, I included a 'ppcompose'\nutility that makes it simple to compose several preprocessors. For example,\none can compose a list comprehension camlp4 syntax with ocaml+twt as follows:\n\n```ocamlc -pp \"ppcompose 'camlp4o pa_compr.cmo' 'ocaml+twt -spaceonly'\" source.ml```\n\nThe last preprocessor specified on the command line is applied first to the\nsource code. This means you usually want to put ocaml+twt last.\n\n### Useful links\n\n* [Understanding GNU Emacs and Tabs](http://www.pement.org/emacs_tabs.htm)\n* [Python: Myths about Indentation](http://www.secnetix.de/~olli/Python/block_indentation.hawk), largely applicable to ocaml+twt as well\n* [F# lightweight syntax](http://blogs.msdn.com/dsyme/archive/2006/08/24/715626.aspx), a similar idea for the OCaml-derived language for .NET. The lightweight syntax seems to be much more popular than the \"normal\" syntax among F# users. (For the record, ocaml+twt [preceded this](http://groups.google.com/group/fa.caml/browse_thread/thread/50009fed6088f114/4e5d40b372f87878?lnk=gst\u0026q=ocaml%2Btwt#4e5d40b372f87878) by about nine months; I don't know if there was any causal relationship.)\n\n### Version history\n\n7/5/15 version 0.94.0\n- New OCaml 4.02 features: quoted strings and ppx infix extension\n  nodes (both are simply passed through)\n\n10/05/13 version 0.93.2\n- New OCaml 4.01 keyword: open!\n- New command-line flag -lwt to accept pa_lwt code (recognize\n  lwt, for_lwt, while_lwt, try_lwt, match_lwt, etc.)\n\n10/24/12 version 0.931\n- Minor changes to installation procedure to support packaging\n\n02/01/12 version 0.93\n- Handle OCaml 3.12's new 'include module type of...' in module\n  signatures\n- Allow pass-through of named operands in applications: f ~x\n- Allow one-liner consequents for predicates:\n    if pred1 then consq1\n    else if pred2 then consq2\n    else consq3\n- Allow one-liner try and with bodies (similar):\n    try expr1\n    with Not_found -\u003e expr2\n\n08/02/10 version 0.92\n- Accepts piped input\n- Supports output to file (new option -o)\n- ppcompose properly cleans up after itself, and makes less stderr noise\n  by default (new option -v)\n- New ocaml 3.12 keywords: val! method! inherit!\n- bugfix: doesn't screw up with character literals '(' and ')'\n- bugfix: allows passed-along optional arguments on their own lines\n\n03/11/08 version 0.91\n- Bug fixes to handling of escaped characters within string and\ncharacter literals\n\n01/16/07 version 0.90\n- Major backwards-incompatible change: elimination of \"in\" from let,\nand elimination of requirement to indent let body.\n\n12/10/06 version 0.86\n- moved all parentheses inserted by the preprocessor from the\nbeginning of a line to the end of the previous line, making column\nnumbers in error messages usually match up, and improving the\nreadability of the postprocessed code. Thanks to Ingo Bormuth for the\nidea and patch.\n\n07/24/06 version 0.85\n- ocamlc now shows the preprocessed filename in errors\n- bugfix: your labelled or optional arguments on their own lines can\nnow have underscores in their names\n- bugfix: you can now declare exceptions in module signatures\n- bugfix: you can now have type constraints in object definitions\n- bugfix: you can now have recursive object types (not thoroughly tested)\n- bugfix: you can now have #load and other directives\n\n02/19/06 version 0.81\n- added the 'ppcompose' utility\n- bugfix: you can now have a character literal on its own line\n- bugfix: you can now have a labelled or optional argument on its own line\n- bugfix: you can now have a polymorphic variant constructor on its own line\n\n11/21/05 version 0.8\n- initial release. works quite well, but the preprocessor's handling\nof various pathological cases of syntax involving objects, module\nsignatures, and functors has not been rigorously tested.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmlin%2Ftwt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmlin%2Ftwt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmlin%2Ftwt/lists"}