#!/usr/bin/env ruby
# Copyright (c) 2024 Darren Smith. All rights reserved.
# 
# BSD 3 License
# 
# Redistribution and use in source and binary forms are permitted provided that
# the above copyright notice and this paragraph are duplicated in all such forms
# and that any documentation, advertising materials, and other materials related
# to such distribution and use acknowledge that the software was developed by
# Darren Smith. The name of Darren Smith may not be used to endorse or promote
# products derived from this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

# lex.rb #######################################################################
class Token < Struct.new(:str, :type, :char_no, :line_no, :explicit_end, :surrounded_by_whitespace)
  def split
    trailing_commas = str[/,*$/]
    str_base = $`
    c = char_no
    results = str_base.scan(/[^1-9]|[0-9]+/).map{|s|
      t = Token.new(s, s=~/[0-9]/ ? :int : :id, c, line_no)
      c += s.size
      t
    }
    if !trailing_commas.empty?
      if results[-1].str =~ /[a-zA-Z]/
        results[-1].str += trailing_commas
      else
        results << Token.new(trailing_commas, :commas, c, line_no)
      end
    end

    results
  end
end
def split_tokens(tokens, str)
  tokens.slice_when{|t| t.str == str }.
    map{|ts| ts.reject{|t| t.str == str } }
end
AllSymbols='@!?`~#%^&*-_=+[]|;<>.,()\'"{}$/\\:'.chars.map{|c|Regexp.escape c}

TokenTypes = {
  :comment => /# .*|\n#\n/, # also match \n#\n since many editors remove trailing spaces on save
  :int => /0|[0-9]+/,
  :char => /'(\\n|\\0|\\x[0-9a-fA-F][0-9a-fA-F]|.)?/m,
  :str => /"(\\.|[^"])*"?/m,
  :space => /[ \t]/,
  :newline => /\r\n|\r|\n/,
  :id => /[^#{AllSymbols.join} \t\n\r0-9][^#{AllSymbols.join} \t\n\r]*,*/,
  :commas => /,+/,
  :sym => /.,*/,
}
NewlineRx = TokenTypes[:newline]

LexRx = /#{TokenTypes.map{|name, rx|"(?<#{name}>#{rx.to_s})"}*"|"}/

def lex(code, line_no=1)
	tokens = []
  char_no = 1
  code.scan(LexRx) {
    match = $&
    type = $~.named_captures.select{|k, v|v}.keys[0].to_sym
    token=Token.new(match, type, char_no, line_no)
    token.surrounded_by_whitespace = [$`[-1], $'[0]].all?{|s| (s||" ") =~ /\s/ }
    line_no += match.scan(NewlineRx).size
    if match[NewlineRx]
      char_no = match.size-match.rindex(NewlineRx)
    else
      # FYI this counts tab as 1, and utf8 characters as len of their bytes, could be misleading
      char_no += match.size
  	end
    tokens << token if ![:space, :newline, :comment].include? type
  }
  tokens
end

# error.rb #####################################################################
def warn(msg, from=nil)
  error_output "WARNING: ".red + to_location(from) + msg + "\n"
end

def to_location(from)
  op = nil
  token = case from
    when Token
      from
    when AST
      from.token
    when IR
      op = from.op.name
      from.token
    when NilClass
      nil
    else
      raise "unknown location type %p " % from
    end

  if token
    "%s:%s (%s) " % [token.line_no||"?", token.char_no||"?", op && op.downcase != token.str.downcase ? "#{token.str} #{op}" : token.str]
  else
    ""
  end
end

class String
  def red
    "\e[31m#{self}\e[0m"
  end
end

class IogiiError < StandardError
  def initialize(message,from=nil)
    @message = message
    @from = from
  end
  def message
    "ERROR: ".red + to_location(@from) + @message
  end
end

# escape.rb ####################################################################
def inspect_char(char)
  return "'\"'" if char=='"'.ord # Don't escape this char (we would in a string)
  "'" + escape_str_char(char) + "'"
end

def escape_str_char(char)
  return "\\0" if char == "\0".ord
  return "\\n" if char == "\n".ord
  return "\\\\" if char == "\\".ord
  return "\\\"" if char == "\"".ord
  return char.to_i.chr if char >= " ".ord && char <= "~".ord # all ascii printables
  return "\\x0%s" % char.to_i.to_s(16) if char < 16 && char >= 0
  return "\\x%s" % (char%256).to_i.to_s(16) if char < 256 && char >= -2
  return "\\{%d}" % char
end

def parse_char(s)
  ans,offset=parse_str_char(s,0)
  raise "internal char error" if ans.size != 1 || offset != s.size
  ans[0].ord
end

def parse_str_char(s,i) # parse char in a string starting at position i
  if s[i]=="\\"
    if s.size <= i+1
      ["\\",i+1]
    elsif s[i+1] == "n"
      ["\n",i+2]
    elsif s[i+1] == "0"
      ["\0",i+2]
    elsif s[i+1] == "\\"
      ["\\",i+2]
    elsif s[i+1] == "\""
      ["\"",i+2]
    elsif s[i+1,3] =~ /x[0-9a-fA-F][0-9a-fA-F]/
      [s[i+2,2].to_i(16).chr,i+4]
    else
      ["\\",i+1]
    end
  else
    [s[i],i+1]
  end
end

def parse_str(s)
  i=0
  r=""
  while i<s.size
    c,i=parse_str_char(s,i)
    r<<c
  end
  r
end

# type.rb ######################################################################
# Prefixed so that they can be compared
EmptyType = :t0_empty # the type of an empty list, it can be anything
IntType = :t1_int
CharType = :t2_char
XSpec = :t3_s_x
ASpec = :t4_s_a
BSpec = :t5_s_b

def type_spec_parts(spec)
  spec.gsub("->"," ").split
end

def concrete_rank(spec)
  !(spec == ASpec || spec == BSpec) # todo what about empty?
end

def concrete_type(spec)
  !(spec == ASpec || spec == BSpec || spec == XSpec)
end

def spec_rank(part)
  t = spec_type(part)
  r = part.scan('[').size
  concrete_rank(t) ? r : ~r
end

def spec_type(part)
  case part.tr('[]','')
  when "x"
    XSpec
  when "a"
    ASpec
  when "b"
    BSpec
  when "int"
    IntType
  when "char"
    CharType
  when ""
    EmptyType
  else raise "unknown arg type" % part
  end
end

def type_to_str(sym)
  sym.to_s.sub(/^.*_/,'')
end
def type_and_rank_to_str(type,rank)
  r = "["*rank + type_to_str(type) + "]"*rank
  r == "[empty]" ? "[]" : r
end

def spec_matches(specs, args)
  specs.zip(args, coerce?(specs, args)).map{|s, a, c|
    spec_match(s, a) ? (c ? :m1_coerces : :m0_matches) : :m2_no_match
  }
end

def spec_match(spec, arg)
  return false if arg == EmptyType && (spec == IntType || spec == CharType)
  return false if spec == EmptyType && arg != EmptyType
  !(spec == IntType && arg == CharType)
end

def same_spec(specs, args, target)
  args.filter.with_index{|a,i| specs[i] == target }
end

def spec_to_ret(way, args)
  if concrete_type(way.ret_type)
    way.ret_type
  else
    same_spec(way.arg_types, args,  way.ret_type).max || EmptyType
  end
end

def coerce?(specs,args)
  specs.zip(args).map{|s,a|
    if s == CharType && a == IntType
      true
    else
      !concrete_type(s) && a == IntType && same_spec(specs, args, s).any?{|o| o == CharType }
    end
  }
end


# version.rb ###################################################################
Version = "0.2-alpha"

# ops.rb #######################################################################

class Op < Struct.new(:name, :parse_nargs, :block, :ways)
  def dup
    Op.new(name.dup, parse_nargs, block, ways.map(&:dup))
  end
  def dup_mod(mod)
    return self if mod <= 0
    r = dup
    r.ways.each{|w| w.mod += mod if w.can_mod? }
    r
  end
end

class Way < Struct.new(:name, :arg_ranks, :ret_rank, :arg_types, :ret_type, :mod, :vectorize, :promote, :impl)
  def mod_arg_ranks
    arg_ranks.map{|r| r < 0 ? ~r + mod : r }
  end
  def mod_ret_rank
    ret_rank < 0 ? ~ret_rank + mod : ret_rank
  end
  def dup
    Way.new(name, arg_ranks.dup, ret_rank, arg_types.dup, ret_type, mod, vectorize, promote, impl)
  end
  def can_mod?
    (arg_ranks + [ret_rank]).any?{|r| r < 0 }
  end
end

def lookup_op(name)
  (0..).each{|mod|
    op = Ops[name]
    return nil if op && mod>0 && !op.ways.any?(&:vectorize)
    return op.dup_mod(mod) if op
    return nil if !name[/,$/]
    name = $`
  }
end

def new_op(name, type_sig, block: false, vectorize: true, promote: true, &impl)
  promote = [promote] unless Array === promote
  parts = type_spec_parts(type_sig)
  *arg_ranks, ret_rank = parts.map{|arg| spec_rank(arg) }
  *arg_types, ret_type = parts.map{|arg| spec_type(arg) }

  way = Way.new(name, arg_ranks, ret_rank, arg_types, ret_type, 0, vectorize, promote, impl)
  Op.new(name, arg_ranks.size, block, [way])
end

Ops = {}
OpExamples = {}

if !defined? mk_op
  def mk_op(names, type_sig, example="", block: false, vectorize: true, promote: true, &impl)
    OpExamples[names] = example if example != ""
    names = names.split

    op = new_op(names[-1], type_sig, block: block, vectorize: vectorize, promote: promote, &impl)

    names.each{|name|
      if Ops[name]
        Ops[name].ways << op.ways[0]
      else
        Ops[name] = op.dup
      end
    }

    if op.ways[0].can_mod? && op.ways[0].vectorize
      mod_op = op.dup
      mod_op.ways[0].mod = 1

      names.each{|name|
        if name =~ /^[a-z]/
          name = name.sub(/^./){|first|first.upcase}
          if Ops[name]
            Ops[name].ways << mod_op.ways[0]
          else
            Ops[name] = mod_op.dup
          end
        end
      }
    end
  end

  def category(name); end
  def quickref_op(names, desc, example)
    OpExamples[names] = example if example != ""
  end
end

NotFirst = :not_first
RepSnd = :rep_second

category("mathy ops")
mk_op("+ add", "int x -> x", '1 2+ -> 3', &add=->a,b{ a.value+b.value })
mk_op("+ add", "char int -> char", "'a 1+ -> 'b'", &add)
mk_op("- sub", "x int -> x", '5 3- -> 2', &subt=->a,b{ a.value-b.value })
mk_op("- sub", "char char -> int", "'b 'a- -> 1", &subt)
mk_op("* mult", "int int -> int", '2 3* -> 6'){|a, b| a.value*b.value }
mk_op("/ div", "int int -> int", '7 2/ -> 3'){|a, b| b.value == 0 ? 0 : a.value/b.value }
mk_op("% mod", "x int -> int", '7 2% -> 1'){|a, b| b.value == 0 ? 0 : a.value%b.value }
mk_op("^ pow", "int int -> int", '2 3^ -> 8'){|a, b| pow(a.value, b.value) }
mk_op("_ negate", "int -> int", '1_ -> -1'){|a| -a.value }
mk_op("| abs", "int -> int", '5~| -> 5'){|a| a.value.abs }
mk_op("( pred", "x -> x", '4( -> 3'){|a| a.value-1 }
mk_op(") succ", "x -> x", "'a) -> 'b'"){|a| a.value+1 }
mk_op("} countTo", "int -> [int]", "3} -> [1,2,3]"){|a| range(1,a.value+1) }
mk_op("{ wholes", "[int]", "{ -> [0,1,2,..."){ range_from(0) }
mk_op("# sum", "[x] -> int", '1,2,3,4 # -> 10', promote: false){|a| to_eager_list(a).sum }

category("char ops")
mk_op("^ charRange", "char char -> [char]", "'a'f^ -> \"abcdef\""){|a,b| range(a.value, b.value+1) }
mk_op("_ read", "[char] -> int", '"23"_ -> 23'){|a| read_num(a)[0] }
mk_op("} readAll", "[char] -> [int]", '"1,2 3"} -> [1,2,3]'){|a| split_non_digits(a) }
mk_op("| ord", "char -> int", "'d| -> 100"){|a| a.value }
mk_op("|, chr", "int -> char", "100|, -> 'd'"){|a| a.value }
mk_op("` str", "int -> [char]", '5` -> "5"'){|a| str(a) }
mk_op("` strip", "[char] -> [char]", '" a b\n"` -> "a b"'){|a| strip(a,$at,$rank) }
mk_op("* replicate", "[char] int -> [char]", '"ab"3* -> "ababab"'){|a, b|
  concat(take(b.value,repeat(a).const).const) }
mk_op("* join", "[[char]] [[char]] -> [char]", '1,2,3 " "* -> "1 2 3"', promote: RepSnd){|a, b| join(a,b)}
mk_op("/ luds charClass", "char int -> int", '"Ab$"9/ -> [0,1,1]'){|a,b| char_class(a.value, b.value) }
mk_op("/ split", "[char] [char] -> [[char]]", '"a  b c " " "/ -> ["a","b","c"]'){|a, b| s=cut(a,repeat(b).const).const; filter(s,s,nil,1) }
mk_op("% cut splitKeepEmpties", "[char] [[char]] -> [[char]]",'"a  b c " " "% -> ["a","","b","c",""]', promote: RepSnd){|a, b| cut(a,b) }

category("generic ops")
mk_op("a append", "[a] [a] -> [a]", '"ab" "cd"a -> "abcd"'){|a, b| append(a,b) }
mk_op("b backwards reverse", "[a] -> [a]", '"abc"b -> "cba"', promote: false){|a| reverse(a) }
mk_op("c cons", "[a] a -> [a]", '1,2 0c -> [0,1,2]'){|a, b| [b, a] }
mk_op("d drop", "[a] int -> [a]", '"abcde" 2d -> "cde"'){|a, b| drop(b.value, a) }
mk_op("g get", "[a] int -> a", '"abcd" 2g -> \'c\''){|a, b| z=zero($rank,$at); a = drop(b.value, a); a==[] ? z : a[0].value }
mk_op("h head", "[a] -> a", '"abc"h -> \'a\'', promote: false){|a| z=zero($rank,$at); a.empty ? z : a.value[0].value }
mk_op("j just", "a -> [a]", "1j -> [1]"){|a|[a, [].const]}
mk_op("k keep take", "[a] int -> [a]", '"abcde" 2k -> "ab"'){|a, b| take(b.value, a) }
mk_op("l last", "[a] -> a", '"abc"l -> \'c\'', promote: false){|a| z=zero($rank,$at); last(a, z.const) }
mk_op("n not", "a -> int", '0,1,2n -> [1,0,0]', promote: false){|a| truthy($at, $rank, a.value) ? 0 : 1 }
mk_op("O Or o, or, or2", "int [char] -> [char]", promote: NotFirst){|a,b| truthy($at, 0, a.value) ? str(a) : b.value }
mk_op("o or", "a a -> a", '" bc" \'- o -> "-bc"', promote: NotFirst){|a,b| truthy($at, $rank, a.value) ? a.value : b.value }
mk_op("p pivot transpose", "[[a]] -> [[a]]", '"abc","def"p -> ["ad","be","cf"]', promote: false){|a| transpose(a, $at, $rank) }
mk_op("q quantity len", "[a] -> int", '"abc"q -> 3', promote: false){|a| len(a.value) }
mk_op("r repeat", "a -> [a]", "1r -> [1,1,1,1,1,1..."){|a| repeat(a) }
mk_op("s sortBy", "[a] [b] -> [a]", '"abc","d":len S -> ["d","abc"]', promote: false){|a,b| sort_by(a,b,$rank) }
mk_op("t tail", "[a] -> [a]", '"abc"t -> "bc"', promote: false){|a| a.empty ? [] : a.value[1].value }
mk_op("u unite concat", "[[a]] -> [a]", '"ab","cd","e"u -> "abcde"', promote: false){|a| concat(a) }
mk_op("v vet filter", "[a] [b] -> [a]", '"abcdef":2%v -> "ace"'){|a,b| filter(a,b,$bt,$rank) }
mk_op("w while takeWhile", "[a] [b] -> [a]", '"abc def":w -> "abc"', promote: false){|a,b| take_while(a,b,$bt,$rank) }
mk_op("x exclude setDiff", "[a] [a] -> [a]", '"abra" "ra"x -> "ba"'){|a,b| set_diff(a,b) }
mk_op("y yesNo ifElse", "a b b -> b", '0 1 2 y -> 2', promote: NotFirst){|a,b,c| truthy($at, $rank, a.value) ? b.value : c.value }
mk_op("z zeroPad", "[a] -> [a]", '1,2,3 z -> [1,2,3,0,0,...'){|a| padv(a, zero($rank,$at).const) }
mk_op("= equal", "a a -> int", "1,2,3 1= -> [1,0,0]"){|a,b| spaceship(a, b, $rank) == 0 ? 1 : 0 }
mk_op("< lessThan", "a a -> int", '1,2,3 2< -> [1,0,0]'){|a,b| spaceship(a,b,$rank) == -1 ? 1 : 0 }
mk_op(". index", "[a] a -> int", '"abcd" \'c . -> 3'){|a,b| index(a,b, $rank) }
mk_op("& reshape", "[a] [int] -> [[a]]", '"abcdef" 2& -> ["ab","cd","ef"]', promote: RepSnd){|a,b| reshape(a,b) }
mk_op("? debut isFirst", "[a] -> [int]", '"aardvark"? -> [1,0,1,1,1,0,0,1]', promote: false){|a| isuniq(a) }
mk_op("\\ chunkWhen", "[a] [b] -> [[a]]", '"ab cd e":\\ -> ["ab ","cd ","e"]', promote: false){|a,b| chunk_while(a,b,$bt,$rank) }


category("folding ops") # (return type is type yielded)
mk_op("i iterate", "a -> [a]", '0 i 1,2,3+ -> [0,1,3,6]', block: true)
mk_op("e expand iterate0", "[a]", 'e 1,2,3+ -> [1,3,6]', block: true)
mk_op("f foldr", "a -> [a]", '0 f 1,2,3+ -> 6', block: true)
mk_op("m meld foldr0", "[a]", 'm 1,2,3+ -> 6', block: true)

category("stack ops")
mk_op(": dup", "a -> a", "5: -> 5 5")
mk_op("; mdup", "a -> a", "5;1- -> 4 5", block: true)

category("special symbols")
quickref_op("$", "arg", "5;$+ -> 10 5")
quickref_op("!", "pop from outer stack", "2 5;!+ -> 7 5")
quickref_op(">", "end block", "3;))> -> 5 3")
quickref_op(",", "unvec", '"ab"j, -> ["ab"]')
quickref_op("@", "register get/set", "@( 5@) -> 4 6")
quickref_op("[ ]", "push pop", "5[( ]) -> 4 6")
quickref_op("~", "assignment", "5~a( a) -> 4 6")

# available for char +
category("low rank overloads")
# Available (for int):
# GSV  2 arg
# LZ   1 arg
# FI   block using

mk_op("U uppercase", "char -> char", "'a U -> 'A'"){|a| a=a.value; a>='a'.ord && a<='z'.ord ? a-32 : a }
mk_op("L lowercase", "char -> char", "'A L -> 'a'"){|a| a=a.value; a>='A'.ord && a<='Z'.ord ? a+32 : a }
mk_op("P parts splitWS", "[char] -> [[char]]", '"ab c\n d" P -> ["ab","c","d"]'){|a| x=promise{ group_while(a,a,0,CharType) }; filter(x,x,CharType,1) }

mk_op("p digits", "int -> [int]", '123 p -> [1,2,3]'){|a| v=to_base(a.value,10); v.empty? ? [0.const, Null] : v }
mk_op("U undigits", "[int] -> int", '1,2,3 U -> 123', promote: false){|a| undigits(a) }
mk_op("K kCopy", "int int -> [int]", '4 3K -> [4,4,4]'){|a,b| take(b.value,repeat(a).const) }
mk_op("D digitize toBase", "int int -> [int]", '6 2 D -> [1,1,0]'){|a,b| to_base(a.value,b.value) }
mk_op("W W, baseWas baseFrom", "int [int] -> int", '2 1,1,0 W -> 6', promote: false){|a,b| from_base(b.value,a.value) }
mk_op("B rangeFrom rangeBegin", "x -> [x]", "2B -> [2,3,4,..."){|a| range_from(a.value) }
mk_op("T rangeTo", "int -> [int]", "3T -> [0,1,2]"){|a| range(0,a.value) }
mk_op("u unsquare sqrt", "int -> int", "9u -> 3"){|a| square_root(a.value, 2)  }
mk_op("A min", "x x -> x", '4 5 A -> 4'){|a,b| spaceship(a,b,$rank) <= 0 ? a.value : b.value }
mk_op("X max", "x x -> x", '"bc" "ad" X -> "bd"'){|a,b| spaceship(a,b,$rank) >= 0 ? a.value : b.value }
mk_op("H hyper pow10", "int -> int", "2H -> 100"){|a| pow(10, a.value) }
mk_op("P prod", "[int] -> int", '1,2,3,4 P -> 24', promote: false){|a| to_eager_list(a).inject(1,&:*) }

category("debugging ops")
mk_op("nil", "[a]", 'nil -> []'){ [] }
mk_op("pad", "[a] a -> [a]", '1,2 3 pad -> [1,2,3,3,3...'){|a, b| padv(a, b) }
mk_op("show", "a -> [char]", '1,2 show -> "[1,2]"', vectorize: false, &show=->a{ inspectv($at, $rank, a) })
mk_op("type", "a -> [char]", '1,2 type -> "[int]"', vectorize: false){|a| str_to_lazy_list(type_and_rank_to_str($at, $rank)) }
mk_op("version", "[char]", "version -> \"#{Version}, ruby#{RUBY_VERSION}\""){ str_to_lazy_list(Version + ", ruby#{RUBY_VERSION}") }
mk_op("del", "a -> a", '1 2 del -> 1', vectorize: false){|a| raise}

category("internal ops")
# mk_op("lines", "[char] -> [[char]]"){|a| lines(a) }
mk_op("superpad", "[a] -> [a]", promote: false){ raise }
mk_op("zero", "[] -> int"){|a| 0 } # default to int in fold0s
mk_op("zerom", "[a] -> a"){|a| zero($rank,$at) } # this is used for rank1+ fold0s
mk_op("zero", "[a] -> a"){|a| zero($rank,$at) }

# todo do this without duplicating logic/names
category("intuitive special promotes (don't need to show them in quickref)")
mk_op("* join join2", "[char] [[char]] -> [char]", promote: [RepSnd,NotFirst]){|a, b| join(map(a){|i| [i,Null] }.const,b)} # ideally don't promote first at all
mk_op("S SortBy sortBy, s, sortBy2", "[[a]] [b] -> [[a]]", promote: false){|a,b| sort_by(a,b,$rank) }
mk_op("V Vet Filter vet, filter, v, filter2", "[[a]] [b] -> [[a]]", promote: false){|a,b| filter(a,b,$bt,$rank) }
mk_op("W While TakeWhile w, while, takeWhile, takeWhile2", "[[a]] [b] -> [[a]]", promote: false){|a,b| take_while(a,b,$bt,$rank-1) }
mk_op("\\, ChunkWhen chunkWhen, chunkWhen2", "[[a]] [b] -> [[[a]]]", promote: false){|a,b| chunk_while(a,b,$bt,$rank) }
mk_op("Y y, IfElse ifElse, ifElse2", "[a] b b -> b", promote: NotFirst){|a,b,c| truthy($at, $rank+1, a.value) ? b.value : c.value }

Unlines = Way.new("unlines", [2], [1], [CharType], CharType, 0, nil, nil, ->a{
  concat(map(a){|e| append(e, [10.const, Null].const) }.const)
})
Unwords = Way.new("unwords", [2], [1], [CharType], CharType, 0, nil, nil, ->a{
  join(a, repeat([32.const, Null].const).const)
})

# parse.rb #####################################################################

AST = Struct.new(:token, :args)
BlockAST = Struct.new(:token, :args, :block, :block_arg)
ArgNode = Struct.new(:arg_ind)
VarNode = Struct.new(:name)
DataNode = Struct.new(:impl, :str)

RegisterName = "@ register"

class Stack
  def initialize
    @stack = []
    @negative_used = 0
  end
  def push(ind)
    @stack << ind
    self
  end
  def pop
      # return node inds lazily since negative stack size means future values that don't exist yet
    if @stack.empty?
      stack_ind = @negative_used += 1
      ->{@stack[-stack_ind]} # only valid once stack size is finalized
    else
      @stack.pop
    end
  end
  def peek
    r = pop
    push r
    r
  end
  def size
    @stack.size - @negative_used
  end
  def finalize(is_main, arg_ind)
    implicit_args = is_main && @negative_used == 0 && size >= 1 ? 0 : [1-size, 1].max
    implicit_args.times{ @stack << arg_ind }
    [@stack[0,size], implicit_args]
  end
end

def parse_main(tokens)
  registers = get_var_ids_set(tokens)
  tokens = split_ids(tokens, registers)
  add_implicit_ends(tokens, registers)
  nodes = []
  main_inds, _, block_arg_ind, token_ind=*parse(tokens, 0, nodes, true, nil, [], registers, false)
  raise if block_arg_ind != 0 # ir assumes 0 => 0
  raise IogiiError.new "program has an extra >", tokens[token_ind-1] if token_ind<=tokens.size
  nodes.each{|node|
    if AST === node || BlockAST === node
      node.args.map!{|a|a=a.call while Proc===a; a}
    end
    if BlockAST === node
      node.block = node.block.call while Proc === node.block
      node.block_arg = node.block_arg.call while Proc === node.block_arg
    end
    if ArgNode === node
      node.arg_ind = node.arg_ind.call while Proc === node.arg_ind
    end
  }
  main_inds.map!{|a| a=a.call while Proc===a; a}
  registers.each{|k,v| registers[k] = v = v.call while Proc===v }
  [nodes, main_inds, registers]
end

def split_ids(tokens, registers)
  tokens.map{|t|
    if t.type==:id && !lookup_op(t.str) && !registers[t.str]
      warn("splitting %p into chars because no op/var by that name, but it is surrounded by whitespace" % t.str, t) if t.surrounded_by_whitespace
      t.split
    else
      t
    end
  }.flatten
end

def get_var_ids_set(tokens)
  set = {}
  tokens.size.times{|i|
    if tokens[i].str == "~"
      raise IogiiError.new "a token must follow assignment operator", tokens[i] if !tokens[i+1]
      raise IogiiError.new "cannot set ~", tokens[i] if tokens[i+1].str == "~"
      set[tokens[i+1].str] = true
    end
  }
  set
end

class DummyStack
  def pop; 0; end
  def peek; 0; end
  def <<(a); self; end
  def empty?; false; end
end
DUMMY_STACK = DummyStack.new

# todo so inefficient, but obvious alg
def can_brackets_match(begins, ends)
  return true if ends.empty?
  return false if begins.empty?
  s=" "*([begins[-1],ends[-1]].max+1)
  begins.each{|i| s[i]="(" }
  ends.each{|i| raise if s[i] != " "; s[i]=")" }
  s.tr!" ",""
  s["()"] = "" while s["()"]
  return !s[")"]
end

# start at leftmost >
# try each block opener starting at right most, if parse matches, then move on to next >, else mark that opener as needing implicit end, and try previous opener
  # if implicit opener doesn't end before >, or number of openers less than remaining >s then force explicit match (implicit args/ops will be used)
def add_implicit_ends(tokens, registers)
  begins = []
  ends = registers['>'] ? [] : tokens.size.times.filter{|ti| tokens[ti].str == ">" }
  all_begins = tokens.size.times.filter{|ti| t=tokens[ti]
      !registers[t.str] && (o=lookup_op(t.str)) && o.block
    }
  raise IogiiError.new "unmatched >", tokens[ends[-1]] if !can_brackets_match(all_begins, ends)
  prev_end = 0
  ends.each.with_index{|e,ei|
    begins += all_begins.filter{|bi| bi >= prev_end && bi < e }
    begin_token = nil
    loop {
      begin_token = tokens[begins.last]
      block_inds, implicit_args, _, _, could_end_earlier = *parse(tokens, begins.last + 1, [], false, DUMMY_STACK, DUMMY_STACK, registers, false)
      begins.pop
      break if !could_end_earlier
      # this block terminates normally
      break if block_inds.size == 1 && implicit_args == 1
      # todo definitely not efficient to call this over and over
      break if !can_brackets_match(begins + all_begins.filter{|bi| bi >= e }, ends[ei..-1])
    }
    begin_token.explicit_end = true
    prev_end = e
  }
end

def parse(tokens, token_ind, nodes, is_main, outer, var_stack, registers, implicit_end)
  could_end_earlier = false
  stack=Stack.new
  nodes << ArgNode.new(arg_ind = nodes.size)

  loop {
    t=tokens[token_ind]
    token_ind+=1
    break if token_ind > tokens.size
    if registers[t.str]
      stack.push nodes.size
      nodes << VarNode.new(t.str)
      next
    end
    break if t.str == ">"
    if t.str == "!"
      raise IogiiError.new "no outer stack in main", t if !outer
      stack.push outer.pop
    elsif t.str == "$"
      stack.push arg_ind
    elsif t.str == "["
      var_stack << stack.peek
    elsif t.str == "]"
      raise IogiiError.new "var stack is empty, cannot pop", t if var_stack.empty?
      stack.push var_stack.pop
    elsif t.str == "~"
      registers[tokens[token_ind].str] = stack.peek
      token_ind += 1
    elsif t.str == "@"
      if tokens[token_ind..-1].any?{|t|t.str == "@"} # get
        stack.push nodes.size
        nodes << VarNode.new(RegisterName)
      else # set
        registers[RegisterName] = stack.peek
      end
    else case t.type
    when :int, :char, :str
      data_impl, token_ind = *parse_data(tokens, orig_token_ind=token_ind)
      stack.push nodes.size
      str = tokens[orig_token_ind-1...token_ind].map(&:str).join
      nodes << DataNode.new(data_impl, str)
    when :id, :sym
      op = lookup_op(t.str)
      raise IogiiError.new "unknown op", t if !op
      args = op.parse_nargs.times.map{ stack.pop }.reverse
      if op.block
        block_inds, _, block_arg_ind, token_ind, _ =*parse(tokens, token_ind, nodes, false, stack, var_stack, registers, !t.explicit_end)
        raise IogiiError.new "excessive values in block and implicit ops not yet implemented", t if block_inds.size > 1
        raise if block_inds.size < 1
        block_ind = block_inds[0]
        nodes << BlockAST.new(t, args, block_ind, block_arg_ind)
      else
        nodes << AST.new(t, args)
      end
      stack.push nodes.size-1
      stack.push args[0] if op.name == "mdup" || op.name == "dup"
      if "del" == t.str
        stack.pop
      end
    when :commas
      raise IogiiError.new "commas must follow data or op that can vectorize", t
    else raise "unknown token type %p" % t.type.to_s
    end end
    if stack.size == 0
      break if implicit_end
      could_end_earlier = true
    end
  }
  [*stack.finalize(is_main, arg_ind), arg_ind, token_ind, could_end_earlier]
end

def parse_data(tokens, token_ind)
  prev_is_datum = false
  tokens = tokens[token_ind-1..-1].take_while{|t|
    if [:int,:char,:str].include?(t.type)
      if prev_is_datum
        false
      else
        prev_is_datum = true
        true
      end
    else
      prev_is_datum = false
      t.type == :commas
    end
  }
  token_ind = token_ind + tokens.size - 1

  depth = tokens.select{|t|t.type == :commas}.map{|t|t.str.size}.max||0
  token_type = nil
  tokens.reject{|t|t.type == :commas}.each{|t|
    token_type ||= t.type
    token_type = :str if token_type != t.type
  }

  value = parse_list(tokens,depth,token_type)
  case token_type
  when :str
    type = CharType
    depth += 1
  when :char
    type = CharType
  when :int
    type = IntType
  else; raise
  end
  [new_op("data", type_and_rank_to_str(type,depth)){ value }, token_ind]
end

def parse_list(tokens,depth,to_type)
  if depth == 0
    raise IogiiError.new 'found ", ," invalid data format' if tokens.empty?
    parse_datum(tokens[0],to_type)
  else
    to_lazy_list(split_tokens(tokens, ","*depth).map{|ts|
      parse_list(ts, depth-1,to_type)
    })
  end
end

def parse_datum(token,to_type)
  case token.type
  when :int
    to_type == :str ? str(token.str.to_i.const) : token.str.to_i
  when :str
    str_to_lazy_list(parse_str(token.str[1..(token.str[-1] != '"' || token.str.size==1 ? -1 : -2)]))
  when :char
    raise IogiiError.new "empty char", token if token.str.size < 2
    x = parse_char(token.str[1..-1]).ord
    to_type == :str ? [x.const, Null] : x
  end
end

# ir.rb ########################################################################
IR = Struct.new(:op, :args, :token)

def to_ir(ast_nodes, arg, registers, override_input, ast_inds)
  nodes = [IR.new(arg, [], nil)]
  ast2ir = { 0 => override_input ? ->{ast2ir[registers[RegisterName]]} : 0 }
  ast_nodes.size.times{|ast_ind| to_ir_h(ast_nodes, ast_ind, nodes, ast2ir, registers) }
  replace_vars = lambda{|a| a=a[] while Proc === a; a }
  nodes.each{|n|n.args.map! &replace_vars }
  ast2ir.each{|k,v| ast2ir[k] = replace_vars[v] }
  ir_inds = ast_inds.map{|i| ast2ir[i] }
  [nodes, ir_inds]
end

def to_ir_h(ast_nodes, ast_ind, nodes, ast2ir, registers)
  return ast2ir[ast_ind] if ast2ir[ast_ind]
  ir_ind = nil
  ast2ir[ast_ind] = ->{ir_ind}
  ast_node = ast_nodes[ast_ind]
  ir_ind = case ast_node
    when ArgNode
      ->{ ast2ir[ast_node.arg_ind] }
    when VarNode
      ->{ ast2ir[registers[ast_node.name]] }
    when DataNode
      nodes << IR.new(ast_node.impl, [], nil)
      nodes.size - 1
    when AST
      args = ast_node.args.map{|a|to_ir_h(ast_nodes, a, nodes, ast2ir, registers)}
      op = lookup_op(ast_node.token.str)

      if op.name == "dup"
        args[0]
      else
        nodes << new_node = IR.new(op, args, ast_node.token)
        nodes.size - 1
      end
    when BlockAST
      args = ast_node.args.map{|a|to_ir_h(ast_nodes, a, nodes, ast2ir, registers)}
      op = lookup_op(ast_node.token.str)
      mod = op.ways[0].mod

      if op.name == "mdup"
        ast2ir[ast_node.block_arg] = args[0]
        to_ir_h(ast_nodes, ast_node.block, nodes, ast2ir, registers)
      elsif op.name == "iterate"
        nodes << IR.new(Ops["cons"].dup_mod(mod), cons_args=[nil, args[0]], ast_node.token)
        consed_ind = nodes.size - 1
        nodes << IR.new(Ops["superpad"].dup_mod(mod), [nodes.size-1], ast_node.token)
        ast2ir[ast_node.block_arg] = nodes.size - 1
        cons_args[0] = to_ir_h(ast_nodes, ast_node.block, nodes, ast2ir, registers)
        consed_ind
      elsif op.name == "iterate0"
        nodes << IR.new(mod > 0 ? Ops["zerom"].dup_mod(mod) : Ops["zero"], zero_args=[nil], ast_node.token)
        nodes << IR.new(Ops["cons"].dup_mod(mod), cons_args=[nil, nodes.size-1], ast_node.token)
        nodes << IR.new(Ops["superpad"].dup_mod(mod), [nodes.size-1], ast_node.token)
        ast2ir[ast_node.block_arg] = nodes.size - 1
        cons_args[0] = zero_args[0] = to_ir_h(ast_nodes, ast_node.block, nodes, ast2ir, registers)
        cons_args[0]
      elsif op.name == "foldr"
        nodes << IR.new(Ops["superpad"].dup_mod(mod), superpad_args=[nil], ast_node.token)
        nodes << IR.new(Ops["pad"].dup_mod(mod), [nodes.size-1, args[0]], ast_node.token)
        nodes << IR.new(Ops["tail"].dup_mod(mod), [nodes.size-1], ast_node.token)
        ast2ir[ast_node.block_arg] = nodes.size - 1
        superpad_args[0] = block_result = to_ir_h(ast_nodes, ast_node.block, nodes, ast2ir, registers)
        nodes << IR.new(Ops["head"].dup_mod(mod), [block_result], ast_node.token)
        nodes.size - 1
      elsif op.name == "foldr0"
        nodes << IR.new(Ops["superpad"].dup_mod(mod), superpad_args=[nil], ast_node.token)
        nodes << IR.new(mod > 0 ? Ops["zerom"].dup_mod(mod) : Ops["zero"], [nodes.size-1], ast_node.token)
        nodes << IR.new(Ops["pad"].dup_mod(mod), [nodes.size-2, nodes.size-1], ast_node.token)
        nodes << IR.new(Ops["tail"].dup_mod(mod), [nodes.size-1], ast_node.token)
        ast2ir[ast_node.block_arg] = nodes.size - 1
        superpad_args[0] = block_result = to_ir_h(ast_nodes, ast_node.block, nodes, ast2ir, registers)
        nodes << IR.new(Ops["head"].dup_mod(mod), [block_result], ast_node.token)
        nodes.size - 1
      else raise
      end
    else raise "unhandled node type %p" % ast_node.class
  end
  ir_ind
end

# ir2.rb #######################################################################
# todo some of this logic is duplicated with infer - unify it

IR2 = Struct.new(:way, :args, :zip_level, :type, :rank)

# IR2 is a simpler graph representation of the program
def to_ir2(ir, ranks, types, ir1_inds)
  ir2ir2 = {}
  ir2_nodes = []
  ir.each.with_index{|node,i|
    arg_types = node.args.map{|a|types[a]}
    arg_ranks = node.args.map{|a|ranks[a]}
    way = find_valid_way(ir[i], arg_types, arg_ranks)

    coerces = coerce?(way.arg_types,arg_types)
    zip_level = zip_level(way, arg_ranks, arg_types)
    excess = excess_ranks(way, arg_ranks, arg_types)

    args = node.args.map{|a| ->{ ir2ir2[a] } }

    # implicit coerces
    args = args.zip(coerces, arg_ranks,0..).map{|a,c,r,i|
      if c
        arg_types[i] = CharType
        arg_ranks[i] += 1
        ir2_nodes << IR2.new(Ops["str"].ways[0], [a], r, CharType, r+1)
        ir2_nodes.size - 1
      else
        a
      end
    }

    # implicit promotes
    special_rep = way.promote.include?(RepSnd) && excess[1] < 0
    args = args.zip(excess,0..,arg_ranks, arg_types).map{|a,e,ind,r,t|
      npromotes = -e - (special_rep && ind==1 ? 1 : 0)
      arg_ranks[ind] += [npromotes, 0].max
      npromotes.times{|time|
        ir2_nodes << IR2.new(Ops["just"].ways[0], [a], 0, t, r+time+1)
        a = ir2_nodes.size - 1
      }
      a
    }

    # implicit repeats
    args = args.zip(excess,0..,arg_ranks, arg_types).map{|a,e,ind,r,t|
      nreps = zip_level - [e, 0].max + (special_rep && ind==1 ? 1 : 0)
      arg_ranks[ind] += [nreps, 0].max
      nreps.times{|time|
        ir2_nodes << IR2.new(Ops["repeat"].ways[0], [a], 0, t, r+time+1)
        a = ir2_nodes.size - 1
      }
      a
    }

    # superpad
    if way.name == "superpad"
      a = args[0]
      zip_level.times{|time|
        ir2_nodes << IR2.new(Ops["zeroPad"].dup_mod(arg_ranks[0]-time-1).ways[0], [a], time, arg_types[0], arg_ranks[0])
        a = ir2_nodes.size - 1
      }
      ir2ir2[i] = a
    else
      # create node
      ir2_nodes << IR2.new(way, args, zip_level, types[i], ranks[i])
      ir2ir2[i] = ir2_nodes.size - 1
    end
  }
  ir2_nodes.each{|node| node.args.map!{|a| a=a.call while Proc===a; a } }
  print_inds = ir1_inds.map{|i| ir2ir2[i] }
  print_inds.map!{|a| a=a.call while Proc===a; a }

  [ir2_nodes, print_inds]
end

# generate implicit nodes that handle converting all outputs to a single string
def to_ir3(ir2, ir2_inds, pp_sep)
  ir3 = ir2.dup
  ir3 << IR2.new(Ops["nil"].ways[0], [], 0, CharType, 2)
  last_cons = ir3.size - 1
  if pp_sep
    ir3 << IR2.new(new_op("data", "[char]"){ str_to_lazy_list(pp_sep) }.ways[0], [], 0, CharType, 1)
    sep_ind = ir3.size - 1
  end

  ir2_inds.reverse_each{|i|
    if pp_sep
      ir3 << IR2.new(Ops["show"].ways[0], [i], 0, CharType, 1)
      ir3 << IR2.new(Ops["append"].ways[0], [ ir3.size - 1, sep_ind], 0, CharType, 1)
      i = ir3.size - 1
    else
      node = ir2[i]
      if node.type == IntType
        ir3 << node = IR2.new(Ops["str"].ways[0], [i], node.rank, CharType, node.rank+1)
        i = ir3.size-1
      end
      if node.rank == 0
        ir3 << node = IR2.new(Ops["just"].ways[0], [i], 0, CharType, 1); i = ir3.size-1
      elsif node.rank > 2
        ir3 << node = IR2.new(Unwords, [i], node.rank-2, CharType, node.rank-1); i = ir3.size-1
      end
      while node.rank > 1
        ir3 << node = IR2.new(Unlines, [i], node.rank-2, CharType, node.rank-1); i = ir3.size-1
      end
    end
    ir3 << IR2.new(Ops["cons"].ways[0], [last_cons, i], 0, CharType, 2)
    last_cons = ir3.size-1
  }
  ir3 << IR2.new(Ops["concat"].ways[0], [last_cons], 0, CharType, 1)
  [ir3, ir3.size-1]
end

# infer.rb #####################################################################
def solve_types(ir)
  queue = ir.size.times.to_a

  deps = ir.map{[]}
  queue.each{|i|ir[i].args.each{|a|deps[a]<<i}}
  deps.map!(&:uniq)

  types = ir.map{EmptyType}

  queue.each{|i|
    op = ir[i].op
    arg_types = ir[i].args.map{|a|types[a]}
    begin
      way = find_way(ir[i], op, arg_types)
    rescue IogiiError # no match yet
      next
    end
    result_type = spec_to_ret(way, ir[i].args.map{|a|types[a]})
    # todo handle case where it is < (char char -)
    if result_type > types[i]
      types[i] = result_type
      queue.concat(deps[i])
    elsif result_type < types[i]
      raise IogiiError.new "type invariant has been violated", ir[i] #todo see nitty gritty, test it
    end
  }
  types
end

def find_way(node, op, arg_types, arg_ranks = [0]*arg_types.size)
  x = op.ways.map{|way|
    excess = excess_ranks(way, arg_ranks, arg_types)

    # Prefer to promote 1 arg rather than use vectorized low rank overload
    # todo: don't do this here if there become more specific cases
    excess = [0,0] if ["Append","SetDiff"].any?{|n|Ops[n].ways[0] == way} && excess.sort == [-1,0]

    [ spec_matches(way.arg_types, arg_types).sort.reverse, # most weight on no_matches
      excess.map{|e| [-e, 0].max }.sort.reverse, # only care about when rank too low
      excess.map{|e| [e, 0].max }.sort.reverse, # but still prefer no excessive rank (for special promotes)
      way.arg_types.sort, # for ops that have special case for empty (zero)
      way.mod_arg_ranks.sort # finally look at spec ranks since empty type never has excess
    ]
  }
#   p x if op.name == "zero"
  xmin_at = (0...x.size).min_by{|i| x[i] }
  xmin = x[xmin_at]
  raise(IogiiError.new "op is not defined for base types: " + arg_types.map{|t|type_to_str(t)}*" ", node) if xmin[0][0] == :m2_no_match
  effects = (0...x.size).filter{|i| x[i] == xmin }.map{|i|
    way = op.ways[i]
    [way.impl, way.ret_type, way.ret_rank] }
  raise "internal error, ambiguous op %p: %p" % [node.token.str, op.name] if effects.uniq.size > 1
  op.ways[xmin_at]
end

def find_valid_way(node, arg_types, arg_ranks)
  way = find_way(node, node.op, arg_types, arg_ranks)

  excess = excess_ranks(way, arg_ranks, arg_types)
  excess.each.with_index{|e,i|
    raise IogiiError.new "arg #{i+1} rank too low and op does not promote (see #{NittyGritty}#promotion )", node if e < 0 && (way.promote.include?(false) || way.promote.include?(NotFirst) && i==0) }

  if !way.can_mod? && node.token && node.token.str[-1] == ","
    begin
      simpler_op = lookup_op(node.token.str[0...-1])
      simpler_way = find_way(node, simpler_op, arg_types, arg_ranks) if simpler_op
    rescue IogiiError
    end
    raise IogiiError.new "extra , used", node if simpler_way == way
  end

  way
end

def solve_ranks(ir,types)
  queue = ir.size.times.to_a

  deps = ir.map{[]}
  queue.each{|i|ir[i].args.each{|a|deps[a]<<i}}
  deps.map!(&:uniq)

  ranks = ir.map{0}

  queue.each{|i|
    op = ir[i].op
    arg_types = ir[i].args.map{|a|types[a]}
    arg_ranks = ir[i].args.map{|a|ranks[a]}
    way = find_way(ir[i], op, arg_types, arg_ranks)
    #technically result rank is wrong if !.vectorize, and generic type but none of those return a generic type
    result_rank = zip_level(way, arg_ranks, arg_types) + way.mod_ret_rank
    if result_rank > 100 # todo distinguish between this and legitimate cases (which for all practical purposes will never exist)
      raise IogiiError.new "cannot construct the infinite type", ir[i]
    elsif result_rank > ranks[i]
      ranks[i] = result_rank
      queue.concat(deps[i])
    elsif result_rank < ranks[i]
      raise IogiiError.new "rank invariant has been violated", ir[i] #todo see nitty gritty, test it
    end
  }
  ranks
end

def excess_ranks(way, ranks, types)
  ranks.zip(types, way.mod_arg_ranks, coerce?(way.arg_types,types)).map{|r, t, o, c|
    e = r-o+(c ? 1 : 0)
    e = [0, e].min if !way.vectorize
    e = [0, e].max if t == EmptyType
    e
  }
end

def zip_level(way, ranks, types)
  (excess_ranks(way, ranks, types)<<0).max
end

# lazylib.rb ###################################################################
require "set"

def make_promises(ir2)
  promises = ir2.map{|node|
    arg_types = node.args.map{|a|ir2[a].type}

    impl = lambda{|*args|
      $rank = node.way.vectorize ? node.way.mod : ir2[node.args[0]].rank
      $at = arg_types[0]
      $bt = arg_types[1]
      node.way.impl[*args]
    }

    promise{
      args = node.args.map{|a| promises[a] }
      zipn(node.zip_level, args, impl)
    }
  }
  promises
end

class Promise
  def initialize(&block)
    @impl=block
  end
  def value
    @impl=@impl[] if Proc===@impl
    @impl
  end
  def empty
    value.empty?
  end
end

def promise(&b)
  Promise.new(&b)
end
class Object
  def const
    Promise.new{self}
  end
end

def zipn(n, a, f)
  return f[*a] if n <= 0 || a==[]
  return [] if a.any?{|i|i.empty}
  [promise{zipn(n-1, a.map{|i|i.value[0]}, f) },
   promise{zipn(n, a.map{|i|i.value[1]}, f) }]
end

def output(str)
   print(str)
end

def error_output(str)
   $stderr.write(str)
end

def repeat(a)
  (ret = [a]) << ret.const
end

def concat_map(v,rhs=Null,first=true,&b)
  if v.empty
    rhs.value
  else
    b[v.value[0],Promise.new{concat_map(v.value[1],rhs,false,&b)},first]
  end
end

def append(v,r)
  v.empty ? r.value : [v.value[0],promise{append(v.value[1], r)}]
end

def join(a,b)
  return [] if a.empty
  append(a.value[0],promise{
    if b.empty || a.value[1].empty
      []
    else
      append(b.value[0], promise{ join(a.value[1], b.value[1]) })
    end
  })
end

def starts_with(a,b) # assumed to be strs, returns remainder if match otherwise nil
  return a if b.empty
  return nil if a.empty || a.value[0].value != b.value[0].value
  starts_with(a.value[1],b.value[1])
end

def cut(a,b) # fyi could be lazier since we know we return a list always
  return [a,Null] if a.empty || b.empty
  if (remainder=starts_with(a,b.value[0]))
    [Null,Promise.new{cut(remainder,b.value[1])}]
  else
    rhs=Promise.new{cut(a.value[1],b)}
    [promise{[a.value[0],Promise.new{rhs.value[0].value}]},promise{rhs.value[1].value}]
  end
end

def last(a, prev)
  until a.empty
    prev = a.value[0]
    a = a.value[1]
  end
  prev.value
end

FalseChars = {0=>1,9=>1,10=>1,11=>1,12=>1,13=>1,32=>1}
def truthy(type, rank, value)
  return !value.empty? if rank > 0
  case type
    when IntType
      return value != 0
    when CharType
      !FalseChars.include?(value)
    else raise
  end
end

def reverse(a,sofar=[])
  return sofar if a.empty
  reverse(a.value[1],[a.value[0],sofar.const])
end

def superpad(n, a)
  if n > 0
    pad(promise { map(a){|e| superpad(n-1, e) } } )
  else
    a.value
  end
end

def pad(a)
  [
    promise{ a.empty ? raise(IogiiError.new("pad value used (see #{NittyGritty}#superpad )")) : a.value[0].value },
    promise{ pad( promise{ a.empty ? [] : a.value[1].value }) }
  ]
end

def padv(a,b)
  [
    promise{ a.empty ? b.value : a.value[0].value },
    promise{ padv( promise{ a.empty ? [] : a.value[1].value }, b) }
  ]
end

def pow(a,b)
  # manually implement this op so that it is consistent and doesn't return rationals / overflow
  return 0 if b < 0 && a == 0
  neg_exponent = b < 0
  b = b.abs

  y = 1
  while b >= 1
    y *= a if b%2 == 1
    a *= a
    b /= 2
  end
  neg_exponent ? 1 / y : y
end

def prints(value)
  while !value.empty?
    v = value[0].value
    # raw print, multiple bytes will show up as one if utf is used. output will match source code so will look correct if editor encoding=terminal encoding
    output((v % 256).chr)
    value = value[1].value
  end
end

Null = [].const

def str_to_lazy_list(s,rhs=Null)
  to_lazy_list(s.chars.map(&:ord), rhs)
end

def to_lazy_list(l, rhs=Null, ind=0)
  ind >= l.size ? rhs.value : [l[ind].const, Promise.new{to_lazy_list(l, rhs, ind+1)}]
end

def to_eager_str(v)
  to_eager_list(v.const).map{|i|(i.to_i%256).chr}.join
end

def to_eager_list(a)
  sofar=[]
  until a.empty
    sofar << a.value[0].value
    a = a.value[1]
  end
  sofar
end

def to_strict(a)
  return a.value unless Array===a.value
  sofar=[]
  until a.empty
    sofar << to_strict(a.value[0])
    a = a.value[1]
  end
  sofar
end

def str(i)
  to_lazy_list(i.value.to_s.bytes.to_a)
end

def is_digit(i)
  i>=48 && i<58
end

def split_non_digits(s)
  return [] if s.empty
  v,found,s2=read_num(s)
  return [] if !found
  [v.const,Promise.new{split_non_digits(s2)}]
end

def read_num(s)
  multiplier=1
  until s.empty || is_digit(s.value[0].value)
    if s.value[0].value == ?-.ord
      multiplier *= -1
    else
      multiplier = 1
    end
    s = s.value[1]
  end
  v = 0
  found_int = false
  until s.empty || !is_digit(s.value[0].value)
    found_int = true
    v = v*10+s.value[0].value-48
    s = s.value[1]
  end

  [multiplier * v, found_int, s]
end

def lines(s)
  return [] if s.empty

  after = Promise.new{lines(s.value[1])}
  if s.value[0].value == 10
    [Null, after]
  else
    [Promise.new{
      after.empty ? [s.value[0], Null] : [s.value[0], after.value[0]]
     },
     Promise.new{
      after.empty ? [] : after.value[1].value
     }]
   end
end

def take(n, a)
  return [] if n < 1 || a.empty
  [a.value[0], Promise.new{ take(n-1, a.value[1]) }]
end

def drop(n, a)
  while n>=1 && !a.empty
    n-=1
    a=a.value[1]
  end
  a.value
end

def index(a, b, rank)
  i=1
  until a.empty
    return i if spaceship(a.value[0],b,rank) == 0
    i += 1
    a = a.value[1]
  end
  return 0
end

def len(a)
  return 0 if a.empty?
  return 1+len(a[1].value)
end

def sort_by(a,b,rank)
  fromby(sort(toby(a,b),rank,true))
end

def toby(a,b)
  return Null if a.empty || b.empty
  Promise.new{ [[a.value[0], b.value[0]], toby(a.value[1], b.value[1])] }
end

def fromby(a)
  return [] if a == []
  [Promise.new{a[0][0].value}, Promise.new{fromby(a[1].value)}]
end

# It would be very interesting and useful to design a more lazy sorting algorithm
# so that you can select ith element in O(n) total time after sorting a list.
def sort(a,rank,by=false)
  return [] if a.empty
  return a.value if a.value[1].empty
  n=len(a.value)
  left=take(n/2, a).const
  right=drop(n/2, a).const
  merge(sort(left,rank,by),sort(right,rank,by),rank,by)
end

def merge(a,b,rank,by)
  return b if a==[]
  return a if b==[]
  if (by ? spaceship(a[0][1], b[0][1], rank) : spaceship(a[0], b[0],rank)) <= 0
    [a[0], Promise.new{merge(a[1].value,b,rank,by)}]
  else
    [b[0], Promise.new{merge(a,b[1].value,rank,by)}]
  end
end

def spaceship(a,b,rank)
  if rank>0
    until a.empty && b.empty
      return -1 if a.empty
      return 1 if b.empty
      s0 = spaceship(a.value[0],b.value[0],rank-1)
      return s0 if s0 != 0
      a = a.value[1]
      b = b.value[1]
    end
    return 0
  else
    a.value<=>b.value
  end
end

def inspectv(t,rank,value,rhs=Null)
  if t == CharType && rank == 1
    ['"'.ord.const, Promise.new{
      concat_map(value,Promise.new{str_to_lazy_list('"',rhs)}){|v,r,first|
       str_to_lazy_list(escape_str_char(v.value),r)
      }
    }]
  elsif rank > 0 #List
    ["[".ord.const, Promise.new{
      concat_map(value,Promise.new{str_to_lazy_list("]",rhs)}){|v,r,first|
        first ?
          inspectv(t,rank-1,v,r) :
          [','.ord.const,Promise.new{inspectv(t,rank-1,v,r)}]
      }
    }]
  elsif t == EmptyType
    value.value # take the value so it can throw
    raise
  elsif t == IntType
      str_to_lazy_list(value.value.to_s,rhs)
  elsif t == CharType
    str_to_lazy_list(inspect_char(value.value),rhs)
  else
    raise
  end
end

def filter(a,b,bt,rank)
  return [] if a.empty || b.empty
  if truthy(bt,rank,b.value[0].value)
   [a.value[0],Promise.new{ filter(a.value[1],b.value[1],bt,rank) }]
  else
    filter(a.value[1],b.value[1],bt,rank)
  end
end

def take_while(a,b,bt,rank)
  return [] if a.empty || b.empty
  if truthy(bt,rank,b.value[0].value)
   [a.value[0],Promise.new{ take_while(a.value[1],b.value[1],bt,rank) }]
  else
    []
  end
end

def drop_until(a,b,bt,rank)
  until a.empty || b.empty
    if truthy(bt,rank,b.value[0].value)
      return a.value
    else
      a = a.value[1]
      b = b.value[1]
    end
  end
  []
end

def chunk_while(a,b,btype,rank)
  return [Null, Null] if a.empty
  rest = promise { b.empty ? [Null, Null] : chunk_while(a.value[1], b.value[1],btype,rank) }
  truthy = promise { b.empty || truthy(btype,rank,b.value[0].value) }
  [promise{ [a.value[0], promise{ truthy.value ? rest.value[0].value : []}] },
   promise{ truthy.value ? rest.value[1].value : rest.value }]
end

def strip(a,at,rank)
  lstrip = drop_until(a,a,at,rank)
  b = reverse(lstrip.const).const
  reverse(drop_until(b,b,at,rank).const)
end

# these aren't as lazy as possible, but it gets to use hashes
def isuniq(a,h=Hash.new(-1))
  return [] if a.empty
  [(h[to_strict(a.value[0])]+=1) == 0 ? 1.const : 0.const, Promise.new{isuniq(a.value[1], h)}]
end

def set_diff(a,b)
  counts = Hash.new(0)
  to_strict(b).each{|be| counts[be] += 1 }
  diff_helper(a, counts)
end

def diff_helper(a, counts)
  while !a.empty && counts[ae=to_strict(a.value[0])] > 0
    a = a.value[1]
    counts[ae] -= 1
  end
  return [] if a.empty
  [a.value[0], Promise.new{ diff_helper(a.value[1], counts) }]
end

def range(a,b)
  return [] if a>=b
  [a.const, Promise.new{range(a+1,b)}]
end

def range_from(a)
  [a.const, Promise.new{range_from(a+1)}]
end

def group_while(a,b,rank,btype)
  return [Null,Null] if a.empty || b.empty
  b0true = Promise.new{ truthy(btype,rank,b.value[0].value)}
  rhs = Promise.new{ group_while(a.value[1],b.value[1],rank,btype) }
  [
    Promise.new{
      if b0true.value
        [a.value[0], rhs.value[0]]
      else
        []
      end
    },
    Promise.new{
      if b0true.value
        rhs.value[1].value
      else
        rhs.value
      end
    }
  ]
end

def reshape(a, b)
  return [] if b.empty
  n = b.value[0].value
  return [] if n>0 && a.empty
  return [Null, promise{ reshape(a, b.value[1]) }] if n <= 0
  [promise{ take(n, a) },
   promise{
    d = drop(n-1, a)
    # took more than all, next is []
    d == [] ? [] : reshape(d[1], b.value[1])
   }
  ]
end

def map(a,&b)
  a.empty ? [] : [Promise.new{b[a.value[0]]}, Promise.new{map(a.value[1],&b)}]
end

def zero(rank,type=nil)
  return [] if rank > 0
  raise IogiiError.new("use of default value for empty type") if type == EmptyType
  type == CharType ? 32 : 0
end

def concat(a)
  concat_map(a){|i,r,first|append(i,r)}
end

def rstrip(a)
  return [] if a.empty
  rest = promise{ rstrip(a.value[1]) }
  return [] if a.value[0].empty && rest.empty
  [a.value[0], rest]
end

def inf_tails(a)
  ans=[a, promise{ map(ans.const){|e| map(e){|e2| e2.empty ? [] : e2.value[1].value }} }]
end

def transpose(a,at,rank)
  z = promise{ map(promise{ inf_tails(a) }){|e| rstrip(e) } }
  t = promise{ map(z){|e| map(e){|e2| e2.empty ? zero(rank,at) : e2.value[0].value } } }
  take_while(t,t,at,rank+1)
end

def square_root(x,n)
  raise IogiiError.new "negative square root" if x < 0
  return 0 if x==0
  guess = x
  while (r=(x/guess+guess)/n) < guess
    guess = r
  end
  guess
end

CharClasses = [
  [*'a'..'z'],
  [*'A'..'Z'],
  [*'0'..'9'],
  [*' '..126.chr] - ([*'a'..'z'] + [*'A'..'Z'] + [*'0'..'9'] + [' ',"\n"]),
  [' ',"\n"]
].map{|chars| chars.map(&:ord).to_set }
def char_class(a,b)
  CharClasses.each.with_index.any?{|clazz, i| b[i] == 1 && clazz.include?(a) } ? 1 : 0
end

def to_base(a,b)
  raise IogiiError.new "base 0", nil if b==0
  reverse(to_base_h(a.abs,b,a<=>0).const)
end

def to_base_h(a,b,sign)
  return [] if a==0
  digit = a%b*sign
  [digit.const, Promise.new{to_base_h((a-digit*sign)/b,b,sign)}]
end

def from_base(a,b)
  x=0
  until a.empty?
    x=x*b+a[0].value
    a=a[1].value
  end
  x
end

def undigits(a)
  from_base(concat(map(a){|i|
    x = to_base(i.value,10)
    x.empty? ? [0.const, Null] : x
  }.const), 10)
end

$ReadStdinLines = Promise.new{
  if $raw_args
    to_lazy_list($raw_args.map{|a| str_to_lazy_list(a) })
  else
    lines(Promise.new{ read_stdin })
  end
}
def read_stdin
  c=STDIN.getc
  if c.nil?
    []
  else
    [c.ord.const, Promise.new{ read_stdin }]
  end
end

# auto_parse.rb ################################################################

SepRx = /, | |,/
NumRx = /-?\d+/

def parse_input
  lazy_input = $ReadStdinLines
  eager_input = to_eager_list(lazy_input)
  # is it just numbers?
  if eager_input.all?{|line| to_eager_str(line) =~ /^(#{NumRx}#{SepRx})*#{NumRx}?$/ } &&
     eager_input.any?{|line| to_eager_str(line) =~ NumRx }
    read = map(lazy_input){|v|split_non_digits(v)}
    eager = to_eager_list(read.const).map{|a| to_eager_list(a.const) }

    if eager.size == 1 && eager[0].size == 1 # is it 1 number
      [eager[0][0], IntType, 0]
    elsif eager.size == 1 # is it a row
      [read[0].value, IntType, 1]
    elsif eager.all?{|a|a.size == 1} # is it a col
      [map(read.const){|v|v.value[0].value}, IntType, 1]
    else # its a matrix
      [read, IntType, 2]
    end
  else # not numbers
    if eager_input.size > 1 # multi line
      [lazy_input.value, CharType, 2]
    else
      [lazy_input.empty ? [] : lazy_input.value[0].value, CharType, 1, lazy_input.empty]
    end
  end
end

# hs.rb ########################################################################
Header = %q(import System.IO
import Data.List
import Data.Char
import Data.String
import Data.Maybe
import System.Info
import qualified Data.Set as Set
import System.Environment

myChr :: Integer -> Char
myChr i = chr $ fromIntegral i `mod` 256
myOrd :: Char -> Integer
myOrd c = fromIntegral $ ord c
bool2i :: Bool -> Integer
bool2i b = if b then 1 else 0
falseChars = Set.fromList [0,9,10,11,12,13,32]
readAll :: String -> [Integer]
readAll xs =
  if num == [] then []
  else sign * (read num :: Integer) : readAll afterNum
  where
    (beforeNum,atNum) = span (not . isDigit) xs
    (num,afterNum) = span isDigit atNum
    negatives = takeWhile (== '-') (reverse beforeNum)
    sign = (-1) ^ length negatives

class IogiiType a where
  truthyChar :: a -> Bool
  truthyInt :: a -> Bool
  zeroChar :: a -> a
  zeroInt :: a -> a

instance IogiiType Integer where
  truthyChar = not . (flip Set.member falseChars)
  truthyInt = (/= 0)
  zeroChar a = 32
  zeroInt a = 0

instance IogiiType a => IogiiType [a] where
  truthyChar = (not . null)
  truthyInt = truthyChar
  zeroChar a = []
  zeroInt = zeroChar

truthyEmpty :: [a] -> Bool
truthyEmpty = (not . null)
zeroEmpty :: [a] -> [a]
zeroEmpty a = []

iogUnwords :: [[Integer]] -> [Integer]
iogUnwords = intercalate [32]
iogUnlines :: [[Integer]] -> [Integer]
iogUnlines = concatMap (++[10])

-- int ops
iogAdd = (+)
iogSub = (-)
iogMult = (*)
iogDiv a b = if b == 0 then 0 else a `div` b
iogMod a b = if b == 0 then 0 else a `mod` b
iogPow a b
  | b < 0 && a == 0 = 0
  | b < 0 = 1 `div` pow
  | otherwise = pow
  where pow = a^(abs b)
iogNegate x = -x
iogAbs = abs
iogCountTo x = [1..x]
iogWholes = [0..]
iogSum = sum :: [Integer] -> Integer
iogSucc = (+1)
iogPred x = x-1

-- char ops
iogCharRange a b = [a..b]
iogRead = iogHeadZeroA zeroInt . iogReadAll
iogReadAll x = readAll (map myChr x)
iogOrd = id
iogChr = id
iogStr = (map myOrd) . show :: Integer -> [Integer]
iogStrip = f . f where f = reverse . dropWhile (not . truthyChar) :: [Integer] -> [Integer]
iogReplicate a b = concat $ genericReplicate b a
iogJoin a b = if null a then [] else head a ++ concat (zipWith (++) b (tail a))
iogSplit a b = filter (not . null) $ iogSplitKeepEmpties a (repeat b)
iogSplitKeepEmpties a b
  | null a || null b = [a]
  | otherwise = maybe
    ((head a : head rhs) : tail rhs)
    (\remainder -> [] : iogSplitKeepEmpties remainder (tail b))
    (stripPrefix (head b) a)
  where
    rhs = iogSplitKeepEmpties (tail a) b
charClasses = map (Set.fromList) [
    ['a'..'z'],
    ['A'..'Z'],
    ['0'..'9'],
    [' '..chr 126] \\\\ (['a'..'z']++['A'..'Z']++['0'..'9']++" \n"),
    " \n"
  ]
iogCharClass a b = bool2i $ any (Set.member (myChr a)) $ (iogFilterTruthyB (/=0) charClasses (iogToBase b 2))

-- generic ops
iogTake = flip genericTake
iogDrop = flip genericDrop
iogJust = (:[])
iogCons = flip (:)
iogNil = []
iogAppend = (++)
iogReverse = reverse
iogNotTruthyA truthyFn x = 1 - bool2i (truthyFn x)
iogRepeat = repeat
iogTakeWhileTruthyB truthyFn a b = iogTakeWhileHelper a (map truthyFn b) where
  iogTakeWhileHelper [] _ = []
  iogTakeWhileHelper _ [] = []
  iogTakeWhileHelper (a:as) (b:bs)
      | b = a : iogTakeWhileHelper as bs
      | otherwise = []
iogTakeWhile2TruthyB = iogTakeWhileTruthyB
iogGetZeroA zeroFn a b = if null rest then zeroFn (head a) else head rest
  where rest = genericDrop b a
iogHeadZeroA zeroFn a = if null a then zeroFn (head a) else head a
iogTail a = if null a then [] else tail a
iogLastZeroA zeroFn a = if null a then zeroFn (head a) else last a
iogOrTruthyA truthyFn a b = if truthyFn a then a else b
iogOr2 a b = if a /= 0 then iogStr(a) else b

iogTransposeZeroA zeroFn a = takeWhile (not . null)
  $ map (map $ iogHeadZeroA zeroFn)
  $ map rstrip
  $ iterate (map iogTail) a
  where
    rstrip [] = []
    rstrip (h:t)
      | null h && null rest = []
      | otherwise = h : rest
      where rest = rstrip t

iogLen = genericLength
iogSortBy a b = map fst $ sortOn snd (zip a b)
iogSortBy2 :: Ord b => [a] -> [b] -> [a] -- why is this needed? todo
iogSortBy2 = iogSortBy
iogConcat :: [[a]] -> [a]
iogConcat = concat
iogFilterTruthyB truthyFn = (catMaybes .) . zipWith (\ai bi->if truthyFn bi then Just ai else Nothing )
iogSetDiff :: Eq a => [a] -> [a] -> [a]
iogSetDiff a b = a \\\\ b
iogIfElseTruthyA truthyFn a b c = if truthyFn a then b else c
iogIfElse2TruthyA = iogIfElseTruthyA
iogZeroPadZeroA zeroFn a = iogPad a (zeroFn $ head a)
iogEqual a b = bool2i (a == b)
iogLessThan a b = bool2i (a < b)
iogIndex a b = toInteger $ maybe 0 (+1) (elemIndex b a)

iogReshape a b
  | null b = []
  | n>0 && null a = []
  | n <= 0 = [] : (iogReshape a $ tail b)
  | otherwise = genericTake n a : if null d then [] else iogReshape (tail d) (tail b)
  where
    n = head b
    d = genericDrop (n-1) a

iogIsFirst :: Ord a => [a] -> [Integer]
iogIsFirst a = zipWith ((bool2i .) . (not .) . Set.member) a sets where
  sets = Set.empty : zipWith Set.insert a sets

iogChunkWhenTruthyB truhtyFn a b = chunkWhenHelper a (map truhtyFn b)
chunkWhenHelper a b
  | null a = [[]]
  | otherwise = (head a : if truthy then head rest else []) : (if truthy then tail rest else rest)
  where
    rest = if null b then [[]] else chunkWhenHelper (tail a) (tail b)
    truthy = null b || head b
iogChunkWhen2TruthyB = iogChunkWhenTruthyB

-- low rank overrides
iogUppercase a = if a >= myOrd('a') && a <= myOrd('z') then a-32 else a
iogLowercase a = if a >= myOrd('A') && a <= myOrd('Z') then a+32 else a
iogSplitWS a = filter (not . null) $ map iogStrip $ chunkWhenHelper a (map truthyChar a)
iogDigits a = if a == 0 then [0] else iogToBase a 10
iogUndigits a = iogBaseFrom 10 $ concat (map iogDigits a)
iogKCopy = flip genericReplicate

iogToBase a b = if b==0 then error "base 0" else reverse $ toBaseHelper (abs a) where
  sign = if a > 0 then 1 else -1
  toBaseHelper a
    | a == 0 = []
    | otherwise = digit : toBaseHelper ((a-digit*sign)`div`b)
    where
      digit = (a `mod` b)*sign

iogBaseFrom b a = last results where
  results = 0 : zipWith (\x aa->x*b + aa) results a

iogRangeBegin a = [a..]
iogRangeTo a = [0..a-1]

iogSqrt x
  | x < 0 = error "negative square root"
  | x == 0 = 0
  | otherwise = sqrtGuess x x
sqrtGuess guess x = case ((x `div` guess) + guess) `div` 2 of
   r | r>=guess -> guess
     | otherwise -> sqrtGuess r x

iogMin a b = minimum [a,b]
iogMax a b = maximum [a,b]
iogPow10 = (10^)
iogProd = product :: [Integer] -> Integer

-- internal ops
iogPad :: [a] -> a -> [a]
iogPad a b = (if null a then b else head a) : iogPad (iogTail a) b
iogZeroZeroA zeroFn a = zeroFn $ head a
iogZeromZeroA = iogZeroZeroA

-- intuitive overloads
iogJoin2 a = iogJoin (map iogJust a)

-- special ops
iogDel = undefined
iogVersion = map myOrd $ VERSION ++ ", " ++ compilerName ++ ": " ++ show compilerVersion
iogType ts a = map myOrd $ ts
iogShow coerceFn a = map myOrd $ show $ coerceFn a

main=do
  hSetEncoding stdin char8
  hSetEncoding stdout char8
  rawArgs <- getArgs
  interact (iogMain rawArgs)

-- END HEADER --

)

Header.sub!("VERSION", to_eager_str(Ops["version"].ways[0].impl.call).inspect)

def to_hs_type(type, rank)
  type_and_rank_to_str(type == EmptyType ? "a" : "Integer", rank)
end

ZipNames = ["", "map", "zipWith", "zipWith3"]
def to_hs_zips(n, nargs, op)
  n <= 0 ? op : "#{ZipNames[nargs]} (#{to_hs_zips(n-1, nargs, op)})"
end

def generate_hs(source, pp_sep)
  output Header

  _,_,_,raw_mode = *lex_parse(source)

  if raw_mode
    output "iogMain rawArgs input = iogMain0 where
   inputLines = if null rawArgs then lines input else rawArgs
   iogStdinLines = map (map myOrd) inputLines\n"
    possible_input_formats = ["a\nb"]
  else
    output 'iogMain rawArgs input
  | null inputLines = iogMain5
  | onlyNums = case nums of
   [[_]] -> iogMain0
   [_] -> iogMain1
   _ -> if all ((==1) . length) nums then iogMain1 else iogMain2
  | otherwise = if length inputLines <= 1 then iogMain3 else iogMain4
  where
   inputLines = if null rawArgs then lines input else rawArgs
   nums = map readAll inputLines
   nonNums = zipWith iogSplitKeepEmpties inputLines (map (map show) nums)
   onlyNums = not (null (concat nums)) && all numLine nonNums
   numLine cut = head cut == "" && all isSep (tail (if null (last cut) && length cut > 1 then init cut else cut))
   isSep s = s == "," || s == ", " || s == " "
   iogAutoInputInt = head $ head nums
   iogAutoInputListInt = concat nums
   iogAutoInputListListInt = nums
   iogAutoInputListChar = map myOrd (if null inputLines then "" else head inputLines)
   iogAutoInputListListChar = map (map myOrd) inputLines
'
   possible_input_formats = ["1","1 1","1 1\n1","a","a\nb",""]
  end

  possible_input_formats.each.with_index{|fake_input, i|
    $ReadStdinLines = lines(str_to_lazy_list(fake_input).const).const
    begin
      ast, ast_out_inds, registers, ir2, ir2_inds, ir3, ir3_ind = run(source, nil, pp_sep)
      output "   iogMain%d =\n    let\n" % i
      to_hs_main(ir3,ir3_ind)
    rescue IogiiError => e
      output(("   iogMain%d = error %p\n" % [i, e.message]).gsub("\\e","\\x1b"))
    end
  }
end

def to_hs_main(ir3, ir3_ind)
  ir3.each.with_index{|node,i|
    args = node.args.map{|a|"a#{a} "}.join
    if node.way.name == "data"
      op = to_strict(node.way.impl.call.const).inspect
    else
      op = "iog#{node.way.name[0].capitalize+node.way.name[1..]}"
      if Header.include?(op+"TruthyB")
        op = "#{op}TruthyB truthy#{type_to_str(ir3[ir3[i].args[1]].type).capitalize}"
      elsif Header.include?(op+"TruthyA")
        op = "#{op}TruthyA truthy#{type_to_str(ir3[ir3[i].args[0]].type).capitalize}"
      elsif Header.include?(op+"ZeroA")
        op = "#{op}ZeroA zero#{type_to_str(ir3[ir3[i].args[0]].type).capitalize}"
      elsif op == "iogType"
        arg = ir3[ir3[i].args[0]]
        op += " " + type_and_rank_to_str(arg.type, arg.rank).inspect
      elsif op == "iogShow"
        arg = ir3[ir3[i].args[0]]
        if arg.type == CharType
          op += " (" + to_hs_zips(arg.rank, 1, "myChr") + ")"
        elsif arg.type == EmptyType
          op += " (" + to_hs_zips(arg.rank, 1, "toInteger") + ")"
        else
          op += " id"
        end
      end
    end

    output "      a#{i} = #{to_hs_zips(node.zip_level, node.args.size, op)} #{args}:: #{to_hs_type(node.type, node.rank)}\n"
  }
  output "    in map myChr a#{ir3_ind}\n"
end

# run.rb #######################################################################

NittyGritty = "https://golfscript.com/iogii/nittygritty.html"

def lex_parse(source)
  tokens = lex(source)
  raw_mode = if !tokens.empty? && tokens[0].str == ">"
    tokens.shift
    true else false end
  ast, ast_inds, registers = *parse_main(tokens)
  arg_used = ast.any?{|a| (AST === a || BlockAST === a) && a.args.any?{|a2|a2 == 0}} || ast_inds.any?{|a| a == 0 }
  raw_mode |= !arg_used
  [ast, ast_inds, registers, raw_mode]
end

def run(source, arg=nil, pp_sep=nil)
  ast, ast_inds, registers, raw_mode = *lex_parse(source)
  override_input = false
  arg ||= if !raw_mode
    value, type, rank, empty_input = parse_input
    override_input = empty_input && registers[RegisterName]
    new_op("autoInput#{"List"*rank}#{type_to_str(type).capitalize}", type_and_rank_to_str(type, rank)){ value }
  else
    new_op("stdinLines", "[[char]]"){ $ReadStdinLines.value }
  end

  ir, ir_inds = *to_ir(ast, arg, registers, override_input, ast_inds)
  types=solve_types(ir)
  ranks=solve_ranks(ir, types)
  ir2,ir2_inds = to_ir2(ir, ranks, types, ir_inds)
  ir3,ir3_ind = to_ir3(ir2, ir2_inds, pp_sep)
  return [ast, ast_inds, registers, ir2, ir2_inds, ir3, ir3_ind]
end

def run_and_do_io(source,pp,hs)
  pp_sep = pp ? "\n" : nil
  if hs
    generate_hs(source, pp_sep)
  else
    ast, ast_out_inds, registers, ir2, ir2_inds, ir3, ir3_ind = run(source, nil, pp_sep)
    promises = make_promises(ir3)
    begin
      prints(promises[ir3_ind].value)
    rescue SystemStackError => e
      raise IogiiError.new "Stack Overflow"
    end
    [ast, ast_out_inds, registers, ir2, ir2_inds]
  end
end

# main.rb ######################################################################
Encoding.default_external="iso-8859-1"
Encoding.default_internal="iso-8859-1"


if i=ARGV.index("--")
  $raw_args = ARGV[i+1..-1].map{|i| i.dup.force_encoding "iso-8859-1" }
  ARGV[i..-1] = []
end

if ["-v", "--version"].any?{|option| ARGV.delete(option) }
  puts Version
  exit
end

pretty_print = ["-pp", "--pretty_print"].any?{|option| ARGV.delete(option) }
hs = ["-hs", "--hs"].any?{|option| ARGV.delete(option) }

begin
  raise IogiiError.new "mustn't give more than one file arg" if ARGV.size > 1
  raise IogiiError.new "usage: ruby #$0 -v,--version | [-p,--pretty_print] [-hs,--hs] <filename.iog> [-- rawargs*]" if ARGV.size == 0
  if ARGV.size == 1
    raise IogiiError.new "file does not exist: %p" % ARGV[0] if !File.file?(ARGV[0])
    input = File.read(ARGV[0])
  end
  run_and_do_io(input, pretty_print, hs)
rescue IogiiError => e
  STDERR.puts e.message
rescue => e
  STDERR.puts "This is an internal iogii error, please report it!\n".red
  raise e
end
