#!/usr/bin/env ruby
# -*- coding: ISO-8859-1 -*-
# This is semi-minified source code, see site for original source
$site="golfscript.com/atlas"
$version="Atlas Beta (Mar 31, 2025)"
def warn(msg, from=nil)
STDERR.puts to_location(from) + msg + " (\e[31mWarning\e[0m)"
end
def to_location(from)
token = case from
when Token
from
when AST
from.token
when IR
from.from.token
when NilClass
nil
else
raise "unknown location type %p " % from
end
if token
"%s:%s (%s) " % [token.line_no||"?", token.char_no||"?", token.str]
else
""
end
end
class AtlasError < StandardError
def initialize(message,from)
@message = message
@from = from
end
def message
to_location(@from) + @message + " (\e[31m#{class_name}\e[0m)"
end
def class_name
self.class.to_s
end
end
class DynamicError < AtlasError
end
class InfiniteLoopError < AtlasError
attr_reader :source
def initialize(message,source,token)
@source = source 
super(message,token)
end
end
class StaticError < AtlasError
end
class AtlasTypeError < StaticError
def class_name
"TypeError"
end
end
class ParseError < StaticError
end
class LexError < StaticError
end
def inspect_char(char)
return "'\"" if char=='"'.ord 
"'" + 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 
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) 
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
def all_nodes(root)
all = []
dfs(root){|node| all << node}
all
end
module Status
UNSEEN = 1 
PROCESSING = 2
SEEN = 3
end
def dfs(root,cycle_fn:->_{},&post_fn)
dfs_helper(root,[],cycle_fn,post_fn)
end
def dfs_helper(node,been,cycle_fn,post_fn)
if been[node.id] == Status::SEEN
elsif been[node.id] == Status::PROCESSING 
cycle_fn[node]
else
been[node.id] = Status::PROCESSING
node.args.each{|arg|
dfs_helper(arg,been,cycle_fn,post_fn)
}
post_fn[node]
end
been[node.id] = Status::SEEN
return
end
def infer(root)
all = all_nodes(root)
q=[]
all.each{|node|
node.used_by = [];
if node.type_with_vec_level == nil
node.type_with_vec_level = UnknownV0
node.in_q = true
q << node
end
}
all.each{|node|node.args.each{|arg| arg.used_by << node} }
q.each{|node| 
node.in_q = false
prev_type = node.type_with_vec_level
calc_type(node)
if node.type_with_vec_level != prev_type && !node.last_error
node.type_updates = (node.type_updates || 0) + 1
if node.type_updates > 100
if node.type.dim < 20 && node.vec_level < 20
raise "congratulations you have found a program that does not find a fixed point for its type, please report this discovery - I am not sure if it possible and would like to know"
end
raise AtlasTypeError.new "cannot construct the infinite type" ,node
end
node.used_by.each{|dep|
if !dep.in_q
dep.in_q = true
q << dep
end
}
end
}
errors = []
dfs(root) { |node|
if node.last_error
errors << node.last_error if node.args.all?{|arg| arg.type_with_vec_level != nil }
node.type_with_vec_level = nil
end
}
errors[0...-1].each{|error| STDERR.puts error.message }
raise errors[-1] if !errors.empty?
root
end
def match_type(types, arg_types)
types = types.call if Proc === types
fn_types = types.select{|fn_type|
check_base_elem_constraints(fn_type.specs, arg_types)
}
return nil if fn_types.empty?
if fn_types.size > 1
fn_types.sort_by!{|fn_type|
fn_type.specs.zip(arg_types).map{|spec,type|
(spec.type.dim-type.dim).abs 
}
}
end
return fn_types[0]
end
def calc_type(node)
node.last_error = nil
fn_type = match_type(node.op.type, node.args.map(&:type))
return node.type_error "op is not defined for arg types: " + node.args.map{|arg|arg.type_with_vec_level.inspect}*',' if !fn_type
node.type_with_vec_level = possible_types(node,fn_type)
end
def possible_types(node, fn_type)
arg_types = node.args.map(&:type)
vec_levels = node.args.map(&:vec_level)
unvec_at_end = false
vec_levels = vec_levels.zip(fn_type.specs,0..).map{|vec_level,spec,i|
if spec.vec_of
if vec_level == 0
return node.type_error "vec level is 0, cannot lower" if node.op.name == "unvec"
unvec_at_end = true if node.op.name == 'consDefault' && arg_types[0].dim > 0
arg_types[i]-=1 
0
else
vec_level - 1
end
else
vec_level
end
}
nargs = arg_types.size
vars = solve_type_vars(arg_types, fn_type.specs)
deficits = rank_deficits(arg_types, fn_type.specs, vars)
rep_levels = [0]*nargs
promote_levels = [0]*nargs
if node.op.name == "build" && deficits[1]==0 && deficits[0]==0
all_known = !arg_types.any?(&:is_unknown)
if all_known || arg_types[0].dim <= arg_types[1].dim
promote_levels[0] += 1
end
if all_known || arg_types[1].dim <= arg_types[0].dim
promote_levels[1] += 1
end
end
nargs.times{|i|
unvec = node.args[i].op.name == "vectorize" ? 0 : [[vec_levels[i], deficits[i]].min, 0].max
unvec -= 1 if node.op.name == "build" && unvec > 0
vec_levels[i] -= unvec
deficits[i] -= unvec
}
nargs.times{|i|
promote = [0, deficits[i]].max
if node.op.name == "build" && promote == 0 && deficits[1-i]<0
promote_levels[i] += 1
deficits[1-i] += 1
elsif promote > 0 && node.op.no_promote
return node.type_error "rank too low for arg #{i+1}"
else
promote_levels[i] += promote
deficits[i] -= promote
end
}
nargs.times{|i|
vec_levels[i] -= deficits[i]
}
zip_level = vec_levels.max || 0
nargs.times{|i|
rep_levels[i] = zip_level - vec_levels[i]
return node.type_error "rank too high for arg #{i+1}" if rep_levels[i] > zip_level
arg_types[i] += promote_levels[i]
}
node.zip_level = zip_level
node.rep_levels = rep_levels
node.promote_levels = promote_levels
vars = solve_type_vars(arg_types, fn_type.specs)
t = spec_to_type(fn_type.ret, vars)
t.vec_level += zip_level
if unvec_at_end
t.vec_level -= 1
t.type.dim += 1
end
t
end
def solve_type_vars(arg_types, specs)
vars = {} 
arg_types.zip(specs) { |arg,spec|
case spec
when VarTypeSpec
(vars[spec.var_name]||=[]) << arg - spec.extra_dims
when ExactTypeSpec
else
error
end
}
vars.each{|name,uses|
max_min_dim = uses.reject(&:is_unknown).map(&:dim).min
base_elems = uses.map(&:base_elem).uniq
base_elem = if base_elems == [Unknown.base_elem]
max_min_dim = uses.map(&:dim).max
Unknown.base_elem
else
base_elems -= [Unknown.base_elem]
base_elems[0]
end
vars[name] = Type.new([max_min_dim,0].max, base_elem)
}
vars
end
def rank_deficits(arg_types, specs, vars)
arg_types.zip(specs).map{|arg,spec|
spec_dim = case spec
when VarTypeSpec
vars[spec.var_name].max_pos_dim + spec.extra_dims
when ExactTypeSpec
spec.type.dim
else
error
end
if arg.is_unknown
[spec_dim - arg.dim, 0].min
else
spec_dim - arg.dim
end
}
end
def check_base_elem_constraints(specs, arg_types)
uses={}
arg_types.zip(specs).all?{|type,spec|
spec.check_base_elem(uses,type)
}
end
$ir_node_count = 0
class IR < Struct.new(
:op,                  
:from,                
:raw_args,            
:args,                
:type_with_vec_level, 
:zip_level,
:promise,
:id, 
:in_q,
:used_by,
:rep_levels,
:promote_levels,
:last_error,
:type_updates, 
)
def initialize(*args)
super(*args)
self.id = $ir_node_count += 1
end
def type
type_with_vec_level.type
end
def vec_level
type_with_vec_level.vec_level
end
def type_error(msg)
self.last_error ||= AtlasTypeError.new msg,self
UnknownV0
end
end
def update(new,old,context,parents)
if old.op.name == "var"
name = old.from.token.str
return false if parents[name]
parents[name] = true
ans = new != context[old.from.token.str] || update(new,new,context,parents)
parents[name] = false
ans
elsif old.op.name == "ans"
false
else
changed = !new.args || new.args.zip(old.raw_args).any?{|new_arg,old_arg|
update(new_arg,old_arg,context,parents)
}
old.args = old.promise = old.type_with_vec_level = nil if changed
changed
end
end
def to_ir(ast,context,last)
context.each{|k,v| update(v,v,context,{}) }
ir=create_ir_and_set_vars(ast,context,last)
lookup_vars(ir,context)
end
def create_ir_and_set_vars(node,context,last)
if node.op.name == "set"
raise "only identifiers may be set" if node.args[1].op.name != "var"
set(node.args[1].token, node.args[0], context,last)
elsif node.op.name == "save"
ir = create_ir_and_set_vars(node.args[0],context,last)
vars = [*'a'..'z']-context.keys
raise(StaticError.new("out of save vars", node)) if vars.empty?
set(Token.new(vars[0]),ir,context,last)
elsif node.op.name == "ans"
raise StaticError.new("there is no last ans to refer to",node) if !last
last
else
args=node.args.map{|arg|create_ir_and_set_vars(arg,context,last)}
args.reverse! if node.is_flipped
IR.new(node.op,node,args)
end
end
def lookup_vars(node,context)
return node if node.args
if node.op.name == "var"
name = node.from.token.str
val = get(context, name, node.from)
val = IR.new(UnknownOp,node.from,[]) if val == node 
lookup_vars(val,context)
else
node.args = :processing
node.args = node.raw_args.map{|arg| lookup_vars(arg,context) }
node
end
end
def set(t,node,context,last)
name = t.str
$warn_on_unset_vars ||= name.size > 1 && !name[/_/]
if Commands[name] || AllOps[name]
warn("overwriting %p even though it is an op" % name, t)
Ops0.delete(name)
Ops1.delete(name)
Ops2.delete(name)
AllOps.delete(name)
Commands.delete(name)
end
if AST === node
ir = create_ir_and_set_vars(node,context,last)
else 
ir = node
end
context[name] = ir
end
def get(context,name,from)
return context[name] if context[name]
warn "using unset var",from if $warn_on_unset_vars
if numeral = to_roman_numeral(name)
type = Num
impl = numeral
elsif name.size>1
if Commands[name] || AllOps[name]
warn("using %p as identifier string even though it is an op" % name, from)
end
type = Str
impl = str_to_lazy_list(name)
else
type = Char
impl = name[0].ord
end
IR.new(create_op(name: "data",type: type,impl: impl),from,[])
end
RN = {"I"=>1,"V"=>5,"X"=>10,"L"=>50,"C"=>100,"D"=>500,"M"=>1000}
def to_roman_numeral(s)
return nil if s.chars.any?{|c|!RN[c]} || !(s =~ /^M{0,3}(CM|CD|D?C?{3})(XC|XL|L?X?{3})(IX|IV|V?I?{3})$/)
sum=0
s.length.times{|i|
v=RN[s[i]]
if i < s.length-1 && RN[s[i+1]] > v
sum-=v
else
sum+=v
end
}
sum
end
def run(root)
v = Promise.new{yield(make_promises(root))}
print_string(v)
end
def make_promises(node)
return node.promise if node.promise
arg_types = node.args.zip(0..).map{|a,i|a.type + a.vec_level - node.zip_level + node.rep_levels[i] + node.promote_levels[i]}
args = nil
node.promise = Promise.new {
zipn(node.zip_level, args, node.op.impl[arg_types, node])
}
args = node.args.zip(0..).map{|arg,i|
promoted = promoten(arg.vec_level, node.promote_levels[i], make_promises(arg))
repn(node.rep_levels[i], promoted)
}
node.promise
end
class Promise
attr_accessor :expect_non_empty
def initialize(&block)
@impl=block
end
def empty
value==[]
end
def value
if Proc===@impl
begin
raise InfiniteLoopError.new "infinite loop detected",self,nil if @calculating 
@calculating=true
@impl=@impl[]
rescue DynamicError => e
@impl = e
ensure
@calculating=false
end
raise DynamicError.new "infinite loop was assumed to be non empty, but was empty",nil if expect_non_empty && @impl == []
end
raise @impl if DynamicError===@impl
@impl
end
end
class Const < Struct.new(:value)
def empty
value==[]
end
end
class Object
def const
Const.new(self)
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 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 occurence_count(a,h=Hash.new(-1))
return [] if a.empty
[(h[to_strict_list(a.value[0])]+=1).const, Promise.new{occurence_count(a.value[1], h)}]
end
def filter(a,b,b_elem_type)
return [] if a.empty || b.empty
if truthy(b_elem_type,b.value[0])
[a.value[0],Promise.new{ filter(a.value[1],b.value[1],b_elem_type) }]
else
filter(a.value[1],b.value[1],b_elem_type)
end
end
def sortby(a,b,t)
fromby(sort(toby(a,b),t,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
def sort(a,t,by=false)
return [] if a.empty
return a.value if a.value[1].empty
n=len(a)
left=take(n/2, a).const
right=drop(n/2, a).const
merge(sort(left,t,by),sort(right,t,by),t,by)
end
def merge(a,b,t,by)
return b if a==[]
return a if b==[]
if (by ? spaceship(a[0][1], b[0][1], t) : spaceship(a[0], b[0],t)) <= 0
[a[0], Promise.new{merge(a[1].value,b,t,by)}]
else
[b[0], Promise.new{merge(a,b[1].value,t,by)}]
end
end
def to_strict_list(a,sofar=[])
a=a.value
return a if !(Array===a)
return sofar if a==[]
to_strict_list(a[1],sofar<<to_strict_list(a[0]))
end
def chunk_while(a,b,t)
return [Null,Null] if a.empty || b.empty
b0true = Promise.new{ truthy(t,b.value[0])}
rhs = Promise.new{ chunk_while(a.value[1],b.value[1],t) }
[
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 reverse(a,sofar=[])
return sofar if a.empty
reverse(a.value[1],[a.value[0],sofar.const])
end
def reshape(a,b,s=0)
raise DynamicError.new("empty list given for lengths of reshape", nil) if b.empty
return [] if a.empty
[Promise.new{take((b.value[0].value+s).to_i,a)},
Promise.new{
c=b.value[0].value+s
reshape(drop(c.to_i,a).const,b.value[1].empty ? b : b.value[1], c%1)
}]
end
def join(a,b)
concat_map(a,Null){|i,r,first|
first ? append(i,r) : append(b,Promise.new{append(i,r)})
}
end
def split(a,b) 
return [Null,Null] if a.empty
if (remainder=starts_with(a,b))
[Null,Promise.new{split(remainder,b)}]
else
rhs=Promise.new{split(a.value[1],b)}
[Promise.new{[a.value[0],Promise.new{rhs.value[0].value}]},Promise.new{rhs.value[1].value}]
end
end
def starts_with(a,b) 
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 init(a)
raise DynamicError.new "init on empty list",nil if a.empty
return [] if a.value[1].empty
[a.value[0], Promise.new{ init(a.value[1]) }]
end
def map(a,&b)
a.empty ? [] : [Promise.new{b[a.value[0]]}, Promise.new{map(a.value[1],&b)}]
end
def trunc(a)
a.empty || a.value[0].empty ? [] : [a.value[0], Promise.new{trunc(a.value[1])}]
end
def transpose(a)
return [] if a.empty
return transpose(a.value[1]) if a.value[0].empty
broken = trunc(a.value[1]).const
hds = Promise.new{ map(broken){|v|v.value[0].value} }
tls = Promise.new{ map(broken){|v|v.value[1].value} }
[Promise.new{[a.value[0].value[0],hds]},
Promise.new{transpose [a.value[0].value[1],tls].const}]
end
def last(a)
prev=nil
until a.empty
prev = a
a = a.value[1]
end
raise DynamicError.new("empty last", nil) if prev == nil
prev.value[0].value
end
def zipn(n,a,f)
return f[*a] if n <= 0 || a==[]
faith = []
return [] if a.any?{|i|
begin
i.empty
rescue InfiniteLoopError => e
faith << i
false 
end
}
faith.each{|i| i.expect_non_empty = true }
[Promise.new{zipn(n-1,a.map{|i|Promise.new{i.value[0].value}},f) },
Promise.new{zipn(n,a.map{|i|Promise.new{i.value[1].value}},f) }]
end
def repeat(a)
ret = [a]
ret << ret.const
ret
end
def repn(n,a)
if n<=0
a
else
Promise.new{ repeat(repn(n-1,a)) }
end
end
def promoten(vec_level,n,a)
if n<=0
a
else
Promise.new{zipn(vec_level,[a],-> av {
[promoten(0,n-1,av),Null]
})}
end
end
def sum(a)
return 0 if a.empty
a.value[0].value+sum(a.value[1])
end
def prod(a)
return 1 if a.empty
a.value[0].value*prod(a.value[1])
end
def spaceship(a,b,t)
if t.dim>0
return 0 if a.empty && b.empty
return -1 if a.empty
return 1 if b.empty
s0 = spaceship(a.value[0],b.value[0],t-1)
return s0 if s0 != 0
return spaceship(a.value[1],b.value[1],t)
else
a.value<=>b.value
end
end
def to_base(a,b,sign)
return [] if a==0
raise DynamicError.new "base 0", nil if b==0
digit = a%b*sign
[digit.const, Promise.new{to_base((a-digit*sign)/b,b,sign)}]
end
def from_base(a,b)
mult=1
x=0
until a.empty
x+=a.value[0].value*mult
mult*=b
a=a.value[1]
end
x
end
def len(a)
return 0 if a.empty
return 1+len(a.value[1])
end
def append(v,r)
v.empty ? r.value : [v.value[0],Promise.new{append(v.value[1], r)}]
end
def concat_map(v,rhs,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 concat(a)
concat_map(a,Null){|i,r,first|append(i,r)}
end
def inspect_value(t,value,zip_level)
inspect_value_h(t,value,Null,zip_level)
end
def inspect_value_h(t,value,rhs,zip_level)
if t==Str && zip_level <= 0
['"'.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 t==Num
str_to_lazy_list(value.value.to_s,rhs)
elsif t==Char
str_to_lazy_list(inspect_char(value.value),rhs)
else #List
[(zip_level>0?"<":"[").ord.const, Promise.new{
concat_map(value,Promise.new{str_to_lazy_list((zip_level>0?">":"]"),rhs)}){|v,r,first|
first ?
inspect_value_h(t-1,v,r,zip_level-1) :
[','.ord.const,Promise.new{inspect_value_h(t-1,v,r,zip_level-1)}]
}
}]
end
end
def coerce2s(ta, a, tb)
return a if ta==tb || tb.is_unknown || ta.is_unknown #??
case [ta.base_elem,tb.base_elem]
when [:num,:char]
raise if ta.dim+1 != tb.dim
return Promise.new{zipn(ta.dim,[a],->av{str_to_lazy_list(av.value.to_s)})}
when [:char,:num]
raise if ta.dim != tb.dim+1
return a
else
raise "coerce of %p %p not supported"%[ta,tb]
end
end
def to_string(t, value, line_mode)
dim = line_mode && t == Num+1 || t == Str+1 ? 2 : t.string_dim
added_newline = dim <= 1 && !value.empty ? Newline : Null
to_string_h(t,value,dim,added_newline)
end
def to_string_h(t, value, dim, rhs)
if t == Num
inspect_value_h(t, value, rhs, 0)
elsif t == Char
[value, rhs]
elsif t == Str
append(value, rhs)
else 
separator1 = dim == 2 ? Newline : Null
separator2 = [Null,Space,Null][dim] || Newline
concat_map(value,rhs){|v,r,first|
svalue = Promise.new{ to_string_h(t-1, v, dim-1, Promise.new{append(separator1, r)}) }
first ? svalue.value : append(separator2, svalue)
}
end
end
def print_string(value)
while !value.empty
v = value.value[0].value
raise DynamicError.new("char value, %s, outside of byte range" % v, nil) if v<0 || v>255
putc v
value = value.value[1]
end
end
def is_digit(i)
i>=48 && i<58
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
if found_int && !s.empty && s.value[0].value == ?..ord && !s.value[1].empty && is_digit(s.value[1].value[0].value)
decimals = "0."
s = s.value[1]
until s.empty || !is_digit(s.value[0].value)
found_int = true
decimals << s.value[0].value.chr
s = s.value[1]
end
v += decimals.to_f
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 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
ReadStdin = Promise.new{ read_stdin }
def read_stdin
c=STDIN.getc
if c.nil?
[]
else
[c.ord.const, Promise.new{ read_stdin }]
end
end
Null = [].const
Newline = [10.const,Null].const
Space = [32.const,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_list(v)
x=[]
until v.empty
x<<v.value[0].value
v=v.value[1]
end
x
end
def to_eager_str(v)
to_eager_list(v).map{|i|(i.to_i%256).chr}.join
end
FalseChars = {0=>1,9=>1,10=>1,11=>1,12=>1,13=>1,32=>1}
def truthy(type, value)
if type == Num
value.value > 0
elsif type == Char
!FalseChars.include?(value.value)
else 
!value.empty
end
end
class Token < Struct.new(:str,:char_no,:line_no)
def ensure_name
raise ParseError.new("cannot set %p (not an id)" % self.str, self) unless IdRx =~ str
self
end
end
AllSymbols='@!?`~#%^&*-_=+[]|;<,>.()\'"{}$/\\:'.chars.map{|c|Regexp.escape c}
NumRx = /([0-9]+([.][0-9]+)?(e-?[0-9]+)?)|([.][0-9]+(e-?[0-9]+)?)/
CharRx = /'(\\n|\\0|\\x[0-9a-fA-F][0-9a-fA-F]|.)/m
StrRx = /"(\\.|[^"])*"?/
AtomRx = /#{CharRx}|#{NumRx}|#{StrRx}/
SymRx = /#{AllSymbols*'|'}/
CommentRx = /--.*/
IgnoreRx = /#{CommentRx}|[ \t]+/
NewlineRx = /\r\n|\r|\n/
IdRx = /[^#{AllSymbols.join} \t\n\r0-9][^#{AllSymbols.join} \t\n\r]*/ 
def lex(code,line_no=1) 
tokens = [[]]
char_no = 1
code.scan(/#{AtomRx}|#{CommentRx}|#{SymRx}|#{NewlineRx}|#{IgnoreRx}|#{IdRx}/m) {|matches|
token=Token.new($&,char_no,line_no)
match=$&
line_no += $&.scan(NewlineRx).size
if match[NewlineRx]
char_no = match.size-match.rindex(NewlineRx)
else
char_no += match.size
end
if token.str =~ /^#{IgnoreRx}$/
elsif token.str =~ /^#{NewlineRx}$/
tokens << []
else
tokens[-1] << token
end
}
[tokens,line_no+1]
end
Inf = 2**61 
Type = Struct.new(:dim,:base_elem) 
TypeWithVecLevel = Struct.new(:type,:vec_level)
class Type
def inspect
"["*dim + (base_elem.to_s.size>1 ? base_elem.to_s.capitalize : base_elem.to_s) + "]"*dim
end
def -(rhs)
self+-rhs
end
def +(zip_level)
Type.new(dim+zip_level, base_elem)
end
def max_pos_dim
is_unknown ? Inf : dim
end
def string_dim 
dim + (is_char ? -1 : 0)
end
def is_char
base_elem == :char
end
def is_unknown
base_elem == :a
end
def can_base_be(rhs) 
return self.base_elem == rhs.base_elem
end
def default_value
return [] if dim > 0
return 32 if is_char
return 0 if base_elem == :num
raise DynamicError.new("access of the unknown type",nil)
end
end
Num = Type.new(0,:num)
Char = Type.new(0,:char)
Str = Type.new(1,:char)
Unknown = Type.new(0,:a)
UnknownV0 = TypeWithVecLevel.new(Unknown,0)
Empty = Unknown+1
class TypeWithVecLevel
def inspect
"<"*vec_level + type.inspect + ">"*vec_level
end
end
A = :a
B = :b
Achar = :a_char
Anum = :a_num
class FnType < Struct.new(:specs,:ret,:orig_key,:orig_val)
def inspect
specs.map(&:inspect)*" "+" -> "+parse_raw_arg_spec(ret).inspect
end
end
class VecOf < Struct.new(:of)
end
def v(a)
VecOf.new(a)
end
def create_specs(raw_spec)
case raw_spec
when Hash
raw_spec.map{|raw_arg,ret|
specs = (x=case raw_arg
when Array
if raw_arg.size == 1
[raw_arg]
else
raw_arg
end
else
[raw_arg]
end).map{|a_raw_arg| parse_raw_arg_spec(a_raw_arg) }
FnType.new(specs,ret,raw_arg,ret)
}
when Type, Array, VecOf, Symbol
[FnType.new([],raw_spec,[],raw_spec)]
when Proc 
lambda{ create_specs(raw_spec.call) }
else
raise "unknown fn type format"
end
end
def parse_raw_arg_spec(raw,list_nest_depth=0)
case raw
when Symbol
VarTypeSpec.new(raw,list_nest_depth)
when Array
raise if raw.size != 1
parse_raw_arg_spec(raw[0],list_nest_depth+1)
when VecOf
r=parse_raw_arg_spec(raw.of)
r.vec_of=true
r
when Type
ExactTypeSpec.new(Type.new(raw.dim+list_nest_depth, raw.base_elem))
else
p raw
error
end
end
class ExactTypeSpec
attr_reader :type
attr_accessor :vec_of
def initialize(rtype)
@type = rtype
end
def check_base_elem(uses,t)
t.can_base_be(@type)
end
def inspect
(vec_of ? "<" : "")+type.inspect+(vec_of ? ">" : "")
end
end
class VarTypeSpec
attr_reader :var_name
attr_reader :extra_dims
attr_accessor :vec_of
def initialize(var_sym, extra_dims) 
@var_name,@var_constraint = name_and_constraint(var_sym)
@extra_dims = extra_dims
@vec_of = false
end
def check_base_elem(uses,type)
if @var_constraint
@var_constraint == type.base_elem.to_s
else
type.base_elem == Unknown.base_elem || (uses[var_name]||=type.base_elem) == type.base_elem
end
end
def inspect
constraint = @var_constraint ? " (#{@var_constraint})" : ""
(vec_of ? "<" : "")+"["*extra_dims+var_name.to_s+constraint+"]"*extra_dims+(vec_of ? ">" : "")
end
end
def name_and_constraint(var_sym)
s = var_sym.to_s.split("_")
[s[0].to_sym, s[1]]
end
def spec_to_type(spec, vars)
case spec
when Type
TypeWithVecLevel.new(spec,0)
when Array
raise "cannot return multiple values for now" if spec.size != 1
TypeWithVecLevel.new(spec_to_type(spec[0], vars).type + 1, 0)
when Symbol
name,constraint=name_and_constraint(spec)
t=TypeWithVecLevel.new(vars[name],0)
t.type.base_elem = constraint.to_sym if constraint
t
when VecOf
t=spec_to_type(spec.of, vars)
TypeWithVecLevel.new(t.type, t.vec_level+1)
else
unknown
end
end
MacroImpl = -> *args { raise "macro impl called" }
class Op < Struct.new(
:name,
:sym, 
:type,
:type_summary,
:examples,
:desc,
:ref_only,
:no_promote,
:impl,
:tests)
def narg
return 0 if Proc === type 
type ? type[0].specs.size : 0
end
def help(out=STDOUT)
out.puts "#{name} #{sym}"
out.puts desc if desc
if type_summary
out.puts type_summary
else
type.each{|t|
out.puts t.inspect.gsub('->',"\xE2\x86\x92").gsub('[Char]','Str')
}
end
(examples+tests).each{|example|
out.puts example.gsub('->',"\xE2\x86\x92")
}
misc = []
out.puts
end
def add_test(s)
tests<<s
self
end
end
class String
def help(unused=true)
puts
puts "#"*(self.size+4)
puts "#"+" "+self+" #"
puts "#"*(self.size+4)
puts
end
end
def create_op(
name: nil,
sym: nil,
type: ,
type_summary: nil,
example: nil,
example2: nil,
no_promote: false,
desc: nil,
ref_only: false,
poly_impl: nil, 
impl: nil,
impl_with_loc: nil, 
coerce: false,
final_impl: nil
)
type = create_specs(type)
raise "exactly on of [poly_impl,impl,impl_with_loc,final_impl] must be set" if [poly_impl,impl,impl_with_loc,final_impl].compact.size != 1
if poly_impl
built_impl = -> arg_types,from { poly_impl[*arg_types] }
elsif impl_with_loc
built_impl = -> arg_types,from { impl_with_loc[from] }
elsif final_impl
built_impl = final_impl
else
built_impl = -> arg_types,from { Proc===impl ? impl : lambda { impl } }
end
examples = []
examples << example if example
examples << example2 if example2
if coerce
f = built_impl
built_impl = -> t,from { -> a,b { f[t,from][coerce2s(t[0],a,t[1]),coerce2s(t[1],b,t[0])] }}
end
Op.new(name,sym,type,type_summary,examples,desc,ref_only,no_promote,built_impl,[])
end
ApplyModifier = "@"
FlipModifier = "\\"
OpsList = [
"math",
create_op(
name: "add",
sym: "+",
example: "1+2 -> 3",
example2: "'a+1 -> 'b",
type: { [Num,Num] => Num,
[Num,Char] => Char,
[Char,Num] => Char },
impl: -> a,b { a.value + b.value })
.add_test("'a+1.2 -> 'b"),
create_op(
name: "sum",
sym: "+",
example: "1,2,3,4+ -> 10",
type: { [Num] => Num },
no_promote: true,
impl: -> a { sum(a) })
.add_test("1;>+ -> 0"),
create_op(
name: "sub",
sym: "-",
example: '5-3 -> 2',
example2: "'b-'a -> 1",
type: { [Num,Num] => Num,
[Char,Num] => Char,
[Num,Char] => Char,
[Char,Char] => Num },
poly_impl: ->at,bt { flipif bt.is_char && !at.is_char, -> a,b { a.value - b.value }})
.add_test("1-'b -> 'a"),
create_op(
name: "mult",
example: '2*3 -> 6',
sym: "*",
type: { [Num,Num] => Num },
impl: -> a,b { a.value * b.value }),
create_op(
name: "prod",
sym: "*",
example: "1,2,3,4* -> 24",
no_promote: true,
type: { [Num] => Num },
impl: -> a { prod(a) })
.add_test("1;>* -> 1"),
create_op(
name: "div",
desc: "0/0 is 0",
example: '7/3 -> 2',
sym: "/",
type: { [Num,Num] => Num },
impl_with_loc: -> from { -> a,b {
if b.value==0
if a.value == 0
0
else
raise DynamicError.new("div 0", from)
end
else
a.value/b.value
end
}})
.add_test("10/5 -> 2")
.add_test("9/5 -> 1")
.add_test("11/(5-) -> -3")
.add_test("10/(5-) -> -2")
.add_test("11-/5 -> -3")
.add_test("10-/5 -> -2")
.add_test("10-/(5-) -> 2")
.add_test("9-/(5-) -> 1")
.add_test("1/0 -> DynamicError")
.add_test("0/0 -> 0"),
create_op(
name: "mod",
desc: "anything mod 0 is 0",
example: '7%3 -> 1',
sym: "%",
type: { [Num,Num] => Num },
impl_with_loc: -> from { -> a,b {
if b.value==0
0
else
a.value % b.value
end
}})
.add_test("10%5 -> 0")
.add_test("9%5 -> 4")
.add_test("11%(5-) -> -4")
.add_test("10%(5-) -> 0")
.add_test("11-%5 -> 4")
.add_test("10-%5 -> 0")
.add_test("10-%(5-) -> 0")
.add_test("9-%(5-) -> -4")
.add_test("5%0 -> 0"),
create_op(
name: "pow", 
desc: "negative exponent will result in a rational, but this behavior is subject to change and not officially supported, similarly for imaginary numbers",
example: '2^3 -> 8',
sym: "^",
type: { [Num,Num] => Num },
impl: -> a,b { a.value ** b.value }),
create_op(
name: "neg",
sym: "-",
type: { Num => Num },
example: '2- -> -2',
impl: -> a { -a.value }
), create_op(
name: "abs",
sym: "|",
type: { Num => Num },
example: '2-| -> 2',
example2: '2| -> 2',
impl: -> a { a.value.abs }),
create_op(
name: "floor",
sym: "&",
type: { Num => Num },
example: '1.3& -> 1',
impl: -> a { a.value.floor }),
create_op(
name: "toBase",
sym: ";",
type: { [Num,Num] => [Num],
[Num,Char] => Str },
example: '6;2 -> [0,1,1]',
impl: -> a,b { to_base(a.value.abs,b.value,a.value<=>0) })
.add_test('3,3.;2 -> <[1,1],[1,1]>')
.add_test('6;(2-) -> [0,-1,-1,-1]')
.add_test('6-;2 -> [0,-1,-1]')
.add_test('5.5;2 -> [1.5,0.0,1.0]')
.add_test('5.5-;2 -> [-1.5,-0.0,-1.0]'),
create_op(
name: "fromBase",
sym: ";",
type: { [[Num],Num] => Num,
[Str,Num] => Num,
[Str,Char] => Num },
example: '0,1,1;2 -> 6',
impl: -> a,b { from_base(a,b.value) })
.add_test('0,1,1,1-%;(2-) -> 6')
.add_test('0,1,1-%;2 -> -6')
.add_test('1.5,0.0,1.0;2 -> 5.5')
.add_test('"abc";\'d -> 999897'),
"vector",
create_op(
name: "unvec",
sym: "%",
example: '1,2+3% -> [4,5]',
type: { v(A) => [A] },
impl: -> a { a.value },
), create_op(
name: "vectorize",
sym: ".",
example: '1,2,3. -> <1,2,3>',
type: { [A] => v(A) },
impl: -> a { a.value }),
create_op(
name: "range",
sym: ":",
example: '3:7 -> <3,4,5,6>',
type: { [Num,Num] => v(Num),
[Char,Char] => v(Char) },
impl: -> a,b { range(a.value, b.value) })
.add_test("5:3 -> <>")
.add_test("1.5:5 -> <1.5,2.5,3.5,4.5>"),
create_op(
name: "repeat",
sym: ",",
example: '2, -> <2,2,2,2,2...',
type: { A => v(A) },
impl: -> a { repeat(a) }),
create_op(
name: "from",
sym: ":",
example: '3: -> <3,4,5,6,7,8...',
type: { Num => v(Num),
Char => v(Char) },
impl: -> a { range_from(a.value) }),
create_op(
name: "consDefault",
sym: "^",
example: '2,3.^ -> <0,2,3>',
type: { v(A) => v(A) },
type_summary: "<a> -> <a>\n[a] -> [a]",
poly_impl: -> at { d=(at-1).default_value.const; -> a { [d,a] }})
.add_test("1,2^ -> [0,1,2]")
.add_test("1^ -> <0,1>"),
"basic list",
create_op(
name: "head",
sym: "[",
example: '"abc"[ -> \'a',
type: { [A] => A },
no_promote: true,
impl_with_loc: -> from { -> a {
raise DynamicError.new "head on empty list",from if a.empty
a.value[0].value
}},
), create_op(
name: "last",
sym: "]",
no_promote: true,
example: '"abc"] -> \'c',
type: { [A] => A },
impl_with_loc: -> from { -> a {
raise DynamicError.new "last on empty list",from if a.empty
last(a)
}}
), create_op(
name: "tail",
example: '"abc"> -> "bc"',
sym: ">",
no_promote: true,
type: { [A] => [A] },
impl_with_loc: -> from { -> a {
raise DynamicError.new "tail on empty list",from if a.empty
a.value[1].value}}
), create_op(
name: "init",
example: '"abc"< -> "ab"',
sym: "<",
no_promote: true,
type: { [A] => [A] },
impl_with_loc: -> from { -> a {
raise DynamicError.new "init on empty list",from if a.empty
init(a)
}}
), create_op(
name: "len",
example: '"asdf"#'+' -> 4',
sym: "#",
type: { [A] => Num },
no_promote: true,
impl: -> a { len(a) }),
create_op(
name: "take",
sym: "[",
example: '"abcd"[3 -> "abc"',
type: { [[A],Num] => [A],
[Num,[Achar]] => [A] },
poly_impl: ->at,bt { flipif bt.is_char, -> a,b { take(b.value, a) }}
).add_test('"abc"[(2-) -> ""')
.add_test('"abc"[1.2 -> "a"')
.add_test('1["abc" -> "a"')
.add_test('""[2 -> ""'),
create_op(
name: "drop",
sym: "]",
example: '"abcd"]3 -> "d"',
type: { [[A],Num] => [A],
[Num,[Achar]] => [A] },
poly_impl: ->at,bt { flipif bt.is_char, -> a,b { drop(b.value, a) }}
).add_test('"abc"](2-) -> "abc"')
.add_test('"abc"]1.2 -> "bc"')
.add_test('1]"abc" -> "bc"')
.add_test('""]2 -> ""'),
create_op(
name: "single",
sym: ";",
example: '2; -> [2]',
type: { A => [A] },
impl: -> a { [a,Null] }),
create_op(
name: "concat",
sym: "_",
no_promote: true,
example: '"abc","123"_ -> "abc123"',
type: { [[A]] => [A] },
impl: -> a { concat(a) }),
create_op(
name: "append",
sym: "_",
example: '"abc"_"123" -> "abc123"',
type: { [[A],[A]] => [A],
[Anum,[Achar]] => [Achar],
[[Achar],Anum] => [Achar] },
type_summary: "[*a] [*a] -> [a]",
impl: -> a,b { append(a,b) },
coerce: true)
.add_test('1_"a" -> "1a"'),
create_op(
name: "cons",
sym: "`",
example: '1`2`3 -> [3,2,1]',
type: { [[A],A] => [A],
[Anum,Achar] => [Achar],
[[[Achar]],Anum] => [[Achar]] },
type_summary: "[*a] *a -> [a]",
poly_impl: -> ta,tb {-> a,b { [coerce2s(tb,b,ta-1),coerce2s(ta,a,tb+1)] }})
.add_test('\'a`5 -> ["5","a"]')
.add_test('"a"`(5) -> ["5","a"]')
.add_test('"a";;`(5;) -> [["5"],["a"]]')
.add_test("5`\'a -> \"a5\"")
.add_test('5;`"a" -> ["a","5"]')
.add_test('\'b`\'a -> "ab"'),
create_op(
name: "build",
sym: ",",
example: '1,2,3 -> [1,2,3]',
type: { [[A],[A]] => [A],
[Anum,[Achar]] => [Achar],
[[Achar],Anum] => [Achar] },
type_summary: "*a *a -> [a]\n[*a] *a -> [a]\n*a [*a] -> [a]",
poly_impl: -> ta,tb {-> a,b {
append(coerce2s(ta,a,tb),coerce2s(tb,b,ta))
}}
).add_test("2,1 -> [2,1]")
.add_test('(2,3),1 -> [2,3,1]')
.add_test('(2,3),(4,5),1 -> <[2,3,1],[4,5,1]>')
.add_test('2,(1,0) -> [2,1,0]')
.add_test('(2,3),(1,0) -> [[2,3],[1,0]]')
.add_test('(2,3).,1 -> <[2,1],[3,1]>')
.add_test('(2,3),(4,5).,1 -> <[2,3,1],[4,5,1]>')
.add_test('2,(1,0.) ->  <[2,1],[2,0]>')
.add_test('(2,3),(1,0.) -> <[2,3,1],[2,3,0]>')
.add_test('\'a,5 -> "a5"')
.add_test('5,\'a -> "5a"')
.add_test('5,"a" -> ["5","a"]')
.add_test('\'b,\'a -> "ba"'),
"more list",
create_op(
name: "count",
desc: "count the number of times each element has occurred previously",
sym: "=",
example: '"abcaab" = -> [0,0,0,1,2,1]',
type: { [A] => [Num] },
no_promote: true,
impl: -> a { occurence_count(a) }
).add_test('"ab","a","ab" count -> [0,0,1]'),
create_op(
name: "filterFrom",
sym: "~",
example: '0,1,1,0 ~ "abcd" -> "bc"',
type: { [v(A),[B]] => [B] },
poly_impl: -> at,bt { -> a,b { filter(b,a,at-1) }}),
create_op(
name: "sort",
desc: "O(n log n) sort - not optimized for lazy O(n) min/max yet todo",
sym: "!",
example: '"atlas" ! -> "aalst"',
type: { [A] => [A] },
no_promote: true,
poly_impl: -> at { -> a { sort(a,at-1) }}
), create_op(
name: "sortFrom",
desc: "stable O(n log n) sort - not optimized for lazy O(n) min/max yet todo",
sym: "!",
example: '3,1,4 ! "abc" -> "bac"',
type: { [v(A),[B]] => [B] },
poly_impl: -> at,bt { -> a,b { sortby(b,a,at-1) }})
.add_test('"hi","there" ! (1,2,3) -> [1,2]')
.add_test('"aaaaaa" ! "abcdef" -> "abcdef"'),
create_op(
name: "chunk",
desc: "chunk while truthy",
sym: "?",
example: '"12 3"? -> ["12","3"]',
type: { [A] => [[A]] },
poly_impl: -> at { -> a { chunk_while(a,a,at-1) } }),
create_op(
name: "chunkFrom",
desc: "chunk while first arg is truthy",
sym: "?",
example: '"11 1" ? "abcd" -> ["ab","d"]',
type: { [v(A),[B]] => [[B]] },
poly_impl: -> at,bt { -> a,b { chunk_while(b,a,at-1) } })
.add_test('" 11  " ? "abcde" -> ["","bc","",""]')
.add_test('()?"" -> [""]'),
create_op(
name: "transpose",
sym: "\\",
example: '"abc","1"\\ -> ["a1","b","c"]',
type: { [[A]] => [[A]] },
impl: -> a { transpose(a) },
).add_test('"abc","1234"\ -> ["a1","b2","c3","4"]'),
create_op(
name: "reverse",
sym: "/",
example: '"abc" / -> "cba"',
type: { [A] => [A] },
no_promote: true,
impl: -> a { reverse(a) }),
create_op(
name: "reshape",
sym: "#",
desc: "Take elements in groups of sizes. If second list runs out, last element is repeated",
example: '"abcdef"#2 -> ["ab","cd","ef"]',
type: { [[A],[Num]] => [[A]],
[[Num],[Achar]] => [[A]] },
poly_impl: ->at,bt { flipif bt.is_char, -> a,b { reshape(a,b) }})
.add_test('"abc"#2 -> ["ab","c"]')
.add_test('"abcd"#(2,1) -> ["ab","c","d"]')
.add_test('"."^10#2.5*" " -> ".. ... .. ..."')
.add_test('2#"abcd" -> ["ab","cd"]')
.add_test('""#2 -> []'),
"string",
create_op(
name: "join",
example: '"hi","yo"*" " -> "hi yo"',
sym: "*",
type: { [[Str],Str] => Str,
[[Num],Str] => Str,},
poly_impl: -> at,bt { -> a,b { join(coerce2s(at,a,Str+1),b) } })
.add_test('1,2,3*", " -> "1, 2, 3"'),
create_op(
name: "split",
desc: "split, keeping empty results (include at beginning and end)",
example: '"hi, yo"/", " -> ["hi","yo"]',
sym: "/",
type: { [Str,Str] => [Str] },
impl: -> a,b { split(a,b) })
.add_test('"abcbcde"/"bcd" -> ["abc","e"]')
.add_test('"ab",*" "/"b "[2 -> ["a","a"]') 
.add_test('",a,,b,"/"," -> ["","a","","b",""]'),
create_op(
name: "replicate",
example: '"ab"^3 -> "ababab"',
sym: "^",
type: { [Str,Num] => Str,
[Num,Str] => Str },
poly_impl: -> ta,tb { flipif !ta.is_char, -> a,b {
ipart = concat(take(b.value,repeat(a).const).const)
if b.value.class == Integer
ipart
else
append(ipart.const, take(b.value%1*len(a), a).const)
end
}})
.add_test('2^"ab" -> "abab"')
.add_test('"abcd"^2.5 -> "abcdabcdab"'),
create_op(
name: "ord",
example: "'a& -> 97",
sym: "&",
type: { Char => Num },
impl: -> a { a.value }),
"logic",
create_op(
name: "equalTo",
example: '3=3 -> [3]',
example2: '3=0 -> []',
sym: "=",
type: { [A,A] => [A] },
poly_impl: -> ta,tb {-> a,b { spaceship(a,b,ta) == 0 ? [a,Null] : [] } })
.add_test("3=2 -> []")
.add_test("1=2 -> []")
.add_test("1=1 -> [1]")
.add_test('\'a=\'a -> "a"')
.add_test("'d=100 -> AtlasTypeError")
.add_test('"abc"="abc" -> ["abc"]')
.add_test('"abc"="abd" -> []')
.add_test('"abc"=\'a -> <"a","","">')
.add_test('"abc"=(\'a.) -> <"a">')
.add_test('"abc".="abd" -> <"a","b","">'),
create_op(
name: "lessThan",
example: '4<5 -> [4]',
example2: '5<4 -> []',
sym: "<",
type: { [A,A] => [A] },
poly_impl: -> ta,tb {-> a,b { spaceship(a,b,ta) == -1 ? [a,Null] : [] } }
).add_test("5<4 -> []"),
create_op(
name: "greaterThan",
example: '5>4 -> [5]',
example2: '4>5 -> []',
sym: ">",
type: { [A,A] => [A] },
poly_impl: -> ta,tb {-> a,b { spaceship(a,b,ta) == 1 ? [a,Null] : [] } }
).add_test("4>5 -> []"),
create_op(
name: "not",
sym: "~",
type: { A => Num },
example: '2~ -> 0',
example2: '0~ -> 1',
poly_impl: -> ta { -> a { truthy(ta,a) ? 0 : 1 } }),
create_op(
name: "and",
sym: "&",
example: '1&2 -> 2',
example2: '1-&2 -> -1',
type: { [A,B] => B },
poly_impl: ->ta,tb { -> a,b { truthy(ta,a) ? b.value : (ta==tb ? a.value : tb.default_value) }}
),
create_op(
name: "or",
sym: "|",
example: '2|0 -> 2',
example2: '0|2 -> 2',
type: { [A,A] => A,
[Anum,[Achar]] => [Achar],
[[Achar],Anum] => [Achar] },
type_summary: "*a *a -> a",
poly_impl: ->ta,tb { -> a,b { truthy(ta,a) ? coerce2s(ta,a,tb).value : coerce2s(tb,b,ta).value }},
).add_test("0|2 -> 2")
.add_test('1|"b" -> "1"')
.add_test('"b"|3 -> "b"')
.add_test('0|"b" -> "b"')
.add_test('""|2 -> "2"')
.add_test(' 0|\'c -> "c"'),
create_op(
name: "catch",
sym: "}",
example: '1/0 catch -> []',
example2: '1/1 catch -> [1]',
type: { A => [A] },
impl: -> a {
begin
a.value
[a, Null]
rescue AtlasError 
[]
end
}),
'"io"',
create_op(
name: "inputRaw",
type: Str,
impl: -> { ReadStdin.value }),
create_op(
name: "inputLines",
type: v(Str),
impl: -> { lines(ReadStdin) }),
create_op(
name: "inputVector",
type: v(Num),
impl: -> { split_non_digits(ReadStdin) }),
create_op(
name: "inputMatrix",
type: v([Num]),
impl: -> { map(lines(ReadStdin).const){|v|split_non_digits(v)} }),
create_op(
name: "ans",
type: A,
impl: MacroImpl),
create_op(
name: "read",
sym: "`",
type: { Str => [Num] },
example: '"1 2 -3"` -> [1,2,-3]',
impl: -> a { split_non_digits(a) })
.add_test('"1 2.30 -3 4a5 - -6 --7 .8" ` -> [1,2.3,-3,4,5,-6,7,8]'),
create_op(
name: "str",
sym: "`",
example: '12` -> "12"',
type: { Num => Str },
impl: -> a { inspect_value(Num,a,0) }),
"syntactic sugar",
create_op(
name: "set",
desc: "save to a variable without consuming it",
example: '5@a+a -> 10',
sym: ApplyModifier,
type: { [A,:id] => A },
impl: MacroImpl,
), create_op(
name: "save",
desc: "save to next available var (a,b,c,...)",
example: '5{,1,a,2 -> [5,1,5,2]',
sym: "{",
type: { A => A },
impl: MacroImpl),
create_op(
name: "flip",
sym: "\\",
desc: "reverse order of previous op's args",
example: '2-\\5 -> 3',
ref_only: true,
type: :unused,
type_summary: "op\\",
impl: MacroImpl,
), create_op(
name: "apply",
sym: "@",
desc: "increase precedence, apply next op before previous op",
example: '2*3@+4 -> 14',
type: {:unused => :unused},
type_summary: "@op",
impl_with_loc: ->from{raise ParseError.new("@ must be followed by an op or atom",from)}, 
),
]
ActualOpsList = OpsList.reject{|o|String===o}
Ops0 = {}
Ops1 = {}
Ops2 = {}
AllOps = {}
def flipif(cond,impl)
if cond
-> a,b { impl[b,a] }
else
impl
end
end
def addOp(table,op)
if (existing=table[op.sym])
combined_type = {}
op.type.each{|s|combined_type[s.orig_key]=s.orig_val}
existing.type.each{|s|combined_type[s.orig_key]=s.orig_val}
combined_impl = -> arg_types,from {
best_match = match_type(existing.type + op.type, arg_types)
if existing.type.include? best_match
existing.impl[arg_types,from]
else
op.impl[arg_types,from]
end
}
combined = create_op(
sym: op.sym,
type: combined_type,
final_impl: combined_impl,
)
table[op.sym] = combined
else
table[op.sym] = op
end
table[op.name] = op
end
ActualOpsList.each{|op|
next if op.ref_only
ops = case op.narg
when 0
addOp(Ops0, op)
when 1
addOp(Ops1, op)
when 2
addOp(Ops2, op)
else; error; end
raise "name conflict #{op.name}" if AllOps.include? op.name
AllOps[op.name] = AllOps[op.sym] = op
}
ImplicitOp = Ops2["build"]
AllOps[""]=Ops2[""]=ImplicitOp 
EmptyOp = create_op(
name: "empty",
type: Empty,
impl: [])
UnknownOp = create_op(
name: "unknown",
type: Unknown,
impl_with_loc: -> from { raise AtlasTypeError.new("cannot use value of the unknown type", from) }
)
Var = Op.new("var")
def create_num(t)
create_op(
name: "data",
type: Num,
impl: t.str[/[.e]/] ? t.str.to_f : t.str.to_i
)
end
def create_str(t)
raise LexError.new("unterminated string",t) if t.str[-1] != '"' || t.str.size==1
create_op(
name: "data",
type: Str,
impl: str_to_lazy_list(parse_str(t.str[1...-1]))
)
end
def create_char(t)
raise LexError.new("empty char",t) if t.str.size < 2
create_op(
name: "data",
type: Char,
impl: parse_char(t.str[1..-1]).ord
)
end
IO_Vars = {"r" => 'inputRaw',
"l" => 'inputLines',
"v" => 'inputVector',
"m" => 'inputMatrix'}
Commands = {
"help" => ["see op's info", "op", -> tokens, ir_cb {
raise ParseError.new("usage: help <op>, see #$site for tutorial",tokens[0]) if tokens.size != 1
relevant = ActualOpsList.filter{|o|[o.name, o.sym].include?(tokens[0].str)}
if !relevant.empty?
relevant.each(&:help)
else
puts "no such op: #{tokens[0].str}"
end
}],
"version" => ["see atlas version", nil, -> tokens, ir_cb {
raise ParseError.new("usage: version",tokens[0]) if !tokens.empty?
puts $version
}],
"type" => ["see expression type", "a", -> tokens, ir_cb {
p ir_cb.call.type_with_vec_level
}],
"p" => ["pretty print value", "a", -> tokens, ir_cb {
ir = ir_cb.call
run(ir) {|v,n,s| inspect_value(ir.type+ir.vec_level,v,ir.vec_level) }
puts
}],
"print" => ["print value (implicit)", "a", -> tokens, ir_cb {
ir = ir_cb.call
run(ir) {|v,n,s| to_string(ir.type+ir.vec_level,v,true) }
}],
}
AST = Struct.new(:op,:args,:token,:is_flipped)
def parse_line(tokens)
get_expr(balance_parens(tokens),false,nil)
end
def get_expr(tokens,apply,implicit_value)
top = tokens.pop
if top == nil || top.str == "("
tokens << top if top != nil 
rhs = implicit_value
elsif op=Ops1[top.str]
arg = get_expr(tokens,apply|apply_check(tokens),implicit_value)
rhs = AST.new(op,[arg],top)
elsif top.str == ")"
if tokens.empty? || tokens[-1].str == "("
rhs = AST.new(EmptyOp,[],top)
else
v = new_paren_var
rhs = AST.new(Ops2["set"], [get_expr(tokens,false,v),v], nil)
end
tokens.pop
else
rhs = make_op0(top)
end
return rhs if apply
until tokens.empty? || tokens[-1].str == "("
flipped = flip_check(tokens)
from = tokens[-1]
op=Ops2[from.str]
if op && op.name == "set"
if rhs.op.name == "var"
rhs.token.ensure_name
else
if tokens[-1].str == "@"
op=nil 
else
raise ParseError.new("must set id's", tokens[-1])
end
end
end
tokens.pop if op
lhs = get_expr(tokens,apply_check(tokens),implicit_value)
rhs = AST.new(op||ImplicitOp,[lhs,rhs],from,flipped)
end
rhs
end
def apply_check(tokens)
!tokens.empty? && tokens[-1].str == ApplyModifier && (tokens.pop; true)
end
def flip_check(tokens)
!tokens.empty? && tokens[-1].str == FlipModifier && (tokens.pop; true)
end
$paren_vars = 0
def new_paren_var
AST.new(Var,[],Token.new("paren_var#{$paren_vars+=1}"))
end
def make_op0(t)
str = t.str
if str =~ /^#{NumRx}$/
AST.new(create_num(t),[],t)
elsif str[0] == '"'
AST.new(create_str(t),[],t)
elsif str[0] == "'"
AST.new(create_char(t),[],t)
elsif (op=Ops0[str])
AST.new(op,[],t)
else
AST.new(Var,[],t)
end
end
def balance_parens(tokens)
depth = left = 0
tokens.each{|t|
if t.str == '('
depth += 1
elsif t.str == ')'
if depth == 0
left += 1
else
depth -= 1
end
end
}
[Token.new("(")]*(left+1) + tokens + [Token.new(")")]*(depth+1)
end
Encoding.default_external="iso-8859-1"
Encoding.default_internal="iso-8859-1"
def repl(input=nil)
context={}
{ "N" => "'\n",
"S" => "' ",
}.each{|name,val|
context[name]=to_ir(AST.new(create_char(Token.new(val)),[]),{},nil)
}
IO_Vars.each{|k,v| context[k] = to_ir(AST.new(Ops0[v],[]),{},nil) }
line_no = 1
last = nil
if input
input_fn = lambda { input.gets(nil) }
elsif !ARGV.empty?
$line_mode = true if $line_mode.nil?
input_fn = lambda {
return nil if ARGV.empty? 
filename = ARGV.shift
raise AtlasError.new("no such file %p" % filename, nil) unless File.exists? filename
File.read(filename)
}
else
require "readline"
$repl_mode = true
hist_file = Dir.home + "/.atlas_history"
if File.exist? hist_file
Readline::HISTORY.push *File.read(hist_file).split("\n")
end
input_fn = lambda {
line = Readline.readline("\e[33m \xE1\x90\xB3 \e[0m".force_encoding("utf-8"), true)
File.open(hist_file,'a'){|f|f.puts line} unless !line || line.empty?
line
}
Readline.completion_append_character = " "
Readline.basic_word_break_characters = " \n\t1234567890~`!@\#$%^&*()_-+={[]}\\|:;'\",<.>/?"
Readline.completion_proc = lambda{|s|
var_names = context.keys.reject{|k|k['_']} 
all = var_names + ActualOpsList.filter(&:name).map(&:name) + Commands.keys
all.filter{|name|name.index(s)==0}.reject{|name|name['_']}
}
end
loop {
begin
line=input_fn.call
break if line==nil 
token_lines,line_no=lex(line, line_no)
token_lines.each{|tokens| 
next if tokens.empty?
exe = lambda{
if tokens.empty?
raise ParseError.new("expecting an expression for command on line %d" % (line_no-1),nil)
else
last = tokens.empty? ? last : infer(to_ir(parse_line(tokens),context,last))
end
}
if (command=Commands[tokens[0].str])
tokens.shift
command[2][tokens, exe]
elsif !command && (command=Commands[tokens[-1].str])
tokens.pop
command[2][tokens, exe]
elsif tokens[0].str=="let"
raise ParseError.new("let syntax is: let var = value", tokens[0]) unless tokens.size > 3 && tokens[2].str=="="
set(tokens[1].ensure_name, parse_line(tokens[3..-1]), context,last)
else
ir = exe.call
puts "\e[38;5;243m#{ir.type_with_vec_level.inspect}\e[0m" if $repl_mode
run(ir) {|v|
to_string(ir.type+ir.vec_level,v,$line_mode)
}
end
}
rescue AtlasError => e
STDERR.puts e.message
rescue SignalException => e
exit if !ARGV.empty? || input 
STDERR.print "CTRL-D to exit" if !line
puts
rescue SystemStackError => e
STDERR.puts DynamicError.new("Stack Overflow Error\nYou could increase depth by changing shell variable (or by compiling to haskell todo):\nexport RUBY_THREAD_VM_STACK_SIZE=<size in bytes>", nil).message
rescue Errno::EPIPE
exit
rescue => e
STDERR.puts "!!!This is an internal Altas error, please report the bug (via github issue or email name of this lang at golfscript.com)!!!\n\n"
raise e
end
} 
end
if ARGV.delete("-l")
$line_mode = true
end
if ARGV.delete("-L")
$line_mode = false
end
repl
