/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8                               :vi │
╞══════════════════════════════════════════════════════════════════════════════╡
│ Copyright 2022 Justine Alexandra Roberts Tunney                              │
│                                                                              │
│ Permission to use, copy, modify, and/or distribute this software for         │
│ any purpose with or without fee is hereby granted, provided that the         │
│ above copyright notice and this permission notice appear in all copies.      │
│                                                                              │
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL                │
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED                │
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE             │
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL         │
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR        │
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER               │
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR             │
│ PERFORMANCE OF THIS SOFTWARE.                                                │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/calls/calls.h"
#include "libc/errno.h"
#include "libc/intrin/strace.h"
#include "libc/log/check.h"
#include "libc/runtime/runtime.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/o.h"
#include "tool/plinko/lib/char.h"
#include "tool/plinko/lib/cons.h"
#include "tool/plinko/lib/error.h"
#include "tool/plinko/lib/plinko.h"
#include "tool/plinko/lib/printf.h"

static int Read1(int);
static int Read2(int);

dontinstrument int ReadSpaces(int fd) {
  size_t n;
  ssize_t rc;
  for (;;) {
    rc = read(fd, g_buffer[fd], sizeof(g_buffer[fd]) - 1);
    if (rc != -1) {
      if ((n = rc)) {
        g_buffer[fd][n] = 0;
        bp[fd] = 1;
        return g_buffer[fd][0] & 255;
      } else if (fd == 0 && *inputs) {
        close(0);
        if (open(*inputs++, O_RDONLY) == -1) {
          ++fails;
          Flush(1);
          Fprintf(2, "error: open(%s) %s%n", inputs[-1], strerror(errno));
          longjmp(exiter, 1);
        }
      } else {
        Flush(1);
        Flush(2);
        longjmp(exiter, 1);
      }
    } else if (errno != EINTR) {
      ++fails;
      Flush(1);
      Fprintf(2, "error: read(%d) %s%n", fd, strerror(errno));
      longjmp(exiter, 1);
    }
  }
}

dontinstrument int ReadByte(int fd) {
  int c;
  if ((c = g_buffer[fd][bp[fd]++] & 255))
    return c;
  return ReadSpaces(fd);
}

dontinstrument int ReadChar(int fd) {
  int b, a = dx;
  for (;;) {
    dx = ReadByte(fd);
    if (dx != ';') {
      break;
    } else {
      do
        b = ReadByte(fd);
      while ((b != '\n'));
    }
  }
  if (a >= 0300) {
    for (b = 0200; a & b; b >>= 1) {
      a ^= b;
    }
    while ((dx & 0300) == 0200) {
      a <<= 6;
      a |= dx & 0177;
      dx = ReadByte(fd);
    }
  }
  if (0 < a && a < TERM) {
    return ToUpper(a);
  }
  Error("thompson-pike varint outside permitted range");
}

static int ReadListItem(int fd, int closer, int f(int)) {
  int x, y;
  if ((x = f(fd)) > 0) {
    if (Get(x) == MAKE(closer, TERM))
      return -0;
    if (Get(x) == MAKE(L'.', TERM)) {
      x = f(fd);
      if ((y = ReadListItem(fd, closer, Read1))) {
        Error("multiple list items after dot: %S", y);
      }
      return x;
    }
  }
  return ShareCons(x, ReadListItem(fd, closer, Read1));
}

static int ReadList(int fd, int closer) {
  int t;
  ++fails;
  t = ReadListItem(fd, closer, Read2);
  --fails;
  return t;
}

static int TokenizeInteger(int fd, int b) {
  dword a;
  int c, i;
  for (i = a = 0;; ++i) {
    if ((c = GetDiglet(ToUpper(dx))) != -1 && c < b) {
      a = (a * b) + c;
      ReadChar(fd);
    } else {
      ax = TERM;
      return Intern(a, TERM);
    }
  }
}

static void ConsumeComment(int fd) {
  int c, t = 1;
  for (;;) {
    c = ReadChar(fd);
    if (c == '#' && dx == '|')
      ++t;
    if (!t)
      return;
    if (c == '|' && dx == '#')
      --t;
  }
}

static int ReadAtomRest(int fd, int x) {
  int y;
  ax = y = TERM;
  if (x == L'\\')
    x = ReadChar(fd);
  if (!IsSpace(dx) && !IsParen(dx) && !IsMathAlnum(x) && !IsMathAlnum(dx)) {
    y = ReadAtomRest(fd, ReadChar(fd));
  }
  return Intern(x, y);
}

static int ReadAtom(int fd) {
  int a, s, x;
  x = ReadChar(fd);
  if ((s = Desymbolize(x)) != -1)
    return s;
  a = ReadAtomRest(fd, x);
  if (LO(Get(a)) == L'T' && HI(Get(a)) == TERM) {
    a = 1;
  } else if (LO(Get(a)) == L'N' && HI(Get(a)) != TERM &&
             LO(Get(HI(Get(a)))) == L'I' && HI(Get(HI(Get(a)))) != TERM &&
             LO(Get(HI(Get(HI(Get(a)))))) == L'L' &&
             HI(Get(HI(Get(HI(Get(a)))))) == TERM) {
    a = 0;
  }
  return a;
}

static int TokenizeComplicated(int fd) {
  int c;
  ReadChar(fd);
  switch ((c = ReadChar(fd))) {
    case L'\'':
      return List(kFunction, Read(fd));
    case L'B':
      return TokenizeInteger(fd, 2);
    case L'X':
      return TokenizeInteger(fd, 16);
    case L'Z':
      return TokenizeInteger(fd, 36);
    case L'O':
      return TokenizeInteger(fd, 8);
    case L'|':
      ConsumeComment(fd);
      return Read(fd);
    default:
      Error("unsuppported complicated syntax #%c [0x%x]", c, c);
  }
}

static int Read2(int fd) {
  int r, l;
  while (IsSpace((l = dx)))
    ReadChar(fd);
  switch (dx) {
    case L'#':
      r = TokenizeComplicated(fd);
      break;
    case L'\'':
      ReadChar(fd);
      r = ShareList(kQuote, Read(fd));
      break;
    case L'`':
      ReadChar(fd);
      r = ShareList(kBackquote, Read(fd));
      break;
    case L',':
      ReadChar(fd);
      if (dx == L'@') {
        ReadChar(fd);
        r = ShareList(kSplice, Read(fd));
      } else {
        r = ShareList(kComma, Read(fd));
      }
      break;
    case L'"':
      r = ShareList(kString, ReadString(fd, ReadByte(fd)));
      break;
    case L'(':
      ReadChar(fd);
      r = ReadList(fd, L')');
      break;
    case L'[':
      ReadChar(fd);
      r = ShareList(kSquare, ReadList(fd, L']'));
      break;
    case L'{':
      ReadChar(fd);
      r = ShareList(kCurly, ReadList(fd, L'}'));
      break;
    default:
      r = ReadAtom(fd);
      break;
  }
  return r;
}

static int ReadLambda(int fd, int n) {
  int a, c, r, q = 0;
  do {
    c = ReadChar(fd);
    if (c == L'λ') {
      for (a = 0; (c = ReadChar(fd)) != '.';) {
        a = Cons(Intern(c, TERM), a);
      }
      for (r = ReadLambda(fd, n); a; a = Cdr(a)) {
        r = List3(kLambda, Cons(Car(a), 0), r);
      }
    } else if (c == L'(') {
      r = ReadLambda(fd, n + 1);
    } else if (c == L')') {
      break;
    } else if (IsSpace(c)) {
      Raise(kRead);
    } else {
      r = Intern(c, TERM);
    }
    if (!q) {
      q = r;
    } else {
      q = List(q, r);
    }
    if (!n && dx == L')')
      break;
  } while (!IsSpace(dx));
  return q;
}

static int Read1(int fd) {
  while (IsSpace(dx))
    ReadChar(fd);
  // todo: fix horrible i/o
  if (dx == 0xCE && (g_buffer[fd][bp[fd]] & 255) == 0xbb) {
    return ReadLambda(fd, 0);
  }
  return Read2(fd);
}

int Read(int fd) {
  int r;
  ftrace_enabled(-1);
  strace_enabled(-1);
  r = Read1(fd);
  strace_enabled(+1);
  ftrace_enabled(+1);
  return r;
}
