diff options
Diffstat (limited to 'theco.rex')
-rw-r--r-- | theco.rex | 2178 |
1 files changed, 2178 insertions, 0 deletions
diff --git a/theco.rex b/theco.rex new file mode 100644 index 0000000..984a799 --- /dev/null +++ b/theco.rex @@ -0,0 +1,2178 @@ +#!/usr/local/bin/nthe -p +/* + * Abandoned (Video)TECO implementation on top of The Hessling Editor, written in Open Object Rexx + */ + +if .environment~theco.initialized \= .nil then return + +/* + * Initialize classic Rexx function packages + */ +call ReLoadFuncs + +/* + * THE profile code: THECO initialization + */ +keys = .Array~of(- + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J",- + "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T",- + "U", "V", "W", "X", "Y", "Z", "S-A", "S-B", "S-C", "S-D",- + "S-E", "S-F", "S-G", "S-H", "S-I", "S-J", "S-K", "S-L", "S-M", "S-N",- + "S-O", "S-P", "S-Q", "S-R", "S-S", "S-T", "S-U", "S-V", "S-W", "S-X",- + "S-Y", "S-Z", "0", "1", "2", "3", "4", "5", "6", "7",- + "8", "9", "`", "-", "=", "[", "]", "\", ";", "'",- + ",", ".", "/", ")", "!", "@", "#", "$", "%",- + "^", "&", "*", "(", "~", "_", "+", "{", "}", "|",- + ":", '"', "<", ">", "?", "SPACE", "ESC", "F0", "F1", "F2", "F3",- + "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "S-F1",- + "S-F2", "S-F3", "S-F4", "S-F5", "S-F6", "S-F7", "S-F8", "S-F9", "S-F10", "S-F11",- + "S-F12", "C-F1", "C-F2", "C-F3", "C-F4", "C-F5", "C-F6", "C-F7", "C-F8", "C-F9",- + "C-F10", "C-F11", "C-F12", "A-F1", "A-F2", "A-F3", "A-F4", "A-F5", "A-F6", "A-F7",- + "A-F8", "A-F9", "A-F10", "A-F11", "A-F12", "F13", "F14", "F15", "F16", "F17",- + "F18", "F19", "F20", "S-F13", "S-F14", "S-F15", "S-F16", "S-F17", "S-F18", "S-F19",- + "NUM0", "NUM1", "NUM2", "NUM3", "NUM4", "CENTER", "NUM6", "NUM7", "NUM8", "NUM9",- + "CURU", "CURD", "CURL", "CURR", "ENTER", "ENTER", "TAB", "HOME", "PGUP",- + "PGDN", "END", "INS", "DEL", "PLUS", "MINUS", "SLASH", "STAR", "NUMENTER", "NUMSTOP",- + "S-HOME", "S-END", "S-CURL", "S-CURR",- + "C-TAB", "C-HOME", "C-PGUP", "C-PGDN", "C-CURU", "C-CURD",- + "C-CURL", "C-CURR", "C-END",- + "S-TAB", "S-INS", "SELECT", "PRINT", "S-PRINT", "FIND", "S-FIND", "SUSPEND", "S-SUSPEND",- + "CLEAR", "OPTIONS", "S-OPTIONS", "BREAK", "CANCEL", "S-CANCEL", "HELP", "S-HELP", "S-TAB",- + "S-INS", "EXIT", "CURD",- + "CURU", "CURL", "CURR", "BACKSPACE", "HOME", "PF1", "PF2", "PF3", "PF4", "NUMENTER",- + "MINUS", "NUMSTOP", "COMMA", "STAR", "PLUS", "SLASH", "S-TAB", "FIND", "INS", "REMOVE",- + "DEL", "SELECT", "PGUP", "PGDN", "TAB", "ENTER", "TAB", "RETURN", "CSI", "BREAK",- + "DL", "IL", "DC", "INS", "EIC", "CLEAR", "EOS", "EOL", "SF", "SR",- + "PGDN", "PGUP", "S-TAB", "C-TAB", "CATAB", "ENTER", "S-RESET", "RESET", "PRINT", "LL",- + "A1", "A3", "B2", "C1", "C3", "S-TAB", "BEG", "CANCEL", "CLOSE", "COMMAND",- + "COPY", "CREATE", "END", "EXIT", "FIND", "HELP", "MARK", "MESSAGE", "MOVE", "NEXT",- + "OPEN", "OPTIONS", "PREVIOUS", "REDO", "REFERENCE", "REFRESH", "REPLACE", "RESTART", "RESUME", "SAVE",- + "S-BEG", "S-CANCEL", "S-COMMAND", "S-COPY", "S-CREATE", "S-DC", "S-DL", "SELECT", "S-END", "S-EOL",- + "S-EXIT", "S-FIND", "S-HELP", "S-HOME", "S-INS", "S-CURL", "S-MESSAGE", "S-MOVE", "S-NEXT", "S-OPTIONS",- + "S-PREVIOUS", "S-PRINT", "S-REDO", "S-REPLACE", "S-CURR", "S-RSUME", "S-SAVE", "S-SUSPEND", "S-UNDO", "SUSPEND",- + "UNDO", "C-CURL", "C-CURR", "C-CURU", "C-CURD", "C-HOME", "C-END", "C-PGUP", "C-PGDN", "C-A",- + "C-B", "C-C", "C-D", "C-E", "C-F", "C-G", "C-H", "C-I", "C-J", "C-K",- + "C-L", "C-M", "C-N", "C-O", "C-P", "C-Q", "C-R", "C-S", "C-T", "C-U",- + "C-V", "C-W", "C-X", "C-Y", "C-Z",- +) +'set msgmode off' +do key over keys + 'define' key 'rexx call theco_keypress' stringify(key)';', + '::requires "theco"' +end +'set msgmode on' + +'set cmdline off' +'set insertmode on' + +/* + * configurable by THECO macro + */ +'color filearea white black' +'color pr green black' +'color cpr black green' +'color arrow green black' +'color st black white' +'color to bold green black' +'color cto bold black green' +'color divider black white' +'color idline black white' +'color scale green black' +'color cur reverse' + +'ecolor b yellow black' +'ecolor s white black' +'ecolor f bright cyan on black' +'ecolor i magenta on black' +'ecolor c bright blue on black' +'ecolor d bright green on black' +'ecolor a blue on black' +'ecolor x magenta on black' +'ecolor 5 red on black' +'ecolor 2 bright blue on black' +'ecolor 6 bright green on black' +'ecolor y bright green on black' +'ecolor w bright red on black' + +'set beep on' +'set insertmode on' +/*'reprofile on'*/ +/* NOTE: currently broken on THE v3.3 RC1? */ +'set tabkey tab character' + +/* + * NOTE: control chars broken on THE v3.3 RC1 + * NOTE: setting excape char yields error + * WORKAROUND: reset attribs after escaping the escape char + * WORKAROUND: disable messages for setting escape char + */ +'nomsg set ctlchar \ escape' +'set ctlchar N protect normal' +'set ctlchar R protect reverse' + +.environment~theco.cmdline = "" +.environment~theco.undo = .UndoStackDummy~new +.environment~theco.quit_requested = .false + +.environment~theco.escape = '1B'x +.environment~theco.modifiers.at = .false +.environment~theco.modifiers.colon = .false + +'set reserved -1' echo_cmdline(.environment~theco.cmdline) + +/* + * Parser state machine + */ +s = .Table~new +s["start"] = .StateStart~new +s["start"][""] = "start" +s["start"][" "] = "start" +s["start"]['0D'x] = "start" +s["start"]['0A'x] = "start" +s["start"]["!"] = "label" +s["start"]["^"] = "ctlcmd" +s["start"]["F"] = "fcmd" +s["start"]['"'] = "condcmd" +s["start"]["O"] = "cmd_goto" +s["start"]["Q"] = "qcmd" +s["start"]["U"] = "ucmd" +s["start"]["%"] = "inccmd" +s["start"]["M"] = "mcmd" +s["start"]["E"] = "ecmd" +s["start"]["I"] = "cmd_insert" +s["start"]["S"] = "cmd_search" + +s["label"] = .StateLabel~new +s["label"][""] = "label" + +s["ctlcmd"] = .StateCtlCmd~new +s["ctlcmd"][""] = "ctlcmd" +s["ctlcmd"]["U"] = "ctlucmd" + +s["ctlucmd"] = .StateCtlUCmd~new +s["ctlucmd"][""] = "ctlucmd" + +s["cmd_ctlu"] = .StateCmdCtlU~new + +s["fcmd"] = .StateFCmd~new +s["fcmd"][""] = "fcmd" + +s["condcmd"] = .StateCondCmd~new +s["condcmd"][""] = "condcmd" + +s["cmd_goto"] = .StateCmdGoto~new + +s["qcmd"] = .StateQCmd~new +s["qcmd"][""] = "qcmd" + +s["ucmd"] = .StateUCmd~new +s["ucmd"][""] = "ucmd" + +s["inccmd"] = .StateIncCmd~new +s["inccmd"][""] = "inccmd" + +s["mcmd"] = .StateMCmd~new +s["mcmd"][""] = "mcmd" + +s["ecmd"] = .StateECmd~new +s["ecmd"][""] = "ecmd" +s["ecmd"]["B"] = "cmd_file" +s["ecmd"]["Q"] = "eqcmd" + +s["cmd_file"] = .StateCmdFile~new + +s["eqcmd"] = .StateEQCmd~new +s["eqcmd"][""] = "eqcmd" + +s["cmd_insert"] = .StateCmdInsert~new + +s["cmd_search"] = .StateCmdSearch~new + +.environment~theco.states = s +.environment~theco.state = s["start"] + +/* + * Operator precedence table + * "=" is not a real operator and excluded from comparisons + */ +operators = .Array~of("^*","*","/","^/","+","-","&","#","(","<") +.ArithmeticStack~precedence = .Table~new +do i = 1 to operators~items + .ArithmeticStack~precedence[operators[i]] = i +end +.ArithmeticStack~precedence[.nil] = i + +.ArithmeticStack~operators = .Operators~new + +.environment~theco.stack = .ArithmeticStack~new + +.environment~theco.reg_arg = .nil +/* + * Strings (for storing string arguments) + */ +.environment~theco.strings.1 = "" +.environment~theco.strings.2 = "" + +/* + * Q-Registers + */ +.environment~theco.registers = .Table~new +do c = "A"~c2d to "Z"~c2d + .environment~theco.registers[c~d2c] = .QRegister~new(c~d2c) +end +do c = 0 to 9 + .environment~theco.registers[c] = .QRegister~new(c) +end +/* search string & status (examined by ";" command) */ +.environment~theco.registers["_"] = .QRegister~new("_") +.environment~theco.registers["_"]~integer = 0 /* failure */ + +/* + * THECO labels mapped to program counters + */ +.environment~theco.goto_table = .Table~new + +.environment~theco.pc = 0 +.environment~theco.exec = .true +.environment~theco.skip_else = .false +.environment~theco.skip_label = .nil +.environment~theco.nest_level = 0 + +'locate 1' + +/* + * Execute TECO.INI + */ +input = .Stream~new("teco.ini") +input~open("read") + +if \execute(input~charIn(1, input~chars)) then do + say "Error executing teco.ini" + return +end + +input~close + +.environment~theco.pc = 0 +.environment~theco.undo = .UndoStack~new + +.environment~theco.initialized = .true + +/* + * Main entry point, called on key press + */ +::routine theco_keypress public + use arg key_the + + /* + * Translate THE key to TECO ASCII char + */ + select + when lastkey.2() \== "" then + key_char = lastkey.2()~d2c + when key_the == "BACKSPACE" then + key_char = '08'x + when key_the == "DC" then + /* FIXME: preliminary escape surrogate */ + key_char = '1B'x + otherwise + 'emsg WARNING: Unresolved key' key_the 'ignored' + return + end + + cmdline = sor(.environment~theco.cmdline, "") + + /* + * Process immediate editing commands + */ + insert = "" + select + when key_the == "BACKSPACE" then do + .environment~theco.undo~pop(cmdline~length) + cmdline = cmdline~left(max(cmdline~length - 1, 0)) + .environment~theco.pc = cmdline~length + end + when key_the == "C-T" | key_char == '09'x,- + .environment~theco.state~name == "cmd_file" then do + filename = .environment~theco.strings.1 + insert = filename_complete(filename, .environment~theco.escape) + end + when key_the == "C-T" then do + start = last_match(cmdline, '0D 0A 09'x "<>,;@") + 1 + insert = filename_complete(cmdline~substr(start)) + end + otherwise + insert = key_char + end + + old_cmdline = .environment~theco.cmdline + .environment~theco.cmdline = cmdline + + /* + * Parse/execute characters + */ + do insert_index = 1 to insert~length + cmdline ||= insert~subchar(insert_index) + .environment~theco.cmdline = cmdline + + if \execute(cmdline) then do + .environment~theco.cmdline = old_cmdline + leave insert_index + end + end + + /* + * Echo command line + */ + 'set reserved -1' echo_cmdline(sor(.environment~theco.cmdline, "")) + +/* + * Parse/execute + */ +::routine execute + use arg code + + do while .environment~theco.pc < code~length + .environment~theco.pc += 1 + + c = code~subchar(.environment~theco.pc) + if \.State~input(c) then do + .environment~theco.pc -= 1 /* FIXME */ + + 'emsg Syntax error "'c'"' + return .false + end + end + + return .true + +/* + * Return cmdline in as a reserved line string (for echoing) + */ +::routine echo_cmdline + use arg cmdline + + /* FIXME : could use CHANGESTR() */ + line = "" + do i = 1 to cmdline~length + c = cmdline~subchar(i) + + select + when c == "\" then + line ||= "\\N" + when c == '1B'x then + line ||= "$" + when c == '0D'x then + line ||= "<CR>" + when c == '0A'x then + line ||= "<LF>" + when c == '09'x then + line ||= "<TAB>" + when c~c2d < 32 then + line ||= "^"ctlecho(c) + otherwise + line ||= c + end + end + + half_line = (lscreen.2() - 2) % 2 + line = line~right(min(line~length,- + half_line + line~length // half_line)) + + return "*"line"\R " + +/* + * Complete filename/path (used for autocompletions) + */ +::routine filename_complete + use arg filename, completed=" " + + /* + * Do not complete match specs + */ + if is_matchspec(filename) then return "" + + /* + * Get all files/directorie beginning with `filename` + */ + if SysFileTree(filename"*", "matching.") \= 0 then return "" + if matching.0 = 0 then return "" + + complete_chars = filespec("name", filename)~length + + /* + * Complete the entire filename if possible + */ + matching.1 = get_real_filename(matching.1) + + if matching.0 = 1 then do + if matching.1~right(1) \== get_path_sep() then + matching.1 ||= completed + return matching.1~substr(complete_chars + 1) + end + + /* + * Find the longest common prefix of all matching files/directories + * and complete it + */ + longest_prefix = matching.1~length + longest_file = matching.1~length + + do i = 2 to matching.0 + matching.i = get_real_filename(matching.i) + + longest_prefix = min(longest_prefix,- + matching.i~compare(matching.1) - 1) + longest_file = max(longest_file, matching.i~length) + end + + if longest_prefix > complete_chars then + return matching.1~left(longest_prefix), + ~substr(complete_chars + 1) + + /* + * If no completion is possible, display all matching files + */ + if SysStemSort("matching.") \= 0 then return "" + + screen_width = lscreen.2() + col_length = min(longest_file + 3, screen_width) + + old_msglines = msgline.3() + 'set msgline on = * =' + + line = "" + do i = 1 to matching.0 + if line~length + col_length > screen_width then do + 'msg' line + line = "" + end + line ||= matching.i~left(col_length) + end + 'msg' line + + 'set msgline on =' old_msglines '=' + return "" + +::class UndoToken + ::attribute pos + ::attribute code + + ::method INIT + use arg self~pos, self~code + + ::method run + interpret self~code + +::class UndoStack subclass Queue + ::method push + use arg code + + token = .UndoToken~new(.environment~theco.cmdline~length, code) + self~push:super(token) + + ::method push_cmd + use arg cmd + + self~push(stringify(cmd)) + + ::method pop + use arg pos + + do while self~peek \= .nil, self~peek~pos = pos + self~pull~run + end + +/* + * Undo stack dummy implementation - use when rubout is not required + */ +::class UndoStackDummy + ::method push + ::method push_cmd + ::method pop + +/* + * Class implementing THECO operators, dy default forwarded to the String class + * (by default THECO operator equals Rexx operator) + */ +::class Operators + ::method "/" + return arg(1) % arg(2) + ::method "&" + return arg(1)~d2c~bitAnd(arg(2)~d2c)~c2d + ::method "#" + return arg(1)~d2c~bitOr(arg(2)~d2c)~c2d + ::method "^*" + return arg(1) ** arg(2) + ::method "^/" + return arg(1) // arg(2) + + ::method UNKNOWN + use arg name, arguments + + return arguments[1]~send(name, arguments[2]) + +::class ArithmeticStack + ::attribute precedence class + ::attribute operators class + + /* special value "" is pushed by "," and means: no argument (yet) */ + ::attribute nums + ::attribute ops + + ::attribute num_sign + ::attribute radix + + ::method INIT + self~nums = .Queue~new + self~ops = .Queue~new + + self~num_sign = 1 + self~radix = 10 + + ::method set_radix + use arg radix + + .environment~theco.undo~~push(- + ".environment~theco.stack~radix =" self~radix- + ) + self~radix = radix + + ::method push_num + do while self~nums~peek = "" + self~pop_num + end + + self~push_op("=") + + .environment~theco.undo~~push(- + ".environment~theco.stack~nums~pull"- + ) + forward message "push" to (self~nums) + + ::method pop_num + use arg index=1 + + n = self~~pop_op~nums~remove(index) + if n \= .nil then + .environment~theco.undo~~push(- + ".environment~theco.stack~nums~insert('"n"',"- + isor(index - 1, ".nil")")"- + ) + + return n + + ::method pop_num_calc + use arg index=1, imply=(self~num_sign) + + n = "" + if self~~eval~args > 0 then + n = self~pop_num(index) + if n == "" then + n = imply + + if self~num_sign < 0 then do + .environment~theco.undo~~push(- + ".environment~theco.stack~num_sign = -1"- + ) + self~num_sign = 1 + end + + return n + + ::method add_digit + use arg digit + + n = "" + if self~args > 0 then + n = self~pop_num + + self~push_num(sor(n, 0)*self~radix + self~num_sign*digit) + + ::method push_op + .environment~theco.undo~~push(- + ".environment~theco.stack~ops~pull"- + ) + forward message "push" to (self~ops) + + ::method push_op_calc + use arg op + + /* calculate if op has lower precedence than op on stack */ + if .ArithmeticStack~precedence[self~ops[self~first_op]] <=, + .ArithmeticStack~precedence[op] then self~calc + self~push_op(op) + + ::method pop_op + use arg index=1 + + o = self~ops~remove(index) + if o \= .nil then + .environment~theco.undo~~push(- + ".environment~theco.stack~ops~insert('"o"',"- + isor(index - 1, ".nil")")"- + ) + + return o + + ::method calc + vright = self~pop_num + op = self~pop_op + vleft = self~pop_num + + self~push_num(- + .ArithmeticStack~operators~send(op, vleft, vright)- + ) + + ::method eval + use arg pop_brace=.false + + if self~nums~items < 2 then return + + do label calc forever + n = self~first_op + op = self~ops[n] + + select + when op = .nil | op == "<" then leave calc + when op == "(" then do + if pop_brace then self~pop_op(n) + leave calc + end + otherwise self~calc + end + end calc + + ::method args + do n = 0 while self~ops[n+1] = "="; end + return n + + ::method first_op + do n = 1 to self~ops~items while self~ops[n] = "="; end + return n + + ::method discard_args + do self~~eval~args + self~pop_num_calc + end + +::class QRegister + ::attribute name + + ::attribute integer + ::attribute fileid + + ::method INIT + use arg self~name + self~integer = 0 + + /* + * FIXME: create as pseudo-files if possible + */ + prev_file = efileid.1() + 'nomsg edit' SysTempFileName("THECO.???") + 'add' /* no we are automatically in line 1 */ + self~fileid = efileid.1() + 'edit' prev_file + + ::method edit + .environment~theco.undo~~push_cmd('edit' efileid.1()) + 'edit' self~fileid + + ::method "string" + old_fileid = efileid.1() + 'edit' self~fileid + buffer = get_buffer() + 'edit' old_fileid + + return buffer~toString("l", get_eol()) + + ::method "string=" + use arg val + + old_fileid = efileid.1() + 'edit' self~fileid + + .environment~theco.undo~~push_cmd('edit' old_fileid), + ~~push_cmd('clocate :'column.1()), + ~~push_cmd('locate :'line.1()) + buffer = get_buffer() + do i = 1 to buffer~items + if i > 1 then + .environment~theco.undo~~push_cmd('split cursor') + .environment~theco.undo~~push_cmd('cinsert' buffer[i]) + end + .environment~theco.undo~~push_cmd('add'), + ~~push_cmd('delete *'), + ~~push_cmd('locate :1'), + ~~push_cmd('edit' self~fileid) + + 'locate :1' + 'delete *' + do line over tokenize(val, '0D 0A'x) + 'add' + 'cinsert' line + end + 'locate :1' + + 'edit' old_fileid + +::class State + ::attribute name + ::attribute transitions + + ::method INIT + use arg self~name + self~transitions = .Table~new + + ::method "[]=" + forward to (self~transitions) + + ::method eval_colon class + if \.environment~theco.modifiers.colon then return .false + + .environment~theco.modifiers.colon = .false + .environment~theco.undo~~push(- + ".environment~theco.modifiers.colon = true"- + ) + return .true + + ::method input class + use arg key + + state = .environment~theco.state + + do forever + next_state = state~get_next_state(key) + + /* syntax error */ + if next_state == "" then return .false + + if next_state == state~name then leave + state = .environment~theco.states[next_state] + + key = "" + end + + if next_state \== .environment~theco.state~name then do + .environment~theco.undo~~push(- + ".environment~theco.state ="- + ".environment~theco.states['"||- + .environment~theco.state~name"']"- + ) + .environment~theco.state = state + end + + return .true + + ::method get_next_state + use arg key + + next_state = self~transitions[key~upper] + if next_state = .nil then + next_state = self~custom(key) + + return next_state + + ::method custom abstract + +/* + * Super-class for states accepting string arguments + * Opaquely cares about alternative-escape characters, + * string building commands and accumulation into a string + */ +::class StateExpectString subclass State + ::attribute state class + ::attribute mode class + ::attribute toctl class + + ::method INIT + .StateExpectString~state = "start" + .StateExpectString~mode = "" + .StateExpectString~toctl = .false + + forward class (super) + + ::method save_machine class + .environment~theco.undo~~push(- + ".StateExpectString~state =" stringify(self~state)- + )~~push(- + ".StateExpectString~mode =" stringify(self~mode)- + )~~push(".StateExpectString~toctl =" self~toctl) + + ::method machine class + use arg input + + select + when self~mode == "upper" then + input = input~upper + when self~mode == "lower" then + input = input~lower + otherwise + end + + if self~toctl then do + input = input~upper~bitAnd('3F'x) + self~toctl = .false + end + + select + when self~state == "escaped" then do + self~state = "start" + return input + end + when input == "^" then + self~toctl = .true + when self~state == "start" then do + if input~c2d >= 32 then return input + + echo = ctlecho(input) + select + when echo == "Q" |, + echo == "R" then self~state = "escaped" + when echo == "V" then self~state = "lower" + when echo == "W" then self~state = "upper" + when echo == "E" then self~state = "ctle" + otherwise + return input + end + end + when self~state == "lower" then do + self~state = "start" + + select + when input~c2d < 32, ctlecho(input) == "V" then + self~mode = "lower" + otherwise + return input~lower + end + end + when self~state == "upper" then do + self~state = "start" + + select + when input~c2d < 32, ctlecho(input) == "W" then + self~mode = "upper" + otherwise + return input~upper + end + end + when self~state == "ctle" then do + input = input~upper + + select + when input == "Q" then self~state = "ctleq" + when input == "U" then self~state = "ctleu" + otherwise + return .nil + end + end + when self~state == "ctleq" then do + reg = .environment~theco.registers[input~upper] + if reg = .nil then return .nil + + self~state = "start" + return reg~string + end + when self~state == "ctleu" then do + reg = .environment~theco.registers[input~upper] + if reg = .nil then return .nil + + self~state = "start" + return reg~integer~d2c + end + otherwise + return .nil + end + + return "" + + ::method custom + use arg key + + if key == "" then do + if .environment~theco.exec then self~initial + return self~name + end + + /* + * String termination handling + */ + if .environment~theco.modifiers.at then do + .environment~theco.undo~~push(- + ".environment~theco.modifiers.at = .true"- + )~~push(".environment~theco.escape = '1B'x") + + .environment~theco.modifiers.at = .false + .environment~theco.escape = key~upper + + return self~name + end + + if key~upper == .environment~theco.escape then do + .environment~theco.undo~~push(- + ".environment~theco.escape ="- + "'".environment~theco.escape~c2x"'x"- + )~~push(- + ".environment~theco.strings.1 ="- + stringify(.environment~theco.strings.1)- + ) + + .environment~theco.escape = '1B'x + str = .environment~theco.strings.1 + .environment~theco.strings.1 = "" + + .StateExpectString~~save_machine~state = "start" + .StateExpectString~mode = "" + .StateExpectString~toctl = .false + + return self~done(str) + end + + /* + * String building characters + */ + insert = .StateExpectString~~save_machine~machine(key) + if insert = .nil then return "" + if insert == "" then return self~name + + /* + * String accumulation + */ + .environment~theco.undo~~push(- + ".environment~theco.strings.1 ="- + stringify(.environment~theco.strings.1)- + ) + .environment~theco.strings.1 ||= insert + + if .environment~theco.exec then + self~process(.environment~theco.strings.1,- + insert~length) + return self~name + + ::method initial abstract + ::method process abstract + ::method done abstract + +/* + * Super class for states accepting Q-Register specifications + */ +::class StateExpectQReg subclass State + ::method INIT + forward class (super) + + ::method save + use arg reg + + .environment~theco.undo~~push(- + ".environment~theco.registers['"reg~name"']~integer ="- + reg~integer- + ) + + ::method custom + use arg key + + reg = .environment~theco.registers[key~upper] + if reg = .nil then return "" + + return self~got_register(reg) + + ::method got_register abstract + +::class StateStart subclass State + ::method INIT + forward array ("start") class (super) + + ::method move + use arg n + + .environment~theco.undo~~push_cmd('clocate :'column.1()), + ~~push_cmd('locate :'line.1()) + + /* FIXME: do this in less commands */ + if n > 0 then + do n + 'cursor cua right' + end + else + do -n + 'cursor cua left' + end + + ::method move_lines + use arg n + + .environment~theco.undo~~push_cmd('clocate :'column.1()), + ~~push_cmd('locate' (-n)) + 'locate' n + 'clocate :1' + + ::method custom + use arg key + + key = key~upper + select + /* + * <CTRL/x> commands implemented in `ctlcmd` state + */ + when key~c2d < 32 then do + return .environment~theco.states["ctlcmd"], + ~get_next_state(ctlecho(key)) + end + /* + * arithmetics + */ + when key~matchchar(1, "0123456789") then do + if \.environment~theco.exec then return self~name + + .environment~theco.stack~add_digit(key) + end + when key~matchchar(1, "/*+&#") then do + if \.environment~theco.exec then return self~name + + .environment~theco.stack~push_op_calc(key) + end + when key == "-" then do + if \.environment~theco.exec then return self~name + + if .environment~theco.stack~args = 0 |, + .environment~theco.stack~nums~peek == "" then do + .environment~theco.undo~~push(- + ".environment~theco.stack~num_sign ="- + .environment~theco.stack~num_sign- + ) + .environment~theco.stack~num_sign *= -1 + end + else + .environment~theco.stack~push_op_calc("-") + end + when key == "(" then do + if \.environment~theco.exec then return self~name + + if .environment~theco.stack~num_sign < 0 then + .environment~theco.stack, + ~~push_num(-1)~push_op_calc("*") + .environment~theco.stack~push_op("(") + end + when key == ")" then do + if \.environment~theco.exec then return self~name + + .environment~theco.stack~eval(.true) + end + when key == "," then do + if \.environment~theco.exec then return self~name + + .environment~theco.stack~~eval~push_num("") + end + when key == "." then do + if \.environment~theco.exec then return self~name + + .environment~theco.stack~~eval~push_num(get_dot()) + end + when key == "Z" then do + if \.environment~theco.exec then return self~name + + .environment~theco.stack~~eval~push_num(get_size()) + end + when key == "H" then do + if \.environment~theco.exec then return self~name + + .environment~theco.stack~~eval, + ~~push_num(0)~push_num(get_size()) + end + /* + * control structures (loops) + */ + when key == "<" then do + if \.environment~theco.exec then do + .environment~theco.nest_level += 1 + .environment~theco.undo~~push(- + ".environment~theco.nest_level -= 1"- + ) + return self~name + end + + if .environment~theco.stack~~eval~args = 0 then + /* infinite loop */ + .environment~theco.stack~push_num(-1) + + if .environment~theco.stack~nums~peek = 0 then do + .environment~theco.stack~pop_num + + /* skip up to end of loop (parse without exec) */ + .environment~theco.exec = .false + .environment~theco.undo~~push(- + ".environment~theco.exec = .true"- + ) + end + else + .environment~theco.stack, + ~~push_num(.environment~theco.pc), + ~~push_op("<") + end + when key == ">" then do + if \.environment~theco.exec then do + if .environment~theco.nest_level = 0 then do + .environment~theco.exec = .true + .environment~theco.undo~~push(- + ".environment~theco.exec ="- + ".false"- + ) + end + else do + .environment~theco.nest_level -= 1 + .environment~theco.undo~~push(- + ".environment~theco.nest_level"- + "+= 1"- + ) + end + + return self~name + end + + .environment~theco.stack~~discard_args~pop_op /* "<" */ + loop_pc = .environment~theco.stack~pop_num + loop_cnt = .environment~theco.stack~pop_num + + if loop_cnt \= 1 then do + /* repeat loop */ + if loop_cnt > 0 then loop_cnt -= 1 + .environment~theco.pc = loop_pc + .environment~theco.stack, + ~~push_num(loop_cnt), + ~~push_num(loop_pc), + ~~push_op("<") + end + end + when key == ";" then do + if \.environment~theco.exec then return self~name + + search = .environment~theco.registers["_"]~integer + v = .environment~theco.stack~pop_num_calc(1, search) + if .State~eval_colon then v = complement(v) + + if v >= 0 then do + .environment~theco.stack, + ~~discard_args, + ~~pop_op~~pop_num~~pop_num + + /* skip up to end of loop (parse without exec) */ + .environment~theco.exec = .false + .environment~theco.undo~~push(- + ".environment~theco.exec = .true"- + ) + end + end + /* + * control structures (conditionals) + */ + when key == "|" then do + if \.environment~theco.exec then do + if \.environment~theco.skip_else &, + .environment~theco.nest_level = 0 then do + .environment~theco.exec = .true + .environment~theco.undo~~push(- + ".environment~theco.exec ="- + ".false"- + ) + end + + return self~name + end + + /* + * skip up to end of conditional; skip ELSE-part + * (parse without exec) + */ + .environment~theco.exec = .false + .environment~theco.undo~~push(- + ".environment~theco.exec = .true"- + ) + end + when key == "'" then do + if \.environment~theco.exec then do + if .environment~theco.nest_level = 0 then do + .environment~theco.undo~~push(- + ".environment~theco.exec ="- + ".false"- + )~~push(- + ".environment~theco.skip_else ="- + .environment~theco.skip_else- + ) + .environment~theco.exec = .true + .environment~theco.skip_else = .false + end + else do + .environment~theco.nest_level -= 1 + .environment~theco.undo~~push(- + ".environment~theco.nest_level"- + "+= 1"- + ) + end + + return self~name + end + end + /* + * modifiers + */ + when key == "@" then do + if \.environment~theco.exec then return self~name + + .environment~theco.undo~~push(- + ".environment~theco.modidifiers.at ="- + .environment~theco.modidifiers.at- + ) + .environment~theco.modifiers.at = .true + end + when key == ":" then do + if \.environment~theco.exec then return self~name + + .environment~theco.undo~~push(- + ".environment~theco.modidifiers.colon ="- + .environment~theco.modidifiers.colon- + ) + .environment~theco.modifiers.colon = .true + end + /* + * commands + */ + when key == "J" then do + if \.environment~theco.exec then return self~name + + .environment~theco.undo~~push_cmd('clocate :'column.1()), + ~~push_cmd('locate :'line.1()) + call set_dot .environment~theco.stack~pop_num_calc(1, 0) + end + when key == "C" then do + if \.environment~theco.exec then return self~name + + self~move(.environment~theco.stack~pop_num_calc) + end + when key == "R" then do + if \.environment~theco.exec then return self~name + + self~move(-.environment~theco.stack~pop_num_calc) + end + when key == "L" then do + if \.environment~theco.exec then return self~name + + self~move_lines(.environment~theco.stack~pop_num_calc) + end + when key == "B" then do + if \.environment~theco.exec then return self~name + + self~move_lines(-.environment~theco.stack~pop_num_calc) + end + when key == "=" then do + if \.environment~theco.exec then return self~name + + 'msg' .environment~theco.stack~pop_num_calc + end + when key == "D" then do + if \.environment~theco.exec then return self~name + + v1 = .environment~theco.stack~pop_num_calc + if .environment~theco.stack~args = 0 then do + /* relative character range */ + if v1 > 0 then do + from = get_dot() + to = from + v1 + end + else do + to = get_dot() + from = to + v1 + end + end + else do + /* absolute character range */ + from = .environment~theco.stack~pop_num_calc + to = v1 + end + + eol_len = get_eol()~length + + call set_dot from + dot = from + + col = column.1() + do forever + line = curline.3() + size = min(to - dot, line~length - col + 1) + line = line~substr(col, size) + + if line~length > 0 then do + /* THE bug: Undo insert does not work properly! */ + .environment~theco.undo, + ~~push_cmd('cinsert' line) + 'cdelete' line~length + end + + dot += line~length + if dot >= to then leave + dot += eol_len + + 'join cursor' + .environment~theco.undo, + ~~push_cmd('split cursor') + end + end + otherwise + return "" + end + + return self~name + +::class StateLabel subclass State + ::method INIT + forward array ("label") class (super) + + ::method custom + use arg key + + select + when key == "!" then do + label = .environment~theco.strings.1 + escaped = stringify(label) + + .environment~theco.undo~~push(- + ".environment~theco.goto_table["escaped"] ="- + sor(.environment~theco.goto_table[label], ".nil")- + )~~push(".environment~theco.strings.1 =" escaped) + .environment~theco.goto_table[label] =, + .environment~theco.pc + .environment~theco.strings.1 = "" + + if .environment~theco.skip_label == label then do + .environment~theco.skip_label = .nil + .environment~theco.exec = .true + + .environment~theco.undo~~push(- + ".environment~theco.skip_label ="- + stringify(label)- + )~~push(".environment~theco.exec = .false") + end + + return "start" + end + otherwise + .environment~theco.undo~~push(- + ".environment~theco.strings.1 ="- + stringify(.environment~theco.strings.1)- + ) + .environment~theco.strings.1 ||= key + return self~name + end + +::class StateCtlCmd subclass State + ::method INIT + forward array ("ctlcmd") class (super) + + ::method custom + use arg key + + key = key~upper + select + when key == "O" then do + if \.environment~theco.exec then return "start" + + .environment~theco.stack~set_radix(8) + end + when key == "D" then do + if \.environment~theco.exec then return "start" + + .environment~theco.stack~set_radix(10) + end + when key == "R" then do + if \.environment~theco.exec then return "start" + + if .environment~theco.stack~~eval~args = 0 then + .environment~theco.stack~push_num(- + .environment~theco.stack~radix- + ) + else + .environment~theco.stack~set_radix(- + .environment~theco.stack~pop_num_calc- + ) + end + /* + * Alternatives: ^i, ^I, <CTRL/I>, <TAB> + */ + when key == "I" then do + if \.environment~theco.exec then return "cmd_insert" + + .environment~theco.stack~~eval~push_num(9) + return "cmd_insert" + end + /* + * Alternatives: ^[, <CTRL/[> (cannot be typed), <ESC> + */ + when key == "[" then do + if \.environment~theco.exec then return "start" + + .environment~theco.stack~discard_args + + /* + * Does not allow the caret-escape form; + * must be typed with two consequtive <ESC> + */ + if .environment~theco.cmdline~right(2) == '1B 1B'x then do + if .environment~theco.quit_requested then + do nbfile.1() + 'qquit' + end + .environment~theco.cmdline = "" + .environment~theco.undo~empty + end + end + /* + * Additional numeric operations + */ + when key == "_" then do + if \.environment~theco.exec then return "start" + + v = .environment~theco.stack~pop_num_calc + .environment~theco.stack~push_num(complement(v)) + end + when key~matchchar(1, "*/") then do + if \.environment~theco.exec then return "start" + + .environment~theco.stack~push_op_calc("^"key) + end + otherwise + return "" + end + + return "start" + +::class StateCtlUCmd subclass StateExpectQReg + ::method INIT + forward array ("ctlucmd") class (super) + + ::method got_register + use arg reg + + if \.environment~theco.exec then return "cmd_ctlu" + + .environment~theco.reg_arg = reg + return "cmd_ctlu" + +::class StateCmdCtlU subclass StateExpectString + ::method INIT + forward array ("cmd_ctlu") class (super) + + ::method initial + /* nothing to be done */ + + ::method process + /* nothing to be done */ + + ::method done + use arg str + + if \.environment~theco.exec then return "start" + + .environment~theco.reg_arg~string = str + return "start" + +::class StateFCmd subclass State + ::method INIT + forward array ("fcmd") class (super) + + ::method custom + use arg key + + select + /* + * loop flow control + */ + when key == "<" then do + if \.environment~theco.exec then return "start" + + /* FIXME: what if in brackets? */ + /* FIXME: what if not in loop -> set PC to 1 */ + .environment~theco.stack~~discard_args~pop_op /* "<" */ + + /* repeat loop */ + /* FIXME: peeking the program counter would be sufficient */ + .environment~theco.pc = .environment~theco.stack~pop_num + .environment~theco.stack, + ~~push_num(.environment~theco.pc), + ~~push_op("<") + end + when key == ">" then do + if \.environment~theco.exec then return "start" + + /* FIXME: what if in brackets? */ + .environment~theco.stack~~discard_args~pop_op /* "<" */ + loop_pc = .environment~theco.stack~pop_num + loop_cnt = .environment~theco.stack~pop_num + + if loop_cnt > 1 then do + /* repeat loop */ + .environment~theco.pc = loop_pc + .environment~theco.stack, + ~~push_num(loop_cnt-1), + ~~push_num(loop_pc), + ~~push_op("<") + end + else do + /* skip up to end of loop (parse without exec) */ + .environment~theco.exec = .false + .environment~theco.undo~~push(- + ".environment~theco.exec = .true"- + ) + end + end + /* + * conditional flow control + */ + when key == "'" then do + if \.environment~theco.exec then return "start" + + /* + * skip to end of conditional (parse without exec) + */ + .environment~theco.exec = .false + .environment~theco.skip_else = .true + .environment~theco.undo~~push(- + ".environment~theco.exec = .true"- + )~~push(".environment~theco.skip_else = .false") + end + when key == "|" then do + if \.environment~theco.exec then return "start" + + /* + * skip to ELSE-part or end of conditional + * (parse without exec) + */ + .environment~theco.exec = .false + .environment~theco.undo~~push(- + ".environment~theco.exec = .true"- + ) + end + otherwise + return "" + end + + return "start" + +::class ConditionalTests + ::method "A" + return self~"V"(arg(1)) | self~"W"(arg(1)) + ::method "C" + /* FIXME */ + return self~"R"(arg(1)) + ::method "D" + return arg(1) >= "0"~c2d & arg(1) <= "9"~c2d + ::method "E" + return arg(1) = 0 + ::method "F" + forward message "E" + ::method "G" + return arg(1) > 0 + ::method "L" + return arg(1) < 0 + ::method "N" + return arg(1) \= 0 + ::method "R" + return self~"A"(arg(1)) | self~"D"(arg(1)) + ::method "S" + forward message "L" + ::method "T" + forward message "L" + ::method "U" + forward message "E" + ::method "V" + return arg(1) >= "a"~c2d & arg(1) <= "z"~c2d + ::method "W" + return arg(1) >= "A"~c2d & arg(1) <= "Z"~c2d + ::method "<" + forward message "L" + ::method ">" + forward message "G" + ::method "=" + forward message "E" + +::class StateCondCmd subclass State + ::attribute tests + + ::method INIT + self~tests = .ConditionalTests~new + + forward array ("condcmd") class (super) + + ::method custom + use arg key + + if \self~tests~hasMethod(key) then return "" + + if \.environment~theco.exec then do + .environment~theco.nest_level += 1 + .environment~theco.undo~~push(- + ".environment~theco.nest_level -= 1"- + ) + return "start" + end + + v = .environment~theco.stack~pop_num_calc + if \self~tests~send(key, v) then do + /* + * skip to ELSE-part or end of conditional + * (parse without exec) + */ + .environment~theco.exec = .false + .environment~theco.undo~~push(- + ".environment~theco.exec = .true"- + ) + end + + return "start" + +::class StateCmdGoto subclass StateExpectString + ::method INIT + forward array ("cmd_goto") class (super) + + ::method initial + /* nothing to be done */ + + ::method process + /* nothing to be done */ + + ::method done + use arg str + + if \.environment~theco.exec then return "start" + + labels = tokenize(str, ",") + label = labels[.environment~theco.stack~pop_num_calc] + + if label \= .nil, label \== "" then do + pc = .environment~theco.goto_table[label] + + if pc \= .nil then + .environment~theco.pc = pc + else do + .environment~theco.skip_label = label + /* skip till label is defined */ + .environment~theco.exec = .false + .environment~theco.undo~~push(- + ".environment~theco.skip_label = .nil"- + )~~push(".environment~theco.exec = .true") + end + end + + return "start" + +::class StateQCmd subclass StateExpectQReg + ::method INIT + forward array ("qcmd") class (super) + + ::method got_register + use arg reg + + if \.environment~theco.exec then return "start" + + .environment~theco.stack~~eval~push_num(reg~integer) + return "start" + +::class StateUCmd subclass StateExpectQReg + ::method INIT + forward array ("ucmd") class (super) + + ::method got_register + use arg reg + + if \.environment~theco.exec then return "start" + + self~save(reg) + reg~integer = .environment~theco.stack~pop_num_calc + return "start" + +::class StateIncCmd subclass StateExpectQReg + ::method INIT + forward array ("inccmd") class (super) + + ::method got_register + use arg reg + + if \.environment~theco.exec then return "start" + + self~save(reg) + reg~integer += .environment~theco.stack~pop_num_calc + .environment~theco.stack~push_num(reg~integer) + return "start" + +::class StateMCmd subclass StateExpectQReg + ::method INIT + forward array ("mcmd") class (super) + + ::method got_register + use arg reg + + if \.environment~theco.exec then return "start" + + pc = .environment~theco.pc + .environment~theco.pc = 0 + .environment~theco.state = .environment~theco.states["start"] + + if \execute(reg~string) then return "" + + .environment~theco.pc = pc + + return "start" + +::class StateECmd subclass State + ::method INIT + forward array ("ecmd") class (super) + + ::method custom + use arg key + + key = key~upper + select + when key == "X" then do + if \.environment~theco.exec then return "start" + + .environment~theco.quit_requested = .true + return "start" + end + otherwise + return "" + end + +::class StateCmdFile subclass StateExpectString + ::method INIT + forward array ("cmd_file") class (super) + + ::method do_edit + use arg filename + + file_cnt = nbfile.1() + old_file = efileid.1() + 'edit' filename + if nbfile.1() > file_cnt then + /* file is new in ring */ + .environment~theco.undo~~push_cmd('qquit') + else + .environment~theco.undo~~push_cmd('edit' old_file) + + select + when size.1() = 0 then 'add' + when tof() then 'locate 1' + otherwise + end + + ::method initial + /* nothing to be done */ + + ::method process + /* nothing to be done */ + + ::method done + use arg filename + + if \.environment~theco.exec then return "start" + + /* FIXME: match-spec error */ + if SysFileTree(filename, "matching.", "FO") \= 0 then + return "" + + if matching.0 = 0 then + /* no match-spec or non-existing file */ + self~do_edit(filename) + else + do i = 1 to matching.0 + self~do_edit(matching.i) + end + + return "start" + +/* + * TODO: expect filename to read into Q-register + */ +::class StateEQCmd subclass StateExpectQReg + ::method INIT + forward array ("eqcmd") class (super) + + ::method got_register + use arg reg + + if \.environment~theco.exec then return "start" + + reg~edit() + return "start" + +::class StateCmdInsert subclass StateExpectString + ::method INIT + forward array ("cmd_insert") class (super) + + ::method do_insert + use arg key + + if key == "" then return + + select + when key == '0D'x | key == '0A'x then do + 'split cursor' + 'next' + 'clocate :1' + .environment~theco.undo, + ~~push_cmd('join cursor'), + ~~push_cmd('sos endchar'), + ~~push_cmd('up') + end + when key == '09'x, tabkey.2() == "TAB" then do + /* NOTE: sos tabf in insertmode currently broken on THE v3.3 RC1!? */ + .environment~theco.undo, + ~~push_cmd('clocate :'column.1()), + ~~push_cmd('sos tabb') + 'sos tabf' + end + otherwise + 'cinsert' key + 'clocate 1' + .environment~theco.undo~~push_cmd('sos cuadelback') + end + + ::method initial + /* + * NOTE: cannot support VideoTECO's <n>I because + * beginning and end of strings must be determined + * syntactically + */ + do i = .environment~theco.stack~~eval~args to 1 by -1 + char = .environment~theco.stack~pop_num_calc(i) + self~do_insert(char~d2c) + end + + ::method process + use arg str, new_chars + + do i = new_chars-1 to 0 by -1 + self~do_insert(str~subchar(str~length - i)) + end + + ::method done + /* nothing to be done when done */ + return "start" + +::class StateCmdSearch subclass StateExpectString + ::attribute initial_dot + + ::attribute from + ::attribute to + ::attribute count + + ::method INIT + forward array ("cmd_search") class (super) + + ::method initial + self~initial_dot = get_dot() + + v = .environment~theco.stack~pop_num_calc + if .environment~theco.stack~args > 0 then do + self~from = .environment~theco.stack~pop_num_calc + self~to = v + self~count = 1 + end + else do + self~from = self~initial_dot + self~to = get_size() + self~count = v + end + + ::method process + use arg str + + cre = ReComp(pattern2regexp(str), "x") + if cre~left(1) then do + .environment~theco.undo~~push(- + ".environment~theco.registers['_']~integer ="- + .environment~theco.registers["_"]~integer- + ) + .environment~theco.registers["_"]~integer = 0 /* failure */ + + call ReFree cre + return + end + + buffer = get_buffer(self~from, self~to)~toString("l", get_eol()) + + offset = 1 + do self~count, + while ReExec(cre, buffer~substr(offset), "matches.", "p") + offset += matches.!match~word(1) - 1 /* offset */ + offset += matches.!match~word(2) /* length */ + end + + call ReFree cre + + .environment~theco.undo~~push_cmd('clocate :'column.1()), + ~~push_cmd('locate :'line.1()) + + .environment~theco.undo~~push(- + ".environment~theco.registers['_']~integer ="- + .environment~theco.registers["_"]~integer- + ) + if matches.!match~word(2) = 0 then do + call set_dot self~initial_dot + .environment~theco.registers["_"]~integer = 0 /* failure */ + end + else do + call set_dot self~from+offset-1 + .environment~theco.registers["_"]~integer = -1 /* success */ + end + + ::method done + use arg str + + if \.environment~theco.exec then return "start" + + search_reg = .environment~theco.registers["_"] + if str == "" then + self~process(search_reg~string) + else + search_reg~string = str + + return "start" + +/* + * auxilliary stuff + */ +::routine sor + use arg obj, val + + select + when obj = .nil then return val + when obj == "" then return val + otherwise + return obj + end + +::routine isor + use arg obj, val + + if obj = 0 then return val + return obj + +/* + * Ones complement (binary NOT), may be used to negate TECO boolean values + * (x < 0 and x >= 0) + */ +::routine complement + return -arg(1) - 1 + +::routine last_match + use arg str, chars + + do i = str~length to 1 by -1 + if str~matchchar(i, chars) then return i + end + return 0 + +::routine tokenize + use arg str, delims + + tokens = .Array~new + + start = 1 + do i = 1 to str~length + if str~matchchar(i, delims) then do + tokens~append(str~substr(start, i - start)) + start = i + 1 + end + end + tokens~append(str~substr(start, i - start)) + + return tokens + +::routine stringify + return "'"arg(1)~changeStr("'", "''")"'" + +::routine ctlecho + return arg(1)~bitOr('40'x) + +::routine is_matchspec + use arg spec + + /* FIXME: glob rules - different on Windows!? */ + do i = 1 to spec~length + if spec~matchchar(i, "?*[") then return .true + end + return .false + +::routine get_path_sep + os = version.3() + + if os == "OS2" | os == "WIN32" then return "\" + return "/" + +::routine get_real_filename + use arg file + + name = filespec("name", file~word(5)) + if file~word(4)~caselessPos("D") \= 0 then + return name || get_path_sep() + return name + +::routine get_eol + eol = eolout.1() + + select + when eol == "LF" then return '0A'x + when eol == "CR" then return '0D'x + when eol == "CRLF" then return '0D 0A'x + end + +::routine get_dot + old_line = line.1() + eol_len = get_eol()~length + + dot = 0 + do l = 1 to old_line-1 + 'up' + /* FIXME: for some reason length.1() always returns 19 */ + dot += curline.3()~length + eol_len + end + 'locate :'old_line + + return dot + column.1() - 1 + +::routine get_size + line = line.1() + column = column.1() + 'locate :'size.1() + 'sos endchar' + + size = get_dot() + + 'locate :'line + 'clocate :'column + + return size + +::routine set_dot + use arg dot + + eol_len = get_eol()~length + 'locate :1' + 'clocate :1' + + do forever + /* FIXME: for some reason length.1() always returns 19 */ + line_length = curline.3()~length + eol_len + if dot < line_length then leave + + dot -= line_length + 'next' + end + 'clocate' dot + +::routine get_buffer + use arg from=0, to=(get_size()) + + eol_len = get_eol()~length + old_line = line.1() + old_column = column.1() + + call set_dot from + dot = from + + /* + * THE bug workaround: sometimes necessary to fixup curline.3() + */ + 'next' + 'up' + + buffer = .Array~new + do forever + col = column.1() + line = curline.3() + line = line~substr(col, min(to - dot, line~length - col + 1)) + buffer~append(line) + + dot += line~length + if dot >= to then leave + dot += eol_len + + 'next' + 'clocate :1' + end + + 'locate :'old_line + 'clocate :'old_column + return buffer + +::routine regexp_escape + use arg char + + if char~matchchar(1, ".[](){}^$*+?|\") then return "\"char + return char + +::routine pattern2regexp + use arg pattern + + re = "" + state = "start" + + do i = 1 to pattern~length + c = pattern~subchar(i) + + select + when state == "start" then do + if c~c2d >= 32 then do + re ||= regexp_escape(c) + iterate i + end + echo = ctlecho(c) + + select + when echo == "X" then re ||= "." + when echo == "S" then re ||= "[^[:alnum:]]" + when echo == "N" then state = "not" + when echo == "E" then state = "ctle" + otherwise + /* control characters never have to be escaped */ + re ||= c + end + end + when state == "not" then do + if c~matchchar(1, "[]-\") then c = "\"c + re ||= "[^"c"]" + + state = "start" + end + when state == "ctle" then do + c = c~upper + + select + when c == "A" then re ||= "[[:alpha:]]" + when c == "B" then re ||= "[^[:alnum:]]" + when c == "C" then re ||= "[[:alnum:].$]" + when c == "D" then re ||= "[[:digit:]]" + /* when c == "G" then */ + when c == "L" then re ||= "[\r\n\v\f]" + /* when c == "M" then */ + when c == "R" then re ||= "[[:alnum:]]" + when c == "S" then re ||= "[[:blank:]]+" + when c == "V" then re ||= "[[:lower:]]" + when c == "W" then re ||= "[[:upper:]]" + when c == "X" then re ||= "." + /* when ^E<nnn> */ + when c == "[" then re ||= "(" + otherwise + return "" + end + + state = "start" + end + end + end + + return re + +/* + * External routines (classic Rexx function packages) + */ +::routine ReLoadFuncs external "REGISTERED rexxre reloadfuncs" |