The Egel Language

DIR home
    1: #!/usr/local/bin/egel
    2: 
    3: # This program is free software: you can redistribute it and/or modify
    4: # it under the terms of the GNU General Public License as published by
    5: # the Free Software Foundation, either version 3 of the License, or (at
    6: # your option) any later version.
    7: #
    8: # This program is distributed in the hope that it will be useful, but
    9: # WITHOUT ANY WARRANTY; without even the implied warranty of
   10: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   11: # General Public License for more details.
   12: #
   13: # You should have received a copy of the GNU General Public License
   14: # along with this program.  If not, see <http://www.gnu.org/licenses/>.
   15: 
   16: import "prelude.eg"
   17: import "dictionary.eg"
   18: 
   19: using System
   20: using List
   21: 
   22: def main = Serf::start
   23: 
   24: namespace Serf (
   25: 
   26:     def start =
   27:         if elem "start" args then start_servers
   28:         else if elem "gopher" args then serve_gopher_request
   29:         else if elem "www" args then serve_www_request
   30:         else if elem "debug" args then debug
   31:         else print "Usage: serf.eg [start|gopher|www|debug]\n"
   32: 
   33:     def start_gopher_server =
   34:         OS::exec "while true ; do nc -w 5 -4 -l 192.168.2.7 1470 -c \"egel serf.eg gopher\"; sleep 1 ; done"
   35: 
   36:     def start_www_server =
   37:         OS::exec "while true ; do nc -w 5 -4 -l 192.168.2.7 1480 -c \"egel serf.eg www\"; sleep 1 ; done"
   38: 
   39:     def start_servers =
   40:         let S0 = async [_ -> start_gopher_server] in
   41:         let S1 = async [_ -> start_www_server] in 
   42:             await S0; await S1
   43: )
   44: 
   45: namespace Serf (
   46:     def serve_gopher_request = serve_selector ((OS::read_line OS::stdin) |> trim_crlf |> trim_lead)
   47: 
   48:     def trim_crlf = do String::to_chars |> break [C -> or (C == '\n') (C == '\r')] |> fst |> String::from_chars
   49: 
   50:     def trim_lead = [ S -> if String::starts_with "/" S then String::remove 0 1 S else S ]
   51: 
   52:     def split_lines = Regex::split (Regex::compile "\n")
   53: 
   54:     def serve_selector = do db_page |> to_gopher |> [T -> print T]
   55: 
   56:     def gopher_line =
   57:         [S -> "i" + S + "\tfake\t(NULL)\t0\r\n"]
   58: 
   59:     def code_numbers =
   60:         do  split_lines
   61:             |> [LL -> zip_with (+) (map [N -> format " {:4}: " N] (from_to 1 (length LL + 1))) LL]
   62: 
   63:     def to_gopher =
   64:         [{} -> "" |{X|XX} -> to_gopher X + to_gopher XX
   65:         |(h0 S) ->   gopher_line ("# " + S)
   66:         |(h1 S) ->   gopher_line ("## " + S)
   67:         |(h2 S) ->   gopher_line ("### " +  S)
   68:         |(line S) -> gopher_line S
   69:         |hr       -> gopher_line ""
   70:         |(code S) -> map gopher_line (code_numbers S) |> foldr (+) ""
   71:         |(link S T) -> "1" + S + "\t/" + T + "\tgopher.egel.dev\t70\r\n" 
   72:         |(url S T) -> "h" + S + "\tURL:" + T + "\tgopher.egel.dev\t70\r\n" ]
   73: )
   74: 
   75: namespace Serf (
   76: 
   77:     def to_request = do Regex::split (Regex::compile " ") |> [{V,U|_} -> (V, trim_lead U)| _ -> none]
   78: 
   79:     def serve_www_request = serve_verb ((OS::read_line OS::stdin) |> trim_crlf |> to_request)
   80: 
   81:     def serve_verb =
   82:         [("GET", URL) -> if db_has URL then db_page URL |> to_html |> serve_page else serve_verb none
   83:         |_ -> db_unknown |> to_html |> serve_lost ]
   84: 
   85:     def serve_page =
   86:         [ T ->
   87:              print "HTTP/1.1 200 OK\r\n";
   88:              print "Content-Type: text/html\r\n\r\n";
   89:              print_html T ]
   90: 
   91:     def serve_lost =
   92:         [ T ->
   93:              print "HTTP/1.1 404 Not Found\r\n";
   94:              print "Content-Type: text/html\r\n\r\n";
   95:              print_html T ]
   96: 
   97:     def print_html =
   98:         [T ->
   99:              print "<!DOCTYPE html>";
  100:              print "<html dir=\"ltr\">\n";
  101:              print html_header;
  102:              print "<body><pre>" T "</pre></body>\n";
  103:              print "</html>\n" ]
  104: 
  105:     def html_header = """
  106: <head>
  107: <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  108: <title>The Egel Language</title>
  109: <style type="text/css">
  110: a { text-decoration: none; } a:hover { text-decoration: underline; }
  111: pre { overflow: auto; }
  112: @media (prefers-color-scheme: dark) { body { background-color: #000; color: #bdbdbd; color-scheme: dark; } a { color: #56c8ff; } }
  113: </style>
  114: <meta content="width=device-width" name="viewport" />
  115: </head>
  116: """
  117: 
  118:     def html_escape = do Regex::replace_all (Regex::compile "&") "&amp;" |> Regex::replace_all (Regex::compile "<") "&lt;"
  119: 
  120:     def to_html =
  121:         [{} -> "" |{X|XX} -> to_html X + to_html XX
  122:         |(h0 S) ->     "<h1>" + S + "</h1>\n"
  123:         |(h1 S) ->     "       <b>" + S + "</b>\n"
  124:         |(h2 S) ->     "       <i>" +  S + "</i>\n"
  125:         |hr ->         "\n"
  126:         |(line S) ->   "       " + S + "\n"
  127:         |(code S) ->   "       <pre><code>" + (foldr [S0 S1 -> S0 + "\n" + S1] "" (code_numbers (html_escape S))) + "</code></pre>\n"
  128:         |(link S T) -> "   DIR <a href=\"/" + T + "\">" + S + "</a>\n" 
  129:         |(url S T)  -> "  HTML <a href=\"" + T + "\">" + S + "</a>\n"]
  130: )
  131: 
  132: namespace Serf (
  133: 
  134:     def debug = map [K -> print "selector: " K "\n"; print (to_txt (db_page K))] db_keys; none
  135: 
  136:     def to_txt =
  137:         [{} -> "" |{X|XX} -> to_txt X + "\n" + to_txt XX
  138:         |(h0 S)   -> "# " + S + "\n"
  139:         |(h1 S)   -> "## " + S + "\n"
  140:         |(h2 S)   -> "### " +  S + "\n"
  141:         |hr       ->  "\n"
  142:         |(line S) -> S + "\n"
  143:         |(code S) -> S + "\n"
  144:         |(link S T) -> S + " -> " + T
  145:         |(url S T)  -> S + " => " + T ]
  146: )
  147: 
  148: namespace Serf (
  149:     data h0, h1, h2, line, hr, code, link, url, action
  150: 
  151:     val db = Dict::from_list db_pages
  152: 
  153:     def db_has = Dict::has db
  154: 
  155:     def db_get = Dict::get db
  156: 
  157:     def db_keys = db_pages |> map fst
  158: 
  159:     def db_subs = [K -> 
  160:         db_keys |> filter (String::starts_with K) 
  161:                 |> filter [S -> and (S /= K) 
  162:                               ((String::index_of "/" (String::remove 0 (String::count K + 1) S)) < 0)] ]
  163: 
  164:     def db_page = [K -> 
  165:         if db_has K then {h0 "The Egel Language", link "home" "", hr, db_get K |> db_action, hr 
  166:                          | db_subs K |> map [K -> link K K]} 
  167:         else db_unknown]
  168: 
  169:     def db_action = [{} -> {} | {X|XX} -> map db_action {X|XX} |(action F) -> F none|X -> X]
  170: 
  171:     def db_pages =
  172:     {
  173:         ("", {line "Egel is an untyped concurrent functional scripting language based on eager",
  174:               line "combinator rewriting with a concise but remarkably powerful syntax.",
  175:               hr,
  176:               url "interpreter" "https://github.com/egel-lang/"}),
  177:         ("manual", action [_ -> code (OS::exec "MANWIDTH=80 man egel")]),
  178:         ("combinators", action [_ -> code (OS::exec "cat combinators.md")]),
  179:         ("self",   action [_ -> code (OS::read_all (OS::open_in "serf.eg"))])
  180:     }
  181: 
  182:     def db_unknown = {h0 "Uhoh", line "You seem to be lost!"}
  183: )