The Egel Language
DIR home1: #!/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 "&") "&" |> Regex::replace_all (Regex::compile "<") "<" 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: )