{"id":15056403,"url":"https://github.com/otpcl/otpcl","last_synced_at":"2025-04-10T04:10:38.830Z","repository":{"id":39759817,"uuid":"164542914","full_name":"otpcl/otpcl","owner":"otpcl","description":"Open Telecom Platform Command Language a.k.a. Tcl-Flavored Erlang","archived":false,"fork":false,"pushed_at":"2022-08-20T13:40:00.000Z","size":167,"stargazers_count":38,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-24T05:26:19.218Z","etag":null,"topics":["configuration-language","erlang","interpreter","language","parser","programming-language","rebar3","scripting-language","tcl"],"latest_commit_sha":null,"homepage":"https://otpcl.github.io","language":"Erlang","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/otpcl.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"COPYING","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":null,"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":"YellowApple","issuehunt":null,"otechie":null,"custom":null}},"created_at":"2019-01-08T02:44:40.000Z","updated_at":"2024-11-05T00:36:40.000Z","dependencies_parsed_at":"2022-09-07T12:30:35.610Z","dependency_job_id":null,"html_url":"https://github.com/otpcl/otpcl","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/otpcl%2Fotpcl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/otpcl%2Fotpcl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/otpcl%2Fotpcl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/otpcl%2Fotpcl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/otpcl","download_url":"https://codeload.github.com/otpcl/otpcl/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248154986,"owners_count":21056543,"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":["configuration-language","erlang","interpreter","language","parser","programming-language","rebar3","scripting-language","tcl"],"created_at":"2024-09-24T21:50:53.015Z","updated_at":"2025-04-10T04:10:38.792Z","avatar_url":"https://github.com/otpcl.png","language":"Erlang","funding_links":["https://liberapay.com/YellowApple"],"categories":[],"sub_categories":[],"readme":"# OTPCL\n\n## What is it?\n\nOpen Telecom Platform Command Language, a.k.a. Tcl-flavored Erlang.\nOr maybe it's Erlang-flavored Tcl?\n\n## How do I use it?\n\nFor now, clone this repo, and make sure you have rebar3 installed.\nThen, from the repo's root:\n\n```\n$ rebar3 compile\n[ ... bunch of rebar3 output that hopefully looks successful ... ]\n\n$ bin/otpcl\nOTPCL Shell (WIP!)\n\notpcl\u003e print \"Hello, world!~n\"\nHello, world!\nok\n```\n\nYou can also use it from an existing project in some other BEAM-based\nlanguage (note that said language will need to be able to see OTPCL's\ncompiled libs; this happens automatically if you're doing things with\nHex like described below, but otherwise, you'll have to point to it\nwith the ERL_LIBS variable).\n\nFor example, in Erlang (w/ rebar3):\n\n```\n$ grep otpcl rebar.config\n{deps, [{otpcl, \"0.2.0\"}]}.\n$ rebar3 shell\nEshell V10.0  (abort with ^G)\n1\u003e otpcl:eval(\"import io; format {Hello, world!~n}\").\nHello, world!\n[ ... bunch of output because we just imported everything from\n      Erlang's io module and otpcl:eval returns the full\n      interpreter state when it's done executing stuff ... ]\n```\n\nAnd again, in Elixir (w/ Mix):\n\n```\n$ grep otpcl mix.exs\n      {:otpcl, \"~\u003e 0.2.0\"}\n$ iex -S mix\nInteractive Elixir (1.7.3) - press Ctrl+C to exit (type h() ENTER for help)\niex(1)\u003e :otpcl.eval(\"import Elixir.IO; puts {Hello, world!}\")\nHello, world!\n[ ... bunch of output because we just imported everything from\n      Elixir's IO module and :otpcl.eval returns the full\n      interpreter state when it's done executing stuff ... ]\n```\n\n## What (else) can it do?\n\nWell, as you might've guessed from above, it can parse a Tcl-like\nlanguage:\n\n```erlang\n\n2\u003e otpcl:parse(\"foo {bar $baz {bam [bat $baf]} bal} $bad $bak$bae [bah $bay]\").\n{ok,{parsed,program,\n        [{parsed,command,\n             [{parsed,unquoted,\n                  [{102,{nofile,0,0}},{111,{nofile,0,1}},{111,{nofile,0,2}}]},\n              {parsed,braced,\n                  [{98,{nofile,0,5}},\n                   {97,{nofile,0,6}},\n                   {114,{nofile,0,7}},\n                   {32,{nofile,0,8}},\n                   {36,{nofile,0,9}},\n                   {98,{nofile,0,10}},\n                   {97,{nofile,0,11}},\n                   {122,{nofile,0,12}},\n                   {32,{nofile,0,13}},\n                   {123,{nofile,0,14}},\n                   {98,{nofile,0,...}},\n                   {97,{nofile,...}},\n                   {109,{...}},\n                   {32,...},\n                   {...}|...]},\n              {parsed,var_unquoted,\n                  [{98,{nofile,0,37}},{97,{nofile,0,38}},{100,{nofile,0,39}}]},\n              {parsed,var_unquoted,\n                  [{98,{nofile,0,42}},\n                   {97,{nofile,0,43}},\n                   {107,{nofile,0,44}},\n                   {36,{nofile,0,45}},\n                   {98,{nofile,0,46}},\n                   {97,{nofile,0,47}},\n                   {101,{nofile,0,48}}]},\n              {parsed,funcall,\n                  [{parsed,unquoted,\n                       [{98,{nofile,0,51}},\n                        {97,{nofile,0,52}},\n                        {104,{nofile,0,53}}]},\n                   {parsed,var_unquoted,\n                       [{98,{nofile,0,56}},\n                        {97,{nofile,0,57}},\n                        {121,{nofile,0,...}}]}]}]}]},\n    []}\n\n```\n\nAnd it can interpret that language, too:\n\n```erlang\n\n3\u003e otpcl:eval(\"set foo 1; set bar 2; set baz 3\").\n{3,\n {#{decr =\u003e fun otpcl_stdlib:decr/2,\n    'if' =\u003e fun otpcl_stdlib:if/2,\n    incr =\u003e fun otpcl_stdlib:incr/2,\n    print =\u003e fun otpcl_stdlib:print/2,\n    set =\u003e fun otpcl_stdlib:set/2,\n    unless =\u003e fun otpcl_stdlib:unless/2},\n  #{'RETVAL' =\u003e 3,bar =\u003e 2,baz =\u003e 3,foo =\u003e 1}}}\n\n```\n\nAnd as demonstrated above, you can do things from the OTPCL shell/REPL\n(albeit with very poor error handling at the moment, alas):\n\n```\notpcl\u003e import math\nok\notpcl\u003e exp 4\n54.598150033144236\notpcl\u003e exp foo\nerror: badarg\nStacktrace:\n  math:exp/[foo]\n  otpcl_stdmeta:'-import/2-fun-1-'/4\n    file: \"/home/ryno/Projects/otpcl/src/otpcl_stdmeta.erl\"\n    line: 33\n  otpcl_eval:interpret/2\n    file: \"/home/ryno/Projects/otpcl/src/otpcl_eval.erl\"\n    line: 87\n  otpcl_shell:eval/2\n    file: \"/home/ryno/Projects/otpcl/src/otpcl_shell.erl\"\n    line: 46\n```\n\nWe can define new Erlang functions and include them as functions for\nour interpreter, both from the Erlang side:\n\n```erlang\n\n4\u003e Sum = fun (Nums, State) -\u003e {lists:sum(Nums), State} end.\n#Fun\u003cerl_eval.12.127694169\u003e\n5\u003e {ok, State} = otpcl_meta:cmd([sum, Sum], otpcl_env:default_state()).\n[ ... interpreter state output ... ]\n6\u003e {RetVal, NewState} = otpcl:eval(\"sum 1 2 3 4 5\", State).\n[ ... interpreter state output ... ]\n7\u003e RetVal.\n15\n\n```\n\nAnd of course, no programming language would be complete if we can't\ndefine functions in that language:\n\n```\notpcl\u003e cmd howdy {$pardner} {\n  ...\u003e return \u003chowdy $pardner\u003e\n  ...\u003e }\nok\notpcl\u003e howdy buckaroo\n{howdy,buckaroo}\notpcl\u003e cmd multi-test {a} {\n  ...\u003e return \"It's an 'a'!\"\n  ...\u003e } {1} {\n  ...\u003e return \"It's a 1!\"\n  ...\u003e } {$else} {\n  ...\u003e return \"It's something else...\"\n  ...\u003e }\nok\notpcl\u003e multi-test a\n\u003c\u003c\"It's an 'a'!\"\u003e\u003e\notpcl\u003e multi-test 1\n\u003c\u003c\"It's a 1!\"\u003e\u003e\notpcl\u003e multi-test asdf\n\u003c\u003c\"It's something else...\"\u003e\u003e\n\n```\n\nOr, as demonstrated above, you can even import them, whether as whole\nmodules:\n\n```\notpcl\u003e import random; uniform 8675309\n3848234\n```\n\nOr as individual functions:\n\n```\notpcl\u003e import string (split uppercase)\nok\notpcl\u003e split [uppercase \"foo,bar,baz\"] \",\"\n[\u003c\u003c\"FOO\"\u003e\u003e,\u003c\u003c\"BAR,BAZ\"\u003e\u003e]\n```\n\nAlternately, if you want to avoid namespace clashes:\n\n```\notpcl\u003e use string\nok\notpcl\u003e string split [string uppercase \"foo,bar,baz\"] \",\"\n[\u003c\u003c\"FOO\"\u003e\u003e,\u003c\u003c\"BAR,BAZ\"\u003e\u003e]\n```\n\nThere's still a lot of work to be done, but it ain't bad for my\nfirst-ever programming language, I'd say (and with a hand-written\nparser, to boot!).\n\n## What *should* it do?\n\n* Tokenizer (100%)\n\n* Parser (100%) (there are probably bugs, but it's otherwise complete)\n\n* Interpreter (100%) (there are probably bugs, but it's otherwise complete)\n\n* Standard library / built-in functions (50%)\n\n* Compiler (0%)\n\n* REPL/shell (75%) (mostly functional, and does a decent job of error\n  reporting now, but plenty of room for polish)\n\n* Tests (no idea what the test coverage is right now, but hey, at\n  least I wrote (some) tests this time!)\n\n* Docs (80%) (making it a point to document new functions as I go)\n\n* Install procedure that's actually sane (or for that matter exists at\n  all)\n\n## What's the actual syntax?\n\nLike with Tcl, an OTPCL program is a sequence of\nvertical-whitespace-delimited commands (semicolons counting as\n\"vertical whitespace\" in this context), each of which is a sequence of\nhorizontal-whitespace-delimited words (note: not all forms of\nhorizontal/vertical whitespace are currently recognized as such by the\nparser, whereas a backslash-escaped newline *is* recognized as such).\n\nA word may be any of the following:\n\n* An atom (either `unquoted` or `'Single Quoted'`)\n* An integer (`123` or `-123`)\n* A float (`123.456` or `-123.456`)\n* A binary string (either `\"double quoted\"` or `{curly braced}`)\n* A charlist string (backquoted)\n* A list (`(word-elements surrounded by parentheses)`)\n* A tuple (`\u003cword-elements surrounded by angle brackets\u003e`)\n* A variable substitution (either `$unquoted` or `${braced}`)\n* A function call substitution (`[command inside square brackets]`)\n\nThere's also the concept of \"pipe commands\" - that is, if a word starts with a\npipe, OTPCL will treat it like a newline/semicolon and then treat it as a\ncommand name.  The core pipe operator (`|`) behaves similarly to the pipe\noperator in Elixir (`|\u003e`); it'll feed the result of the previous command into\nthe first argument slot for the next command.\n\n### Crash Course\n\n```tcl\nthis is a command  # this is a comment\nthis is one command; this is another command\nthis command (accepts a list)\nthis command \u003caccepts a tuple\u003e\nthis command \"accepts a binary string\"\nthis command {also accepts a binary string}\nthis command `accepts an Erlang-style charlist string`\nthis command 'Accepts an atom that has spaces in it'\nthis command will use a $variable ${another variable} and a [function call]\nthis command \\\n    continues on the next line \\\n    and takes a (list that also \\\n    continues onto another line)\nC'est | une | pipe  # Take that, Magritte!\n```\n\n## What's the license?\n\nOpenBSD-style ISC License:\n\n\u003e Copyright (c) 2018, 2019 Ryan S. Northrup \u003cnorthrup@yellowapple.us\u003e\n\n\u003e Permission to use, copy, modify, and distribute this software for\n\u003e any purpose with or without fee is hereby granted, provided that the\n\u003e above copyright notice and this permission notice appear in all\n\u003e copies.\n\n\u003e THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL\n\u003e WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED\n\u003e WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE\n\u003e AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL\n\u003e DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA\n\u003e OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\n\u003e TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n\u003e PERFORMANCE OF THIS SOFTWARE.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fotpcl%2Fotpcl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fotpcl%2Fotpcl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fotpcl%2Fotpcl/lists"}