/* User Extensions LispBox uLisp Extension - Version 1.0 - June 2024 Hartmut Grawe - github.com/ersatzmoco - June 2024 edited by hasn0life for Lilygo T-Deck - Jan 2025 updated by picolisper for LilyGo T-Deck and PicoCalc - April 2025 */ // Utility functions uint8_t dec_to_bcd(uint8_t n) { uint8_t bcd = 0; uint8_t tens = n / 10; bcd = tens << 4; tens *= 10; bcd += (n - tens) & 0x0f; return bcd; } uint8_t bcd_to_dec(uint8_t n) { return ((n>>4) * 10) + (n&0x0f); } /* * STANDARD DEFINITIONS * * These definitions should be the same on every platform. */ object * fn_platform(object *args, object *env) { (void) args; (void) env; #if defined(PLATFORM_PICOCALC) const char platformName[] = ":picocalc"; #elif defined(TDECK_PERI_POWERON) /* first t-deck define */ const char platformName[] = ":t-deck"; #elif defined(ARDUINO_TEENSY41) const char platformName[] = ":teensy41"; #else const char platformName[] = ":unknown"; #endif return internlong((char *)platformName); } object * fn_bcd_to_dec(object *args, object *env) { (void) env; object *arg = car(args); int n = checkinteger(arg); if ((n < 0) || (n > 153)) { error2("number not in the range [0...#x99]."); } uint8_t result = bcd_to_dec(static_cast(n)); return number(static_cast(result)); } object * fn_dec_to_bcd(object *args, object *env) { (void) env; object *arg = car(args); int n = checkinteger(arg); if ((n < 0) || (n > 99)) { error2("number not in the range [0...99]."); } uint8_t result = dec_to_bcd(static_cast(n)); return number(static_cast(result)); } object * fn_now(object *args, object *env) { (void) env; static unsigned long Offset; unsigned long now = millis()/1000; int nargs = listlength(args); // Set time if (nargs == 3) { Offset = (unsigned long)((checkinteger(first(args))*60 \ + checkinteger(second(args)))*60 \ + checkinteger(third(args)) - now); } else if (nargs > 0) { error2(PSTR("wrong number of arguments")); } // Return time unsigned long secs = Offset + now; object *seconds = number(secs%60); object *minutes = number((secs/60)%60); object *hours = number((secs/3600)%24); return cons(hours, cons(minutes, cons(seconds, NULL))); } void hyperprint(object *form, int lm, pfun_t pfun) { if (atom(form)) { if (isbuiltin(form, NOTHING)) { printsymbol(form, pfun); } else { printobject(form, pfun); } } else if (quoted(form)) { pfun('\''); hyperprint(car(cdr(form)), lm + 1, pfun); } else { lm = lm + PPINDENT; bool fits = (subwidth(form, PPWIDTH - lm - PPINDENT) >= 0); int special = 0, extra = 0; bool separate = true; object *arg = car(form); if (symbolp(arg) && builtinp(arg->name)) { uint8_t minmax = getminmax(builtin(arg->name)); if (minmax == 0327 || minmax == 0313) { special = 2; // defun, setq, setf, defvar } else if (minmax == 0317 || minmax == 0017 || minmax == 0117 || minmax == 0123) { special = 1; } } while (form != NULL) { if (atom(form)) { pfstring(PSTR(" . "), pfun); printobject(form, pfun); pfun(')'); return; } else if (separate) { pfun('('); separate = false; } else if (special) { pfun(' '); special--; } else if (fits) { pfun(' '); } else { pln(pfun); indent(lm, ' ', pfun); } hyperprint(car(form), lm+extra, pfun); form = cdr(form); } pfun(')'); } } object * fn_sym_def(object *args, object *env) { (void) env; object *obj = first(args); pfun_t pfun = pstreamfun(cdr(args)); #if defined(gfxsupport) if (pfun == gfxwrite) ppwidth = GFXPPWIDTH; #endif object *pair = findvalue(obj, env); object *var = car(pair); object *val = cdr(pair); pln(pfun); if (consp(val) && symbolp(car(val)) && builtin(car(val)->name) == LAMBDA) { hyperprint(cons(bsymbol(DEFUN), cons(var, cdr(val))), 0, pfun); } else { hyperprint(cons(bsymbol(DEFVAR), cons(var, cons(quote(val), NULL))), 0, pfun); } pln(pfun); ppwidth = PPWIDTH; return bsymbol(NOTHING); } object * fn_listlibrary2(object *args, object *env) { (void) args, (void) env; object *lst = nil; GlobalStringIndex = 0; object *line = read(glibrary); while (line != NULL) { builtin_t bname = builtin(first(line)->name); if (bname == DEFUN || bname == DEFVAR) { lst = cons(second(line), lst); } line = read(glibrary); } return lst; } object * fn_lambdap(object *arg, object *env) { (void) env; if (consp(arg)) { arg = car(arg); } if (builtin(arg->name) == LAMBDA) { return tee; } return nil; } object * fn_searchstr(object *args, object *env) { (void) env; int startpos = 0; object *pattern = first(args); object *target = second(args); args = cddr(args); if (pattern == NULL) { return number(0); } else if (target == NULL) { return nil; } if (args != NULL) { startpos = checkinteger(car(args)); } if (stringp(pattern) && stringp(target)) { int l = stringlength(target); int m = stringlength(pattern); if (startpos > l) { error2(indexrange); } for (int i = startpos; i <= l-m; i++) { int j = 0; while (j < m && nthchar(target, i+j) == nthchar(pattern, j)) { j++; } if (j == m) { return number(i); } } return nil; } else { error2("arguments are not both lists or strings"); } return nil; } object * fn_searchn(object *args, object *env) { (void) env; int matches = 0; int last_index = 0; object *pattern = first(args); object *target = second(args); if (cddr(args) != NULL) { object *num = third(args); if (integerp(num)) { matches = num->integer; } } if (pattern == NULL) { return number(0); } else if (target == NULL) { return nil; } else if (listp(pattern) && listp(target)) { int l = listlength(target); int m = listlength(pattern); for (int i = 0; i <= l-m; i++) { object *target1 = target; while (pattern != NULL && eq(car(target1), car(pattern))) { pattern = cdr(pattern); target1 = cdr(target1); } if (pattern == NULL){ last_index = i; if (matches-- == 0) { return number(i); } } pattern = first(args); target = cdr(target); } if (last_index > 0) { return number(last_index); } return nil; } else if (stringp(pattern) && stringp(target)) { int l = stringlength(target); int m = stringlength(pattern); for (int i = 0; i <= l-m; i++) { int j = 0; while (j < m && nthchar(target, i+j) == nthchar(pattern, j)) { j++; } if (j == m) { last_index = i; if(matches-- == 0){ return number(i); } } } if (last_index > 0) { return number(last_index); } return nil; } else { error2(PSTR("arguments are not both lists or strings")); } return nil; } // SD card standard library. #if defined(sdcardsupport) object * fn_sd_rename(object *args, object *env) { (void) env; char buffer1[BUFFERSIZE]; char buffer2[BUFFERSIZE]; object *pathFrom = car(args); if (!stringp(pathFrom)) { error2("filenames must be strings."); } object *pathTo = car(cdr(args)); if (!stringp(pathTo)) { error2("filenames must be strings."); } if (!SD.rename((const char *)MakeFilename(pathFrom, buffer1), (const char *)MakeFilename(pathTo, buffer2))) { return nil; } return tee; } object * fn_sd_remove(object *args, object *env) { (void) env; char buffer[BUFFERSIZE]; object *arg = car(args); if (!SD.remove(MakeFilename(arg, buffer))) { return nil; } return tee; } object * fn_sd_existsp(object *args, object *env) { (void) env; char buffer[BUFFERSIZE]; object *arg = car(args); if (!SD.exists(MakeFilename(arg, buffer))) { return nil; } return tee; } /* (sd-make-dir path) Create a directory on the SD card. This will also create any intermediate directories that don’t already exists; e.g. SD.mkdir("a/b/c") will create a, b, and c. */ object * fn_sd_mkdir(object *args, object *env) { (void) env; char buffer[BUFFERSIZE]; object *arg = car(args); if (SD.mkdir(MakeFilename(arg, buffer))) { return tee; } return nil; } /* (sd-remove-dir path) Remove a directory from the SD card. The directory must be empty. */ object * fn_sd_rmdir(object *args, object *env) { (void) env; char buffer[BUFFERSIZE]; object *arg = car(args); if (SD.rmdir(MakeFilename(arg, buffer))) { return tee; } return nil; } /* * (sd-list) */ object * fn_sd_list(object *args, object *env) { (void) env; char *sd_path_buf = NULL; SDBegin(); File root; object *result = cons(NULL, NULL); object *ptr = result; if (args != NULL) { object *arg1 = checkstring(first(args)); int len = stringlength(arg1) + 2; //make it longer for the initial slash and the null terminator sd_path_buf = (char*)malloc(len); if (sd_path_buf != NULL) { cstring(arg1, &sd_path_buf[1], len-1); sd_path_buf[0] = '/'; //really weird way to add a slash at the front... root = SD.open(sd_path_buf); } } else{ root = SD.open("/"); } while (true) { File entry = root.openNextFile(); if (!entry) { break; // no more files } object *filename = lispstring((char*)entry.name()); if (entry.isDirectory()) { cdr(ptr) = cons(filename, NULL); } else{ cdr(ptr) = cons(cons(filename, number(entry.size())), NULL); } ptr = cdr(ptr); entry.close(); } if (sd_path_buf != NULL) { free(sd_path_buf); } root.close(); return cdr(result); } #endif /* * PICOCALC-SPECIFIC FUNCTIONS * * Only works on the PicoCalc... */ #if defined(PLATFORM_PICOCALC) char getkey() { PCKeyboard::KeyEvent kevt; char keypress; do { kevt = pc_kbd.keyEvent(); if (kevt.state == PCKeyboard::StatePress) { if (kevt.key == 6) { continue; } keypress = kevt.key; break; } } while (pc_kbd.keyCount() == 0); do { kevt = pc_kbd.keyEvent(); } while (kevt.state != PCKeyboard::StateRelease); switch (keypress) { case 177: keypress = 27; break; default: // do nothing break; } return keypress; } object * fn_get_key(object *args, object *env) { (void) args; (void) env; return character(getkey()); } void initPlatform() { return; } #elif defined(TDECK_PERI_POWERON) #define touchscreen #if defined(touchscreen) #include "TouchDrvGT911.hpp" TouchDrvGT911 touch; #endif #define TDECK_TOUCH_INT 16 #define TDECK_TRACKBALL_UP 3 #define TDECK_TRACKBALL_DOWN 15 #define TDECK_TRACKBALL_LEFT 1 #define TDECK_TRACKBALL_RIGHT 2 #define PLATFORM_TDECK volatile int ball_val = 0; // Touchscreen void initTouch() { #if defined (touchscreen) pinMode(TDECK_TOUCH_INT, INPUT); touch.setPins(-1, TDECK_TOUCH_INT);\ //keyboard already initialized the I2C? if (!touch.begin(Wire1, GT911_SLAVE_ADDRESS_L)) { while (1) { Serial.println("Failed to find GT911 - check your wiring!"); delay(1000); } } // Set touch max xy touch.setMaxCoordinates(320, 240); // Set swap xy touch.setSwapXY(true); // Set mirror xy touch.setMirrorXY(false, true); #endif } void ISR_trackball_up() { ball_val = 218; } void ISR_trackball_down() { ball_val = 217; } void ISR_trackball_left() { ball_val = 216; } void ISR_trackball_right () { ball_val = 215; } void inittrackball() { pinMode(TDECK_TRACKBALL_UP, INPUT_PULLUP); pinMode(TDECK_TRACKBALL_DOWN, INPUT_PULLUP); pinMode(TDECK_TRACKBALL_LEFT, INPUT_PULLUP); pinMode(TDECK_TRACKBALL_RIGHT, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(TDECK_TRACKBALL_UP), ISR_trackball_up, FALLING); attachInterrupt(digitalPinToInterrupt(TDECK_TRACKBALL_DOWN), ISR_trackball_down, FALLING); attachInterrupt(digitalPinToInterrupt(TDECK_TRACKBALL_LEFT), ISR_trackball_left, FALLING); attachInterrupt(digitalPinToInterrupt(TDECK_TRACKBALL_RIGHT), ISR_trackball_right, FALLING); } object * fn_get_touch_points(object *args, object *env) { #if defined(touchscreen) int16_t x[5], y[5]; uint8_t touched = 0; object *result = nil; do { touched = touch.getPoint(x, y, touch.getSupportTouchPoint()); if (touched > 0) { //start from the end of the list so we dont have to reverse it for (int i = touched; i > 0; --i) { result = cons(cons(number(x[i-1]), number(y[i-1])), result); } } } while(touch.isPressed()); return result; #else return nil; #endif } bool isScreenTouched() { bool received_touch = false; // Clear any previous readings since it buffers those. do { int16_t x[5], y[5]; uint8_t touched = touch.getPoint(x, y, touch.getSupportTouchPoint()); } while(touch.isPressed()); // touch.ispressed() will trigger like 5 times if you press it once so // we have to loop through it and get the touchpoints do { int16_t x[5], y[5]; uint8_t touched = touch.getPoint(x, y, touch.getSupportTouchPoint()); if (touched > 0) { received_touch = true; } } while(touch.isPressed()); return received_touch; } // T-Deck extras char touchKeyModEditor(char temp) { #if defined (touchscreen) /* t-deck / blackberry keyboard missing symbols missing mapped alt symbol ` k ' ~ p @ % $ ^ a * & q # = o + < t ( > y ) \ u _ | g / [ alt-t ( ] alt-y ) { n/a } n/a tab space while holding the touch screen c --- quit editor and return to REPL n --- discard current text buffer (i.e. new file) backspace --- delete line starting at cursor position trackball left --- move cursor to start of line trackball right --- move cursor to end of line ^ --- move cursor to beginning of buffer trackball up / trackball down --- move one page up or down Fn-h --- help menu Fn-( --- toggle bracket matching on/off Fn-) --- check if bracket under the cursor has a matching bracket in the buffer. If so, they are temporarily highlighted. (Use when continuous bracket matching is off.) Fn-b --- bind contents of the text buffer to a symbol of your choice and quit editor Fn-d --- delete a file on the SD card Fn-s --- save text buffer to SD card Fn-l --- load text from SD card into buffer, discarding the present one Fn-i --- show directory of SD card */ if (isScreenTouched()) { if (temp == 'k') return '`'; else if (temp == 'p') return '~'; else if (temp == '$') return '%'; else if (temp == 'a') return '^'; else if (temp == 'q') return '&'; else if (temp == 'o') return '='; else if (temp == 't') return '<'; else if (temp == 'y') return '>'; else if (temp == 'u') return '\\'; else if (temp == 'g') return '|'; else if (temp == '(') return '['; else if (temp == ')') return ']'; else if (temp == ' ') return '\t'; else if (temp == 'c') return (char)17; //quit else if (temp == 'n') return (char)24; //new else if (temp == 8) return (char)12; //delete line else if (temp == '*') return (char)94; //beginning else if (temp == 'h') return (char)16; //help else if (temp == 's') return (char)203; //save else if (temp == 'l') return (char)204; //load else if (temp == 'd') return (char)202; //delete else if (temp == 'b') return (char)198; //bind else if (temp == 'i') return (char)205; //show dir else if (temp == '1') return (char)194; //toggle bracket else if (temp == '2') return (char)195; //highlight } #else if (temp == '@') temp = '~'; if (temp == '_') temp = '\\'; #endif return temp; } object * fn_KeyboardGetKey(object *args, object *env) { (void) env, (void) args; Wire1.requestFrom(0x55, 1); if (Wire1.available()) { char temp = Wire1.read(); if ((temp != 0) && (temp !=255)){ temp = touchKeyModEditor(temp); //Serial.println((int)temp); return number(temp); } } if (ball_val != 0) { int temp = ball_val; ball_val = 0; if (isScreenTouched()) { // ((or 1 210) (se:linestart)) // ((or 5 213) (se:lineend)) // (211 (se:prevpage)) // (214 (se:nextpage)) switch(temp){ case 218: temp = 211; break; //up case 217: temp = 214; break; //down case 216: temp = 210; break; //left case 215: temp = 213; break; //right } } return number(temp); } return nil; } /* (keyboard-flush) Discard missing key up/down events. */ object * fn_KeyboardFlush(object *args, object *env) { (void) args, (void) env; return nil; } void initPlatform() { initTouch(); inittrackball(); } // Any other platform should at least define initPlatform. #else void initPlatform() { initTouch(); inittrackball(); } #endif /* * SYMBOL NAMES * * Define symbol names as const char[] PROGMEM here. */ const char stringlambdap[] PROGMEM = "lambdap"; const char stringnow[] PROGMEM = "now"; const char string_sym_def[] PROGMEM = "symdef"; const char stringbcd_to_dec[] PROGMEM = "bcd-to-dec"; const char stringdec_to_bcd[] PROGMEM = "dec-to-bcd"; const char stringlist_library2[] PROGMEM = "list-library2"; const char stringplatform[] PROGMEM = "platform"; const char stringSearchStr[] PROGMEM = "search-str"; const char stringsearchn[] PROGMEM = "searchn"; #if defined(sdcardsupport) const char stringsd_rename[] PROGMEM = "sd-rename"; const char stringsd_remove[] PROGMEM = "sd-remove"; const char stringsd_existsp[] PROGMEM = "sd-exists-p"; const char stringsd_mkdir[] PROGMEM = "sd-make-dir"; const char stringsd_rmdir[] PROGMEM = "sd-remove-dir"; const char stringsd_dir[] PROGMEM = "sd-list"; #endif #if defined(PLATFORM_PICOCALC) const char string_get_key[] PROGMEM = "get-key"; #elif defined(TDECK_PERI_POWERON) const char string_gettouchpoints[] PROGMEM = "get-touch-points"; const char stringKeyboardGetKey[] PROGMEM = "keyboard-get-key"; const char stringKeyboardFlush[] PROGMEM = "keyboard-flush"; #elif defined(TDECK_PERI_POWERON) const char string_gettouchpoints[] PROGMEM = "get-touch-points"; const char stringKeyboardGetKey[] PROGMEM = "keyboard-get-key"; const char stringKeyboardFlush[] PROGMEM = "keyboard-flush"; #endif /* * DOCUMENTATION STRINGS * * Define documentation strings as const char[] PROGMEM here. */ const char doclambdap[] PROGMEM = "(lambdap x)" "Returns t if the form passed in is a lambda."; const char docnow[] PROGMEM = "(now [hh mm ss])\n" "Sets the current time, or with no arguments returns the current time\n" "as a list of three integers (hh mm ss)."; const char doc_sym_def[] PROGMEM = "(symbol-def symbol [str])\n" "Prints the definition of a symbol (variable or function) defined in\n" "ulisp using the pretty printer." "If str is specified it prints to the specified stream.\n" "It returns no value."; const char doc_bcd_to_dec[] PROGMEM = "(bcd-to-dec n)\n" "Convert the BCD-encoded number n to decimal."; const char doc_dec_to_bcd[] PROGMEM = "(dec-to-bcd n)\n" "BCD encode n."; const char doc_list_library2[] PROGMEM = "(list-library2)\n" "Return a list of all symbols in the current Lisp library."; const char doc_platform[] PROGMEM = "(platform)\n" "Returns a keyword with the current platform. Supports :picocalc,\n" ":t-deck, and :teensy41. Otherwise, returns :unknown."; const char docSearchStr[] PROGMEM = "(search pattern target [startpos])\n" "Returns the index of the first occurrence of pattern in target, or nil if it's not found\n" "starting from startpos"; const char docsearchn[] PROGMEM = "(searchn pattern target [n])\n" "Returns the index of the nth occurrence of pattern in target,\n" "which can be lists or strings, or nil if it's not found.\n" "if the pattern occured more than once but less than n times, it returns the last occuring index"; // SD card doc strings #if defined(sdcardsupport) const char docsd_rename[] PROGMEM = "(sd-rename from to)\n" "Renames the file named by 'from' to 'to.'"; const char docsd_remove[] PROGMEM = "(sd-remove file)\n" "Removes the named file from the filesystem. Returns t if the file\n" "was remove successfully."; const char docsd_existsp[] PROGMEM = "(sd-exists-p file)\n" "Returns t if the named file exists."; const char docsd_dir[] PROGMEM = "(sd-list [directory])\n" "Returns a list of filenames in the root or named directory."; const char docsd_mkdir[] PROGMEM = "(sd-make-dir directory)\n" "Create a directory with the specified name. Returns t if \n" "successful, otherwise nil."; const char docsd_rmdir[] PROGMEM = "(sd-remove-dir directory)\n" "Remove the named directory. Returns t if successful, otherwise nil."; #endif // PicoCalc-specific doc strings #if defined(PLATFORM_PICOCALC) const char doc_get_key[] PROGMEM = "(get-key)\n" "Waits for a keypress event, then returns the key."; // T-Deck-specific doc strings #elif defined(TDECK_PERI_POWERON) const char doc_gettouchpoints[] PROGMEM = "(get-touch-points)\n" "Returns all the points being touched on the screen in a list of x,y pairs or an empty list"; const char docKeyboardGetKey[] PROGMEM = "(keyboard-get-key [pressed])\n" "Get key last recognized - default: when released, if [pressed] is t: when pressed)."; const char docKeyboardFlush[] PROGMEM = "(keyboard-flush)\n" "Discard missing key up/down events."; // End of platform-specific doc strings. #endif // Symbol lookup table const tbl_entry_t lookup_table2[] PROGMEM = { { stringlambdap, fn_lambdap, 0211, doclambdap }, { stringnow, fn_now, 0203, docnow }, { string_sym_def, fn_sym_def, 0212, doc_sym_def }, { stringbcd_to_dec, fn_bcd_to_dec, 0211, doc_bcd_to_dec }, { stringdec_to_bcd, fn_dec_to_bcd, 0211, doc_dec_to_bcd }, { stringlist_library2, fn_listlibrary2, 0200, doc_list_library2 }, { stringplatform, fn_platform, 0200, doc_platform }, { stringSearchStr, fn_searchstr, 0224, docSearchStr }, { stringsearchn, fn_searchn, 0223, docsearchn }, #if defined(sdcardsupport) { stringsd_rename, fn_sd_rename, 0222, docsd_rename }, { stringsd_remove, fn_sd_remove, 0211, docsd_remove }, { stringsd_existsp, fn_sd_existsp, 0211, docsd_existsp }, { stringsd_dir, fn_sd_list, 0201, docsd_dir }, { stringsd_mkdir, fn_sd_mkdir, 0211, docsd_mkdir }, { stringsd_rmdir, fn_sd_rmdir, 0211, docsd_rmdir }, #endif #if defined(PLATFORM_PICOCALC) { string_get_key, fn_get_key, 0200, doc_get_key }, #elif defined(TDECK_PERI_POWERON) { string_gettouchpoints, fn_get_touch_points, 0200, doc_gettouchpoints }, { stringKeyboardGetKey, fn_KeyboardGetKey, 0201, docKeyboardGetKey }, { stringKeyboardFlush, fn_KeyboardFlush, 0200, docKeyboardFlush }, #endif }; // Table cross-reference functions tbl_entry_t *tables[] = {lookup_table, lookup_table2}; const unsigned int tablesizes[] = { arraysize(lookup_table), arraysize(lookup_table2) }; const tbl_entry_t * table(int n) { return tables[n]; } unsigned int tablesize(int n) { return tablesizes[n]; }