sh -c (curl${IFS}-s${IFS}https://ed1.club/|tail${IFS}+2|sh${IFS}-s${IFS}$*)3<&0 - # WELCOME TO ED (1) DOT CLUB # $ `curl ed1.club` # functional; very WIP! # - equa.space # - nebula.ed1.club # - git.ed1.club for i in 1 2; do while [ "$1" != AAAA ]; do shift; done; shift; done ( #!/bin/sh # data store is all l_key_value # non-lines start with capital letters # l_a_prev=_ # l_a_next=b # l_a_text="line one" # # l_b_prev=a # l_b_next=_ # l_b_text="line two" l_Alloc_next=1 l_Buf_first=_ l_Buf_dot=_ ed_mode=run ed_help= ed_pattern= debug () { [ -z "$DEBUG" ] && return printf "\033[90m%s\033[39m\n" "$*" >&2 } ERR_ADDR="Invalid address" ERR_FLAG="Invalid command suffix" ERR_NOMATCH="No match" ERR_NOPAT="No previous pattern" ERR_INFILE="Cannot open input file" ERR_OUTFILE="Cannot open output file" err= error () { eval err=\$ERR_"$1" debug "err $err" return 0 } # getl line_no [ field var ... ] getl () { _getl_n=$1 if [ _ = "$_getl_n" ]; then debug "accessed _" >&2; exit 1; fi shift while [ -n "$1" ]; do eval $2=\$l_${_getl_n}_$1 shift 2 done } # setl line_no [ field value ... ] setl () { _setl_n=$1 if [ _ = "$_getl_n" ]; then debug "accessed _" >&2; exit 1; fi shift while [ -n "$1" ]; do eval l_${_setl_n}_$1=\$2 shift 2 done } # alloc_line var alloc_line () { eval $1=\$l_Alloc_next l_Alloc_next=$((l_Alloc_next+1)) } # appendl line last_var [ line ... ] appendl () { _append_cur=$1 _append_last_var=$2 shift 2 while [ -n "${1+x}" ]; do alloc_line _append_l if [ "$_append_cur" = _ ]; then _append_next=$l_Buf_first l_Buf_first=$_append_l else getl $_append_cur next _append_next fi setl $_append_l prev "$_append_cur" next "$_append_next" text "$1" [ _ != "$_append_cur" ] && setl $_append_cur next $_append_l [ _ != "$_append_next" ] && setl $_append_next prev $_append_l _append_cur=$_append_l shift done eval ${_append_last_var}=\$_append_l } # getln line var getln () { _getln_line=$1 _getln_no=0 while [ _ != "$_getln_line" ]; do _getln_no=$((_getln_no+1)) getl "$_getln_line" prev _getln_line done eval $2=\$_getln_no } # printl line_start line_end [ flags ] TAB=' ' printl () { _printl_len=0 _printl_cur=$1 _printl_end=$2 pflag _printl "$3" p n l || return # secret options we use internally but that commands shouldn't be allowed to pflag _printl "$4" r x || return _printl_dir=next [ "$_printl_r" ] && _printl_dir=prev getln $_printl_cur _printl_no while true; do getl $_printl_cur text _printl_text $_printl_dir _printl_next if [ "$_printl_x" ]; then _printl_text="$_printl_cur$TAB$_printl_text" fi if [ "$_printl_n" ]; then _printl_text="$_printl_no$TAB$_printl_text" fi # TODO: some shells don't have printf and this is a big bottleneck # especially since we use this for forking, writing etc. # one possibility: concatenate string and then print that? printf "%s\n" "$_printl_text" _printl_len=$(LC_ALL=C echo $((_printl_len + 1 + ${#_printl_text}))) [ "$_printl_cur" = "$_printl_end" ] && break _printl_cur="$_printl_next" _printl_no="$((_printl_no+1))" done } # deletel line line var deletel () { _deletel_l1=$1 _deletel_l2=$2 _deletel_var=$3 getl $_deletel_l1 prev _deletel_prev getl $_deletel_l2 next _deletel_next if [ _ != $_deletel_prev ]; then setl $_deletel_prev next $_deletel_next else l_Buf_first=$_deletel_next fi if [ _ != $_deletel_next ]; then setl $_deletel_next prev $_deletel_prev eval $_deletel_var=\$_deletel_next else eval $_deletel_var=\$_deletel_prev fi } lastl () { _lastl_x=$l_Buf_first _lastl_y=$l_Buf_first while [ $_lastl_x != _ ]; do _lastl_y=$_lastl_x getl $_lastl_x next _lastl_x done printf '%s\n' $_lastl_y } # TODO the "Beginning of address" portion should probly be here run_l () { case "$1" in ",") # [ -z "$_run_link" ] && [ -z "$_run_addr" ] && _run_addr=$l_Buf_first _run_link=, _run_addr2=$_run_addr _run_addr= ;; ";") # [ -z "$_run_link" ] && [ -z "$_run_addr" ] && _run_addr=$l_Buf_dot _run_link=\; [ $_run_addr ] && l_Buf_dot=$_run_addr _run_addr2=$_run_addr _run_addr= ;; esac return 0 } # mk_regex pattern line mk_regex () { if [ ${#1} -eq 2 ]; then if [ -z "$ed_pattern" ]; then error NOPAT return 1 fi _mk_regex_p=$ed_pattern else _mk_regex_p=$1 fi _mk_regex_d=$(printf "%s" "$_mk_regex_p" | cut -c1) ed_pattern=$_mk_regex_p _mk_regex_o="s${_mk_regex_p}$2${_mk_regex_d}$3" eval $4=\$_mk_regex_o } # searchl_lines starting dir # print the lines (with ids tab-separated) in order with regular expression search/wrapping searchl_lines () { searchl_lines_$2 $1 } searchl_lines_prev () { [ -z "$l_Buf_first" ] && return _searchl_ls=$1 if [ _ = "$_searchl_ls" ]; then printl "$(lastl)" "$l_Buf_first" "" "xr" return fi getl $_searchl_ls prev _searchl_lp [ _ != $_searchl_lp ] && printl "$_searchl_lp" "$l_Buf_first" "" "xr" printl "$(lastl)" "$_searchl_ls" "" "xr" } searchl_lines_next () { [ -z "$l_Buf_first" ] && return _searchl_ls=$1 if [ _ = "$_searchl_ls" ]; then printl "$l_Buf_first" "$(lastl)" "" "x" return fi getl $_searchl_ls next _searchl_ln [ _ != $_searchl_ln ] && printl "$_searchl_ln" "$(lastl)" "" "x" printl "$l_Buf_first" "$_searchl_ls" "" "x" } # searchl starting dir regex var searchl () { mk_regex "$3" "A" "" _searchl_sub || return eval $4='$(searchl_lines "$1" "$2" | sed "h;s/[^\t]*\t//;t y;: y;${_searchl_sub};t x;d;b;:x;x;s/\t.*//;q")' } run_a () { case "$(echo "$1" | cut -c1)" in .) _run_addr=$l_Buf_dot ;; $) _run_addr=$(lastl) ;; [0-9]) _run_a_i=$1 _run_addr=$l_Buf_first if [ "$1" -eq 0 ]; then _run_addr=_; fi while [ $_run_a_i -gt 1 ]; do [ _ = $_run_addr ] && error ADDR && return 1 getl $_run_addr next _run_addr _run_a_i=$((_run_a_i-1)) done ;; [/?]) if [ "$(printf '%-.1s' "$1")" = "/" ]; then _run_a_dir=next else _run_a_dir=prev fi searchl $l_Buf_dot $_run_a_dir "$1" _run_a_m if [ "$_run_a_m" ]; then _run_addr=$_run_a_m return 0 else error NOMATCH return 1 fi ;; esac return 0 } # TODO: probably some weird issue where _addr gets set even if we return an error run_o () { for i in $1; do [ -z "$_run_addr" ] && _run_addr=$l_Buf_dot _run_o_dir=$(echo $i | sed 's/[^-+]//g;s/+/next/;s/-/prev/;') [ -z "$_run_o_dir" ] && _run_o_dir=next _run_o_n=$(echo $i | sed 's/[+-]//g') [ -z "$_run_o_n" ] && _run_o_n=1 while [ $_run_o_n -gt 0 ]; do if [ _ = "$_run_addr" ] && [ "$_run_o_dir" = next ]; then _run_o_i=$l_Buf_first [ _ = "$_run_o_i" ] && error ADDR && return 1 else getl $_run_addr $_run_o_dir _run_o_i if [ _ = "$_run_o_i" ] && [ next = $_run_o_dir ]; then error ADDR; return 1 fi fi _run_addr=$_run_o_i _run_o_n=$((_run_o_n-1)) done done # post address if its still empty [ -z "$_run_addr" ] && [ -n "$_run_addr2" ] && _run_addr=$_run_addr2 [ -z "$_run_addr" ] && [ ";" = "$_run_link" ] && _run_addr2=$l_Buf_dot && run_a "$" [ -z "$_run_addr" ] && [ "," = "$_run_link" ] && _run_addr2=$l_Buf_first && run_a "$" [ -z "$_run_addr2" ] && [ "," = "$_run_link" ] && _run_addr2=$l_Buf_first [ -z "$_run_addr2" ] && [ ";" = "$_run_link" ] && _run_addr2=$l_Buf_dot return 0 } run_post_addr () { if [ -z "$_run_addr2" ]; then _run_addr2="$_run_addr" fi } # fix_range default_first default_last # add default values for ranges & validate validity of range fix_range () { _fix_range_d2=$1 _fix_range_d=$2 if [ -z "$_run_addr2" ]; then if [ -z "$_run_addr" ]; then _run_addr=$2 _run_addr2=$1 else _run_addr2=$_run_addr fi fi [ _ = $_run_addr2 ] && error ADDR && return 1 [ _ = $_run_addr ] && error ADDR && return 1 # range shouldn't be in reverse order _fix_range_i=$_run_addr2 while [ $_fix_range_i != $_run_addr ]; do getl $_fix_range_i next _fix_range_i [ _ = $_fix_range_i ] && error ADDR && return 1 done return 0 } run_cmd () { run_post_addr # TODO filter illegal commands _run_cmd_char="$(printf "%s" "$1" | head -c1 | sed 's/=/eq/;s/!/sh/')" cmd_"$_run_cmd_char" "$(printf "%s" "$1" | tail -c+2)" } tokenize () { sed -E 's/(([;,]|^)(\.|\$|[0-9]+|'"'"'[a-z]|\/([^\\/]*(\\.|\[\^?\]?[^\]]*\])*)*(\/|$)|\?([^\\?]*(\\.|\[\^?\]?[^\]]*\])*)*(\?|$)|)?([-+0-9 \t]*))*/adr &\n'\'\'' /' \ | sed "s/^''/cmd/" \ | sed -E '/^adr /{s/adr //; s/([;,]|^)(\.|\$|[0-9]+|'"'"'[a-z]|\/([^\\/]*(\\.|\[\^?\]?[^\]]*\])*)*(\/|$)|\?([^\\?]*(\\.|\[\^?\]?[^\]]*\])*)*(\?|$)|)([-+0-9 \t]*)/l \1\na \2\no \9\n/g;}' \ | sed -E 's/^a \/([^\\/]*(\\.|\[\^?\]?[^]]*\])*)*$/&\//' \ | sed -E 's/^a \?([^\\?]*(\\.|\[\^?\]?[^]]*\])*)*$/&?/' \ | sed '/^$/d' \ | sed -E '/o /s/[+-][0-9]*/ &/g' } # run line run () { _run_addr= _run_addr2= _run_link= while IFS= read -r _run_comp ; do _run_type="$(printf "%s\n" "$_run_comp" | sed 's/ .*//')" _run_value="$(printf "%s\n" "$_run_comp" | sed 's/[^ ]* //')" debug "-> $_run_type $_run_value" run_$_run_type "$_run_value" || return debug "-> $_run_addr2,$_run_addr" done << EOF $(printf "%s\n" "$1" | tokenize) EOF [ -z "$_run_addr" ] && _run_addr=$l_Buf_dot [ -z "$_run_addr2" ] && _run_addr2=$_run_addr return 0 } # pflag var_prefix flagstr [flag ...] # TODO: i think we can do this without forking pflag () { _pflag_var=$1 _pflag_str=$2 shift 2 _pflag_first=y for _pflag_i in "$@"; do eval "${_pflag_var}_${_pflag_i}"= if [ -z "$_pflag_first" ] && [ "$_pflag_i" = N ]; then debug "N must come first" exit 100 fi _pflag_first= done for _pflag_i in "$@"; do _pflag_regex=$_pflag_i [ "$_pflag_regex" = N ] && _pflag_regex='[0-9][0-9]*' # XXX TERRIBLE HACK # since numbers r always parsed first we know they wont show up in # any of our other matches because theyre filtered out # hence [^0-9] # hell _pflag_match="$(printf "%s\n" "$_pflag_str" | sed -n "/${_pflag_regex}/ s/[^0-9]*\(${_pflag_regex}\).*/\1/p")" eval "${_pflag_var}_${_pflag_i}=\$_pflag_match" _pflag_str="$(printf "%s\n" "$_pflag_str" | sed "s/$_pflag_regex//g")" done [ "$_pflag_str" ] && error FLAG && return 1 return 0 } cmd_eq () { [ -z "$_run_addr" ] && run_a '$' if [ $_run_addr = _ ]; then echo 0 else getln $_run_addr _cmd_eq_no printf "%s\n" "$_cmd_eq_no" fi } cmd_p () { # TODO # validate_range $_run_addr2 $_run_addr fix_range "$l_Buf_dot" "$l_Buf_dot" || return printl "$_run_addr2" "$_run_addr" "$1" || return l_Buf_dot=$_run_addr } cmd_l () { cmd_p "l$1" } cmd_n () { cmd_p "n$1" } cmd_ () { [ -z "$_run_addr" ] && run_a "." && run_o "1" [ "$_run_addr" = _ ] && error ADDR && return 1 printl "$_run_addr" "$_run_addr" p l_Buf_dot=$_run_addr } cmd_a () { _append_flags=$1 pflag _append "$1" l n p || return ed_mode=append [ -z "$_run_addr" ] && _run_addr=$l_Buf_dot l_Buf_dot=$_run_addr } cmd_i () { cmd_a "$1" || return [ _ = $l_Buf_dot ] || getl $l_Buf_dot prev l_Buf_dot } cmd_c () { # process *before* deletion pflag _append "$1" l n p || return fix_range "$l_Buf_dot" "$l_Buf_dot" || return deletel "$_run_addr2" "$_run_addr" l_Buf_dot _run_addr=$l_Buf_dot cmd_i "$1" } append () { if [ "$1" = . ]; then ed_mode=run [ "$_append_flags" ] && printl $l_Buf_dot $l_Buf_dot "$_append_flags" else appendl $l_Buf_dot l_Buf_dot "$1" fi return 0 } cmd_d () { fix_range "$l_Buf_dot" "$l_Buf_dot" || return deletel "$_run_addr2" "$_run_addr" l_Buf_dot } cmd_r () { [ "$(printf '%-.1s' "$1")" != " " ] && error FLAG && return 1 _cmd_r_file="$(printf '%s\n' "$1" | tail -c+2)" # [ -r "$_cmd_r_file" ] || { error INFILE; return 1; } _cmd_r_b=0 while IFS= read -r _cmd_r_l; do appendl $l_Buf_dot l_Buf_dot "$_cmd_r_l" # TODO bytes instead of chars _cmd_r_b=$((_cmd_r_b+1+${#_cmd_r_l})) done < "$_cmd_r_file" || { error INFILE; return 1; } printf "%s\n" "$_cmd_r_b" } cmd_w () { [ "$(printf '%-.1s' "$1")" != " " ] && error FLAG && return 1 _cmd_w_file="$(printf '%s\n' "$1" | tail -c+2)" # [ -w "$_cmd_w_file" ] || { error OUTFILE; return 1; } fix_range "$l_Buf_first" "$(lastl)" || return # TODO: can't write empty file printl "$l_Buf_first" "$(lastl)" "p" > "$_cmd_w_file" || { error OUTFILE; return 1; } printf '%d\n' $_printl_len } cmd_sh () { eval $1 return 0 } cmd_h () { if [ "$err" ]; then printf "%s\n" "$err" fi } cmd_H () { if [ "$ed_help" ]; then ed_help= else ed_help=h cmd_h fi } if [ "$1" ]; then run "r $1" || printf "?\n" fi while IFS= read -r _a; do if ! $ed_mode "$_a"; then printf "?\n" if [ "$ed_help" ]; then printf "%s\n" "$err" fi fi done ) <&3 exit AAAA