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