text: #include <stdlib.h>
  #include <string.h>
  #include <errno.h>
  #include "common.h"
  #include "object.h"
  #include "script.h"
  
  static void intstr(struct obj *);
  static void liststr(struct obj *);
  
  static void reprinit(struct obj *obj, enum objtype type) {
  	obj->type = type;
  	switch (type) {
  		case OBJ_INT:
  			obj->repr.i = 0;
  			break;
  		case OBJ_LIST:
  			obj->repr.list = NULL;
  			break;
  		case OBJ_SCRIPT:
  			obj->repr.script = NULL;
  			break;
  	}
  }
  
  struct obj *objinit(char *str) {
  	struct obj *obj = xmalloc(sizeof(*obj));
  	obj->refc = 1;
  	obj->str = str;
  	obj->type = 0;
  	return obj;
  }
  
  struct obj *objnew(enum objtype type) {
  	struct obj *obj = xmalloc(sizeof(*obj));
  	obj->refc = 1;
  	obj->str = NULL;
  	reprinit(obj, type);
  	return obj;
  }
  
  void objfree(struct obj *obj) {
  	if (!--obj->refc) {
  		free(obj->str);
  		objclear(obj, 0);
  		free(obj);
  	}
  }
  
  void objfreestr(struct obj *obj) {
  	if (obj->str) {
  		free(obj->str);
  		obj->str = NULL;
  	}
  }
  
  void objclear(struct obj *obj, enum objtype newtype) {
  	size_t i;
  
  	switch (obj->type) {
  		case OBJ_LIST:
  			for (i = 0; i < vec_len(obj->repr.list); i++)
  				objfree(obj->repr.list[i]);
  			vec_free(obj->repr.list);
  			break;
  		case OBJ_SCRIPT:
  			freescript(obj->repr.script);
  			break;
  		case OBJ_INT:
  			break;
  	}
  
  	reprinit(obj, newtype);
  }
  
  struct obj *objmut(struct obj *obj) {
  	struct obj *new;
  	size_t i;
  
  	if (obj->refc == 1)
  		return obj;
  
  	new = objnew(obj->type);
  
  	if (!obj->type)
  		new->str = xstrdup(obj->str);
  
  	switch (obj->type) {
  		case OBJ_INT:
  			new->repr.i = obj->repr.i;
  			break;
  		case OBJ_LIST:
  			for (i = 0; i < vec_len(obj->repr.list); i++) {
  				obj->repr.list[i]->refc++;
  				vec_push(new->repr.list, obj->repr.list[i]);
  			}
  			break;
  		case OBJ_SCRIPT:
  			new->type = 0;
  			new->str = xstrdup(obj->str);
  			break;
  	}
  
  	return new;
  }
  
  bool objeq(struct obj *a, struct obj *b) {
  	size_t i;
  	if (a->type == b->type) {
  		switch (a->type) {
  			case OBJ_INT:
  				return a->repr.i == b->repr.i;
  			case OBJ_LIST:
  				if (vec_len(a->repr.list) != vec_len(b->repr.list))
  					return false;
  				for (i = 0; i < vec_len(a->repr.list); i++)
  					if (!objeq(a->repr.list[i], b->repr.list[i]))
  						return false;
  				return true;
  			case OBJ_SCRIPT:
  				break;
  		}
  	}
  	return !strcmp(objstr(a), objstr(b));
  }
  
  char *objstr(struct obj *obj) {
  	if (obj->str)
  		return obj->str;
  
  	switch (obj->type) {
  		case OBJ_INT:
  			intstr(obj);
  			break;
  		case OBJ_LIST:
  			liststr(obj);
  			break;
  		case OBJ_SCRIPT:
  			err("OBJ_SCRIPT with no string");
  	}
  	return obj->str;
  }
  
  long objint(struct obj *obj) {
  	char *str, *endp;
  
  	if (obj->type == OBJ_INT)
  		return obj->repr.i;
  
  	str = objstr(obj);
  	errno = 0;
  	obj->repr.i = strtol(str, &endp, 0);
  	if (errno || *endp || endp == str)
  		err("Invalid integer '%s'", str);
  	return obj->repr.i;
  }
  
  vec(struct obj *) objlist(struct obj *obj) {
  	char *start, *end;
  	size_t depth;
  	bool string;
  
  	if (obj->type == OBJ_LIST)
  		return obj->repr.list;
  
  	start = objstr(obj);
  	objclear(obj, OBJ_LIST);
  	while (*start) {
  		start += strspn(start, " \t\n");
  		if (*start == '{') {
  			end = start++;
  			depth = 1;
  			while (depth) {
  				end = strpbrk(++end, "\\{}");
  				if (!end)
  					err("Unterminated string");
  				if (*end == '\\') {
  					if (!*++end)
  						err("Unterminated string");
  				} else if (*end == '{') {
  					depth++;
  				} else {
  					depth--;
  				}
  			}
  			vec_push(obj->repr.list, objinit(xstrndup(start, end-start)));
  			start = ++end;
  		} else {
  			string = (*start == '"');
  			if (string)
  				end = ++start;
  			else
  				end = start-1;
  			while (1) {
  				end = strpbrk(++end, string ? "\"\\" : "\\ \t\n");
  				if (!end) {
  					if (!string) {
  						end = start + strlen(start);
  						break;
  					} else {
  						err("Unterminated string");
  					}
  				} else if (*end == '\\') {
  					end++;
  				} else {
  					break;
  				}
  			}
  			vec_push(obj->repr.list, objinit(escexpand(start, end-start, NULL)));
  			start = end;
  			if (string)
  				start++;
  		}
  	}
  	return obj->repr.list;
  }
  
  static void intstr(struct obj *obj) {
  	long i, c, r;
  	unsigned long u;
  	size_t len;
  	char *p;
  	bool neg;
  
  	i = obj->repr.i;
  	neg = (i < 0);
  
  	len = 1;
  	c = i;
  	if (neg)
  		len++;
  	while ((c /= 10))
  		len++;
  
  	obj->str = xmalloc(len+1);
  	p = &obj->str[len];
  	*p = '\0';
  
  	u = neg ? -i : i;
  	if (!u) {
  		*--p = '0';
  	} else {
  		while (u) {
  			r = u % 10;
  			*--p = '0' + r;
  			u /= 10;
  		}
  		if (neg)
  			*--p = '-';
  	}
  }
  
  static void liststr(struct obj *obj) {
  	struct obj **cur;
  	char *str, *p;
  	size_t outlen, len, depth;
  	bool quote;
  	char c;
  
  	outlen = 0;
  	for (cur = obj->repr.list; cur < vec_end(obj->repr.list); cur++) {
  		str = objstr(*cur);
  		depth = 0;
  		p = str;
  		while ((p = strpbrk(p, "\\{}"))) {
  			if (*p == '\\') {
  				p++;
  				if (*p == '{' || *p == '}')
  					p++;
  				continue;
  			}
  			if (*p == '{')
  				depth++;
  			else if (*p == '}')
  				depth--;
  			p++;
  		}
  		if (outlen) {
  			obj->str = xrealloc(obj->str, outlen+1+1);
  			obj->str[outlen++] = ' ';
  		}
  		if (depth == 0) {
  			quote = !!strpbrk(str, " \t\n;");
  			len = strlen(str);
  			obj->str = xrealloc(obj->str, outlen+(quote?2:0)+len+1);
  			if (quote)
  				obj->str[outlen++] = '{';
  			memcpy(obj->str+outlen, str, len);
  			outlen += len;
  			if (quote)
  				obj->str[outlen++] = '}';
  		} else {
  			while (str && *str) {
  				p = strpbrk(str, "{} \t\n;");
  				if (!p || p > str) {
  					len = p ? (size_t)(p - str) : strlen(str);
  					obj->str = xrealloc(obj->str, outlen+len+1);
  					memcpy(obj->str+outlen, str, len);
  					outlen += len;
  				} else {
  					obj->str = xrealloc(obj->str, outlen+2+1);
  					obj->str[outlen++] = '\\';
  					switch (*p) {
  						case '\n':
  							c = 'n';
  							break;
  						case '\t':
  							c = 't';
  							break;
  						default:
  							c = *p;
  							break;
  					}
  					obj->str[outlen++] = c;
  					p++;
  				}
  				str = p;
  			}
  		}
  	}
  	if (outlen)
  		obj->str[outlen] = '\0';
  	else
  		obj->str = xstrdup("");
  }
  