Commit c101266d authored by Mart Lubbers's avatar Mart Lubbers

windows implementation, not thoroughly tested

parent a52d31ff
Pipeline #15123 passed with stage
in 1 minute and 35 seconds
CLEAN_HOME?=/opt/clean CLEAN_HOME?=/opt/clean
CFLAGS?=-Wall -Wextra -DDEBUG
ifeq ($(OS), Windows_NT) ifeq ($(OS), Windows_NT)
DETECTED_OS?=Windows DETECTED_OS?=Windows
...@@ -9,12 +10,14 @@ DETECTED_OS?=POSIX ...@@ -9,12 +10,14 @@ DETECTED_OS?=POSIX
LIBFOLDER?=lib LIBFOLDER?=lib
endif endif
all: Clean\ System\ Files/ctty.o
test: test.icl TTY.icl TTY.dcl Clean\ System\ Files/ctty.o test: test.icl TTY.icl TTY.dcl Clean\ System\ Files/ctty.o
clm -I $(DETECTED_OS) -IL Platform $(basename $<) -o $@ clm -I $(DETECTED_OS) -IL Platform $(basename $<) -o $@
Clean\ System\ Files/ctty.o: $(DETECTED_OS)/tty.c Clean\ System\ Files/ctty.o: tty.c
mkdir -p Clean\ System\ Files mkdir -p Clean\ System\ Files
$(CC) -c "$<" -o "$@" $(CC) $(CFLAGS) -c "$<" -o "$@"
Monitor.prj: Monitor.prj:
cpm project $(basename $@) create cpm project $(basename $@) create
...@@ -25,7 +28,7 @@ Monitor.prj: ...@@ -25,7 +28,7 @@ Monitor.prj:
install: Clean\ System\ Files/ctty.o $(LIBFILE) install: Clean\ System\ Files/ctty.o $(LIBFILE)
mkdir $(CLEAN_HOME)/$(LIBFOLDER)/CleanSerial mkdir $(CLEAN_HOME)/$(LIBFOLDER)/CleanSerial
cp -R TTY.[id]cl iTasksTTY.[id]cl $(DETECTED_OS)/Platform.[id]cl "Clean System Files" $(CLEAN_HOME)/$(LIBFOLDER)/CleanSerial cp -R TTY.[id]cl iTasksTTY.[id]cl $(DETECTED_OS)/Platform.[id]cl "Clean System Files" $(CLEAN_HOME)/$(LIBFOLDER)/CleanSerial
cp -f $(DETECTED_OS)/CleanSerial_library "$(CLEAN_HOME)/$(LIBFOLDER)/CleanSerial" cp -f $(DETECTED_OS)/*_library "$(CLEAN_HOME)/$(LIBFOLDER)/CleanSerial"
clean: clean:
$(RM) -r $(DETECTED_OS)/Clean\ System\ Files/* Clean\ System\ Files/* test $(RM) -r $(DETECTED_OS)/Clean\ System\ Files/* Clean\ System\ Files/* test
implementation module Platform implementation module Platform
import code from library "CleanSerial_library" import code from library "CleanSerial_library"
import code from library "CleanSerial2_library"
import System._Pointer import System._Pointer
import System._WinBase import System._WinBase
......
//#include <ctype.h>
//#include <errno.h>
//#include <fcntl.h>
//#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//#include <time.h>
//
//#include <termios.h>
//#include <sys/ioctl.h>
#include "../Clean.h"
#ifdef DEBUG
#define debug(s) {puts(s); fflush(stdout);}
#else
#define debug(s) ;
#endif
//#ifdef __APPLE__
//int CMSPAR = 0;
//#endif
//
//#define INITIAL_BUFFERSIZE 2
#define die(s) {perror(s);exit(EXIT_FAILURE);}
//static speed_t baudrates[] = {B0, B50, B75, B110, B134, B150, B200, B300, B600,
// B1200, B1800, B2400, B4800, B9600, B19200, B38400, B57600, B115200,
// B230400};
//static int bytesizes[4] = {CS5, CS6, CS7, CS8};
static char *error = "";
//
//struct termioslist
//{
// int fd;
// struct termios to;
// struct termioslist *next;
//};
//
//struct termioslist *head = NULL;
//static void *my_malloc(size_t s)
//{
// void *r = malloc(s);
// if(r == NULL)
// die("malloc");
// return r;
//}
//static struct termios *getTermios(int fd)
//{
// struct termioslist *h = head;
// while(h != NULL)
// if(h->fd == fd)
// return &h->to;
// return NULL;
//}
//
//static void remTermios(int fd)
//{
// struct termioslist *beforeit = NULL;
// struct termioslist *it = head;
// while(it != NULL){
// if(it->fd == fd){
// if(beforeit == NULL)
// head = it->next;
// else
// beforeit->next = it->next;
// free(it);
// break;
// }
// beforeit = it;
// it = it->next;
// }
//}
//
//static char *cleanStringToCString(CleanString s)
//{
// unsigned long len = CleanStringLength(s);
// char *cs = (char *)my_malloc(len+1);
// memcpy(cs, CleanStringCharacters(s), len);
// cs[len] = '\0';
// return cs;
//}
//
//static void addTermios(int fd, struct termios *t)
//{
// struct termioslist *new = my_malloc(sizeof(struct termioslist));
// new->fd = fd;
// memcpy(&new->to, t, sizeof(struct termios));
// new->next = NULL;
// if(head == NULL){
// head = new;
// } else {
// struct termioslist *h = head;
// while(h->next != NULL)
// h = h->next;
// h->next = new;
// }
//}
//
void ttyopen(CleanString fn, int baudrate, int bytesize, int parity,
int stopbits, int xonoff, int sleepTime, int *status, int *fd)
{
}
// debug("ttyopen");
// struct termios tio;
// char *cs_fn = cleanStringToCString(fn);
// debug(cs_fn);
// *fd = open(cs_fn, O_RDWR | O_NOCTTY | O_NONBLOCK);
// *status = 0;
// fcntl(*fd, F_SETFL, 0);
// if(*fd < 0){
// error = strerror(errno);
// } else {
// //Get
// tcgetattr(*fd, &tio);
// addTermios(*fd, &tio);
// //Baudrate
// cfsetispeed(&tio, baudrates[baudrate]);
// //Bytesize
// tio.c_cflag &= ~CSIZE;
// tio.c_cflag |= bytesizes[bytesize];
// //Parity
// if(parity == 0) {
// tio.c_cflag &= ~PARENB | ~INPCK;
// } else if(parity == 1) {
// tio.c_cflag |= PARODD | PARENB;
// } else if(parity == 2) {
// tio.c_cflag |= PARENB;
// tio.c_cflag &= ~PARODD;
// } else if(parity == 3) {
// tio.c_cflag |= PARENB | CMSPAR;
// tio.c_cflag &= ~PARODD;
// } else if( parity == 4) {
// tio.c_cflag |= PARENB | CMSPAR | PARODD;
// }
// //Stopbits
// if(stopbits != 0)
// tio.c_cflag |= CSTOPB;
// else
// tio.c_cflag &= ~CSTOPB;
// //Xonoff
// if(xonoff == 1)
// tio.c_cflag |= IXON;
// else
// tio.c_cflag &= ~IXON;
// //Set
// tio.c_oflag = 0;
// tio.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN | ISIG);
//// tio.c_lflag |= ICANON;
//
// tio.c_cc[VMIN]=5;
// tio.c_cc[VTIME]=0;
// tcsetattr(*fd, TCSANOW, &tio);
//
// *status = 1;
// error = strerror(errno);
// }
// free(cs_fn);
// debug("ttyopen-done");
//}
//
unsigned long *errcl = NULL;
void ttyerror(CleanString *result)
{
debug("ttyerror");
// if(errcl != NULL)
// free(errcl);
// errcl = my_malloc(
// sizeof(unsigned long)*CleanStringSizeInts(strlen(error)));
// *result = (CleanString) errcl;
// memcpy(CleanStringCharacters(errcl), error, strlen(error));
// CleanStringLength(errcl) = strlen(error);
// debug("ttyerror-done");
}
void ttyread(int fd, int *ch, int *fdo)
{
// debug("ttyread");
// unsigned int c;
// if(read(fd, &c, 1) == -1){
// die("read");
// }
// *ch = (int)c;
// *fdo = fd;
// debug("ttyread done");
}
void ttyavailable(int fd, int *r, int *fdo)
{
// debug("ttyavailable");
// fd_set fds;
// struct timeval tv;
// tv.tv_sec = 0;
// tv.tv_usec = 0;
//
// FD_ZERO(&fds);
// FD_SET(fd, &fds);
//
// *r = select(fd+1, &fds, NULL, NULL, &tv);
// if(*r == -1)
// die("select");
// *fdo = fd;
// debug("ttyavailable-done");
}
int ttywrite(CleanString s, int fd)
{
debug("ttywrite");
// int i;
// for(i = 0; i< CleanStringLength(s); i++){
// unsigned char c = ((unsigned char*)CleanStringCharacters(s))[i];
// printf("%02x(%u) ", c, c);
// }
// write(fd, (void *)CleanStringCharacters(s), CleanStringLength(s));
// tcdrain(fd);
// debug("ttywrite-done");
// return fd;
}
int ttyclose(int fd)
{
debug("ttyclose");
// struct termios *to = getTermios(fd);
// tcsetattr(fd, TCSANOW, to);
// remTermios(fd);
// int ret = close(fd);
// error = strerror(errno);
// debug("ttyclose-done");
// return ret + 1;
}
...@@ -15,7 +15,7 @@ rm -rf "./Clean System Files/*" ...@@ -15,7 +15,7 @@ rm -rf "./Clean System Files/*"
DETECTED_OS=Windows CC=x86_64-w64-mingw32-gcc make -B 'Clean System Files/ctty.o' DETECTED_OS=Windows CC=x86_64-w64-mingw32-gcc make -B 'Clean System Files/ctty.o'
mv 'Clean System Files' CleanSerial mv 'Clean System Files' CleanSerial
cp TTY.[id]cl iTasksTTY.[id]cl Windows/Platform.[id]cl CleanSerial cp TTY.[id]cl iTasksTTY.[id]cl Windows/Platform.[id]cl CleanSerial
cp Windows/CleanSerial_library CleanSerial/Clean\ System\ Files cp Windows/*_library CleanSerial/Clean\ System\ Files
zip -rv CleanSerial-win64.zip CleanSerial zip -rv CleanSerial-win64.zip CleanSerial
rm -r CleanSerial rm -r CleanSerial
...@@ -25,6 +25,6 @@ rm -rf "./Clean System Files/*" ...@@ -25,6 +25,6 @@ rm -rf "./Clean System Files/*"
DETECTED_OS=Windows CC=x86_64-w64-mingw32-gcc-win32 make -B 'Clean System Files/ctty.o' DETECTED_OS=Windows CC=x86_64-w64-mingw32-gcc-win32 make -B 'Clean System Files/ctty.o'
mv 'Clean System Files' CleanSerial mv 'Clean System Files' CleanSerial
cp TTY.[id]cl iTasksTTY.[id]cl Windows/Platform.[id]cl CleanSerial cp TTY.[id]cl iTasksTTY.[id]cl Windows/Platform.[id]cl CleanSerial
cp Windows/CleanSerial_library CleanSerial/Clean\ System\ Files cp Windows/*_library CleanSerial/Clean\ System\ Files
zip -rv CleanSerial-win32.zip CleanSerial zip -rv CleanSerial-win32.zip CleanSerial
rm -r CleanSerial rm -r CleanSerial
...@@ -4,18 +4,31 @@ import StdEnv ...@@ -4,18 +4,31 @@ import StdEnv
import TTY import TTY
TTYerrorclose :: !*File !*World -> *World TTYerrorclose :: !*World -> *World
TTYerrorclose f w TTYerrorclose w
# (err, w) = TTYerror w # (err, w) = TTYerror w
# (ok, w) = fclose (f <<< err <<< "\n") w = cwrite err w
| not ok = abort "Couldn't close file"
= w cwrite :: String !*World -> *World
cwrite s w
# (io, w) = stdio w
= snd (fclose (io <<< s <<< "\n") w)
Start :: *World -> *World Start :: *World -> *World
Start w Start w
# (io, w) = stdio w # w = cwrite "open" w
# (ok, tty, w) = TTYopen {zero & devicePath="/dev/ttyUSB0"} w # (ok, tty, w) = TTYopen {zero & sleepTime=2, devicePath="COM3"} w
| not ok = TTYerrorclose w
#! (l, tty) = TTYreadline tty
= w
/*
# io = io <<< "close\n"
# (ok, w) = TTYclose tty w
# io = io <<< "ok: " <<< toString ok <<< "\n"
| not ok = TTYerrorclose io w | not ok = TTYerrorclose io w
= snd (fclose io w)
*/
/*
#! tty = TTYwrite "echo123\n" tty #! tty = TTYwrite "echo123\n" tty
#! (av, e, tty) = TTYavailable tty #! (av, e, tty) = TTYavailable tty
# io = io <<< ("Bytes available: " +++ toString av +++ "\n") # io = io <<< ("Bytes available: " +++ toString av +++ "\n")
...@@ -23,4 +36,5 @@ Start w ...@@ -23,4 +36,5 @@ Start w
# io = io <<< ("Line read: " +++ l) # io = io <<< ("Line read: " +++ l)
#! (ok, w) = TTYclose tty w #! (ok, w) = TTYclose tty w
| not ok = TTYerrorclose io w | not ok = TTYerrorclose io w
= snd (fclose io w) = snd (fclose io w)*/
//Windows imports
#ifdef _WIN32
#include <Windows.h>
typedef HANDLE ttyhandle;
//Posix imports
#else
#include <ctype.h> #include <ctype.h>
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <unistd.h> #include <unistd.h>
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
#include <termios.h> #include <termios.h>
#include <sys/ioctl.h> #include <sys/ioctl.h>
typedef int ttyhandle;
#endif
#include "../Clean.h" //Common imports
#include <stdio.h>
#include <stdbool.h>
#include "Clean.h"
#ifdef DEBUG #ifdef DEBUG
#define debug(s) {puts(s); fflush(stdout);} #define debug(s) {printf("%s\n", s);}
#else #else
#define debug(s) ; #define debug(s) ;
#endif #endif
...@@ -22,33 +34,64 @@ ...@@ -22,33 +34,64 @@
int CMSPAR = 0; int CMSPAR = 0;
#endif #endif
#define INITIAL_BUFFERSIZE 2
#define die(s) {perror(s);exit(EXIT_FAILURE);} #define die(s) {perror(s);exit(EXIT_FAILURE);}
#ifdef _WIN32
static int baudrates[] = {0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800,
2400, 4800, 9600, 19200, 38400, 57600, 115200, 230400};
static int bytesizes[4] = {5, 6, 7, 8};
#else
static speed_t baudrates[] = {B0, B50, B75, B110, B134, B150, B200, B300, B600, static speed_t baudrates[] = {B0, B50, B75, B110, B134, B150, B200, B300, B600,
B1200, B1800, B2400, B4800, B9600, B19200, B38400, B57600, B115200, B1200, B1800, B2400, B4800, B9600, B19200, B38400, B57600, B115200,
B230400}; B230400};
static int bytesizes[4] = {CS5, CS6, CS7, CS8}; static int bytesizes[4] = {CS5, CS6, CS7, CS8};
static char *error = ""; #endif
static char *error = "NoError";
#ifdef _WIN32
#endif
static void *my_malloc(size_t s)
{
void *r;
#ifdef _WIN32
r = HeapAlloc(GetProcessHeap(), 0, s);
#else
if((r = malloc(s)) == NULL)
die("my_malloc");
#endif
return r;
}
static void my_free(void *p)
{
#ifdef _WIN32
HeapFree(GetProcessHeap(), 0, p);
#else
free(p);
#endif
}
static char *cleanStringToCString(CleanString s)
{
unsigned long len = CleanStringLength(s);
char *cs = (char *)my_malloc(len+1);
memcpy(cs, CleanStringCharacters(s), len);
cs[len] = '\0';
return cs;
}
#ifndef _WIN32
//This buggery is needed for linux systems that don't reset the termios settings...
struct termioslist struct termioslist
{ {
int fd; int fd;
struct termios to; struct termios to;
struct termioslist *next; struct termioslist *next;
}; };
struct termioslist *head = NULL; struct termioslist *head = NULL;
static void *my_malloc(size_t s)
{
void *r = malloc(s);
if(r == NULL)
die("malloc");
return r;
}
static struct termios *getTermios(int fd) static struct termios *getTermios(int fd)
{ {
struct termioslist *h = head; struct termioslist *h = head;
...@@ -57,7 +100,6 @@ static struct termios *getTermios(int fd) ...@@ -57,7 +100,6 @@ static struct termios *getTermios(int fd)
return &h->to; return &h->to;
return NULL; return NULL;
} }
static void remTermios(int fd) static void remTermios(int fd)
{ {
struct termioslist *beforeit = NULL; struct termioslist *beforeit = NULL;
...@@ -68,23 +110,13 @@ static void remTermios(int fd) ...@@ -68,23 +110,13 @@ static void remTermios(int fd)
head = it->next; head = it->next;
else else
beforeit->next = it->next; beforeit->next = it->next;
free(it); my_free(it);
break; break;
} }
beforeit = it; beforeit = it;
it = it->next; it = it->next;
} }
} }
static char *cleanStringToCString(CleanString s)
{
unsigned long len = CleanStringLength(s);
char *cs = (char *)my_malloc(len+1);
memcpy(cs, CleanStringCharacters(s), len);
cs[len] = '\0';
return cs;
}
static void addTermios(int fd, struct termios *t) static void addTermios(int fd, struct termios *t)
{ {
struct termioslist *new = my_malloc(sizeof(struct termioslist)); struct termioslist *new = my_malloc(sizeof(struct termioslist));
...@@ -100,73 +132,136 @@ static void addTermios(int fd, struct termios *t) ...@@ -100,73 +132,136 @@ static void addTermios(int fd, struct termios *t)
h->next = new; h->next = new;
} }
} }
#endif
void ttyopen(CleanString fn, int baudrate, int bytesize, int parity, void ttyopen(CleanString fn, int baudrate, int bytesize, int parity,
int stopbits, int xonoff, int sleepTime, int *status, int *fd) int stopbits, int xonoff, int sleepTime, int *status, ttyhandle *fd)
{ {
debug("ttyopen"); debug("ttyopen");
struct termios tio;
char *cs_fn = cleanStringToCString(fn); char *cs_fn = cleanStringToCString(fn);
debug(cs_fn); debug(cs_fn);
*fd = open(cs_fn, O_RDWR | O_NOCTTY | O_NONBLOCK);
*status = 0; *status = 0;
#ifdef _WIN32
*fd = CreateFile(cs_fn, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);
debug("Opened");
if(*fd == INVALID_HANDLE_VALUE){
debug("Error opening");
error = strerror(errno);
return;
}
DCB dcb;
FillMemory(&dcb, sizeof(dcb), 0);
dcb.DCBlength = sizeof(dcb);
//Get
if (!GetCommState(*fd, &dcb)){
error = strerror(errno);
return;
}
//Baudrate
dcb.BaudRate = baudrates[baudrate];
//Bytesize
dcb.ByteSize = bytesizes[bytesize];
//Parity
if(parity == 0) {
dcb.fParity = false;
} else if(parity == 1) {
dcb.fParity = true;
dcb.Parity = ODDPARITY;
} else if(parity == 2) {
dcb.fParity = true;
dcb.Parity = EVENPARITY;
} else if(parity == 3) {
dcb.fParity = true;
//Parity space???
} else if( parity == 4) {
dcb.fParity = true;
dcb.Parity = MARKPARITY;
}
//Stopbits
if(stopbits != 0)
dcb.StopBits = ONESTOPBIT;
else
dcb.StopBits = TWOSTOPBITS;
//Xonoff
if(xonoff == 1)
dcb.fTXContinueOnXoff = true;
else
dcb.fTXContinueOnXoff = false;
//Set
//tio.c_oflag = 0;
//tio.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN | ISIG);
if (!SetCommState(*fd, &dcb)){
error = strerror(errno);
return;
}
*status = 1;
if(sleepTime > 0){
//Sleep on windows is in milliseconds...
Sleep(sleepTime*1000);
}
#else
struct termios tio;
*fd = open(cs_fn, O_RDWR | O_NOCTTY | O_NONBLOCK);
fcntl(*fd, F_SETFL, 0); fcntl(*fd, F_SETFL, 0);
if(*fd < 0){ if(*fd < 0){
error = strerror(errno); error = strerror(errno);
} else { return;
//Get }
tcgetattr(*fd, &tio); //Get
addTermios(*fd, &tio); tcgetattr(*fd, &tio);
//Baudrate addTermios(*fd, &tio);
cfsetispeed(&tio, baudrates[baudrate]); //Baudrate
//Bytesize cfsetispeed(&tio, baudrates[baudrate]);
tio.c_cflag &= ~CSIZE; //Bytesize
tio.c_cflag |= bytesizes[bytesize]; tio.c_cflag &= ~CSIZE;
//Parity tio.c_cflag |= bytesizes[bytesize];
if(parity == 0) { //Parity
tio.c_cflag &= ~PARENB | ~INPCK; if(parity == 0) {
} else if(parity == 1) { tio.c_cflag &= ~PARENB | ~INPCK;
tio.c_cflag |= PARODD | PARENB; } else if(parity == 1) {
} else if(parity == 2) { tio.c_cflag |= PARODD | PARENB;
tio.c_cflag |= PARENB; } else if(parity == 2) {
tio.c_cflag &= ~PARODD; tio.c_cflag |= PARENB;
} else if(parity == 3) { tio.c_cflag &= ~PARODD;
tio.c_cflag |= PARENB | CMSPAR; } else if(parity == 3) {
tio.c_cflag &= ~PARODD; tio.c_cflag |= PARENB | CMSPAR;
} else if( parity == 4) { tio.c_cflag &= ~PARODD;
tio.c_cflag |= PARENB | CMSPAR | PARODD; } else if( parity == 4) {
} tio.c_cflag |= PARENB | CMSPAR | PARODD;
//Stopbits
if(stopbits != 0)
tio.c_cflag |= CSTOPB;
else
tio.c_cflag &= ~CSTOPB;
//Xonoff
if(xonoff == 1)
tio.c_cflag |= IXON;
else
tio.c_cflag &= ~IXON;
//Set
tio.c_oflag = 0;
tio.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN | ISIG);
#ifdef __APPLE__
tio.c_cflag |= CLOCAL;
#endif
tio.c_cc[VMIN]=1;
tio.c_cc[VTIME]=0;
tcsetattr(*fd, TCSANOW, &tio);
*status = 1;
error = strerror(errno);
} }
//Stopbits
if(stopbits != 0)
tio.c_cflag |= CSTOPB;
else
tio.c_cflag &= ~CSTOPB;
//Xonoff
if(xonoff == 1)
tio.c_cflag |= IXON;
else
tio.c_cflag &= ~IXON;
//Set
tio.c_oflag = 0;
tio.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN | ISIG);
#ifdef __APPLE__
tio.c_cflag |= CLOCAL;
#endif
tio.c_cc[VMIN]=1;
tio.c_cc[VTIME]=0;
tcsetattr(*fd, TCSANOW, &tio);
*status = 1;
error = strerror(errno);
if(sleepTime > 0){ if(sleepTime > 0){
sleep(sleepTime); sleep(sleepTime);
tcflush(*fd, TCIOFLUSH); tcflush(*fd, TCIOFLUSH);
} }
#endif
free(cs_fn); my_free(cs_fn);
debug("ttyopen-done"); debug("ttyopen-done");
} }
...@@ -175,7 +270,7 @@ void ttyerror(CleanString *result) ...@@ -175,7 +270,7 @@ void ttyerror(CleanString *result)
{ {
debug("ttyerror"); debug("ttyerror");
if(errcl != NULL) if(errcl != NULL)
free(errcl); my_free(errcl);
errcl = my_malloc( errcl = my_malloc(
sizeof(unsigned long)*CleanStringSizeInts(strlen(error))); sizeof(unsigned long)*CleanStringSizeInts(strlen(error)));
*result = (CleanString) errcl;