     has_side_effects: has_side_effects
   return Vop
 
-class il.LogicOperation extends il.VirtualOperation
-class il.BinaryLogicOperation extends il.VirtualOperation
-class il.UnaryLogicOperation extends il.VirtualOperation
-
-Call_to_code = (compiler) ->  "(#{@args[0].to_code(compiler)})(#{join(', ', [x.to_code(compiler) for x in @args[1..]])})"
+il.AddAssign = (var1, value) ->  Assign(var1, BinaryOperationApply(add, [var1, value]))
+
+Call_to_code  = (compiler) ->  "(#{@args[0].to_code(compiler)})(#{join(', ', [x.to_code(compiler) for x in @args[1..]])})"
 il.Call = vop('Call', -1, Call_to_code, true)
 
 il.Attr = vop('Attr', 2, '%s.%s', false)
 
-AttrCall_to_code = (compiler) ->  "#{@args[0].to_code(compiler)}(#{join(', ', [x.to_code(compiler) for x in @args[1..]])})"
+AttrCall_to_code  = (compiler) ->  "#{@args[0].to_code(compiler)}(#{join(', ', [x.to_code(compiler) for x in @args[1..]])})"
 il.AttrCall = vop('AttrCall', -1, AttrCall_to_code, true)
 
 il.SetItem = vop2('SetItem', 3, '(%s)[%s] = %s', true)

  else if result.length is 1 then result[0]
  else il.Begin(result)

class il.Begin extends il.Element
  constructor: (@statements) ->
  find_assign_lefts: ( ) ->
    result = set()
    for exp in @statements
      result |= exp.find_assign_lefts()
    return result

  side_effects: ( ) ->  true
  subst: (bindings) ->  Begin((x.subst(bindings) for x in @statements))

  free_vars: ( ) -> set().mergeAt(exp.free_vars() for exp in @statements)

  code_size: ( ) ->  1
  analyse: (compiler) -> for x in @statements then x.analyse(compiler)

  optimize: (env, compiler) ->
    result = []
    for arg in @statements
      arg1 = arg.optimize(env, compiler)
      if arg1 isnt undefined
        result.push(arg1)
      if result
        return begin(((x for x in result[..-1] if not isinstance(x, Atom))+[result[-1]])...)
      else return_statement

  remove: (exp) ->
    for i, stmt in enumerate(@statements)
      if stmt is exp then break
      else return @
    return begin((@statements[..i]+@statements[i+1..])...)

  insert_return_statement: ( ) ->
    inserted = @statements[-1].insert_return_statement()
    return begin((@statements[..-1]+[inserted])...)

  replace_return_with_pyyield: ( ) ->  Begin((exp.replace_return_with_pyyield() for exp in @statements))

  javascriptize: (env, compiler) ->
    result = []
    for exp in @statements
      exps2 = exp.javascriptize(env, compiler)
      result += exps2
    return result

  to_code: (compiler) ->   '\n'.join([x.to_code(compiler) for x in @statements])
  __eq__: (x, y) ->  classeq(x, y) and x.statements==y.statements
  toString: ( ) ->  "il.begin(#{'\n '.join(x for x in @statements)})"


class il.PassStatement extends il.Element
  constructor: ( ) ->
  code_size: ( ) ->  0
  side_effects: ( ) ->  false
  analyse: (compiler) ->
  optimize: (env, compiler) -> @
  javascriptize: (env, compiler) ->  [@]
  insert_return_statement: ( ) ->  @
  replace_return_with_pyyield: ( ) ->  @
  subst: (bindings) ->  @
  __eq__: (x, y) ->  classeq(x, y)
  to_code: (compiler) ->  'pass'
  toString: ( ) ->  'il.pass_statement'

il.pass_statement = new il.PassStatement()

class il.Nil extends il.Element
  constructor: ( ) ->
  code_size: ( ) ->  0
  side_effects: ( ) ->  false
  analyse: (compiler) ->
  optimize: (env, compiler) ->  nil
  javascriptize: (env, compiler) ->  [@]
  insert_return_statement: ( ) ->  @
  replace_return_with_pyyield: ( ) ->  @
  subst: (bindings) ->  @
  __eq__: (x, y) ->  classeq(x, y)
  to_code: (compiler) ->  'nil'
  toString: ( ) ->  'il.nil'

il.nil = new il.Nil()

type_map = dict("int", il.Integer,    "float", il.Float, "str",il.String, "unicode", il.String,
                "Array",il.List, "dict",il.Dict,
                "bool",il.Bool,
                (typeof undefined), il.Atom
               )

il.lamda = (params, body...) ->  new il.Lamda(params, begin(body...))

class il.Lamda extends il.Element
  constructor: (@params, @body) -> @has_javascriptized = false
  make_new: (params, body) ->  new il.Lamda(params, body)
  callOn: (args...) ->  Apply(args)
  find_assign_lefts: ( ) ->  @body.find_assign_lefts()
  analyse: (compiler) ->
    compiler.lamda_stack.push(@)
    @body.analyse(compiler)
    compiler.lamda_stack.pop()
  code_size: ( ) ->  @body.code_size()+@params.length+2
  side_effects: ( ) ->  false

  subst: (bindings) ->
    result = @make_make_new(@params, @body.subst(bindings))
    return result

  optimize: (env, compiler) ->
    env = env.extend()
    body = @body.optimize(env, compiler)
    result = @make_make_new(@params, body)
    return result

  optimize_apply: (env, compiler, args) ->
    #1. ((lambda () body))  =>  body
    if @params.length==0
      return @body.optimize(env, compiler)

    #2. (lamda x: ...x...)(y) => (lambda : ... y ...)()
    bindings = {}
    [new_params, new_args] = [[], []]
    for i, p in enumerate(@params)
      arg = args[i]
      if arg.side_effects()
        new_params += [p]
        new_args += [arg]
        continue
      else
        ref_count = compiler.ref_count.get(p, 0)
      if ref_count==0 then continue
      else if ref_count==1
        bindings[p] = arg
      else
        if arg.code_size()*ref_count>MAX_EXTEND_CODE_SIZE
          # a(...y...), and a is (lamda ...x...: ...x...),
          #then convert as above if code size is ok.
          new_params += [p]
          new_args += [arg]
        else
          bindings[p] = arg

    if new_params
      if bindings
        return Apply(@make_make_new(new_params, @body.subst(bindings).optimize(env, compiler)),
                     (arg.optimize(env, compiler) for arg in new_args))
      else
        if new_params.length!=@params.length
          Apply(@make_make_new(new_params, @body.subst(bindings).optimize(env, compiler)),
                (arg.optimize(env, compiler) for arg in new_args))
        else  Apply(@make_make_new(new_params, @body.optimize(env, compiler)),
                    optimize_args(new_args, env, compiler))
    else
      if bindings then @body.subst(bindings).optimize(env, compiler)
      else  @body.optimize(env, compiler)

  insert_return_statement: () ->  new il.Return(@)

  javascriptize: (env, compiler) ->
    if @has_javascriptized then return [@name]
    @has_javascriptized = true
    body_exps = @body.javascriptize(env, compiler)
    [@make_make_new(@params, il.begin(body_exps...))]

  to_code: (compiler) -> "function (#{join(', ', (x.to_code(compiler) for x in @params))}) { "  + @body.to_code(compiler)+";}"

  free_vars: ( ) ->  @body.free_vars()-set(@params)
  bool: ( ) ->  true
  __eq__: (x, y) ->  classeq(x, y) and x.params==y.params and x.body==y.body
  __hash__: ( ) ->  hash(id(@))
  toString: ( ) ->  "il.Lamda((#{join(', ', x for x in @params)}), \n#{@body})"

class il.RulesLamda extends il.Lamda
  constructor: (@params, @body) ->  @has_javascriptized = false
  callOn: (args...) ->  Apply((element(x) for x in args))

  optimize_apply: (env, compiler, args) ->  Lamda.optimize_apply(env, compiler, args)

  to_code: (compiler) -> "lambda #{@params[0].to_code(compiler)}, #{@params[1].to_code(compiler)}: " + @body.to_code(compiler)

il.clamda = (v, body...) -> new il. Clamda(v, il.begin(body...))

class il.Clamda extends il.Lamda
  constructor: (v, @body) ->
    @has_javascriptized = false
    @params = [v]
    @name = undefined

  make_new: (params, body) ->  new @constructor(params[0], body)

  optimize_apply: (env, compiler, args) ->
    [param, arg] = [@params[0], args[0]]
    if not arg.side_effects()
      body = @body.subst({param: arg}).optimize(env, compiler)
      return body
    else
      ref_count = compiler.ref_count.get(param, 0)
      if ref_count==0 then begin(arg, @body).optimize(env, compiler)
      else begin(Assign(param, arg), @body).optimize(env, compiler)

  callOn: (arg) ->
    if arg.side_effects() then begin(Assign(@params[0], arg), @body)
    else
      bindings = {}
      bindings[@params[0]] = arg
      @body.subst(bindings)

  toString: ( ) ->  "il.Clamda(#{@params[0]}, \n#{@body})"

class il.EqualCont
  callOn: (body) ->  body
  subst: (bindings) ->  @
  analyse: (compiler) ->
  code_size: ( ) ->  1
  side_effects: ( ) ->  false
  optimize:  (env, compiler) ->  @
  javascriptize: (env, compiler) ->  [@]
  to_code: (compiler) ->  'lambda v:v'
  toString: ( ) ->  'EqualCont'

il.equal_cont = new il.EqualCont()

class il.Done extends il.Clamda
  constructor: (param) ->
    @has_javascriptized = false
    @params = [param]
    @body = param

  make_new: (params, body) ->  new @constructor(@params[0])
  callOn: (args...) ->
    bindings = {}
    bindings[@params[0]] = args[0]
    @body.subst(bindings)
  replace_assign: (bindings) ->  @
  toString: ( ) ->  "il.Done(#{@params[0]}, #{@body})"

class il.Function extends il.Lamda
  constructor: (@name, @params, @body) ->

il.cfunction = (name, v, body...) ->  new il.CFunction(name, v, begin(body...))

class il.CFunction extends il.Function
  is_fun: true
  constructor: (name, v, body) ->
  make_new: (params, body) ->  new @constructor(@name, params[0], body)
  optimize_apply: (env, compiler, args) ->
    new_env = env.extend()
    bindings = {}
    bindings[@params[0]] = args[0]
    body = @body.subst(bindings)
    body = body.optimize(new_env, compiler)
    result = CFunction(@name, @params[0], body)(il.NULL)
    return result
  toString: ( ) ->  "il.CFunction(#{@name}, #{@params[0]}, \n#{@body})"

class il.RulesDict extends il.Element
  constructor: (arity_body_map) ->
    @arity_body_map = arity_body_map
    @to_coded = false

  analyse: (compiler) ->
    try @seen  catch e then  @seen = true
    compiler.occur_count[@] = compiler.occur_count.setdefault(0)+1
    for arity, body in @arity_body_map.items()
      body.analyse(compiler)

  subst: (bindings) ->
    @arity_body_maparity_body_map = {arity:body.subst(bindings) for arity, body in @arity_body_map.items()}
    return @

  side_effects: ( ) ->  false
  free_vars: ( ) ->
    result = set()
    for arity, body in @arity_body_map.items()
      result |= body.free_vars()
    return result

  optimize: (env, compiler) ->  @
  javascriptize: (env, compiler) ->  [@]
  bool: ( ) ->  true

  to_code: (compiler) ->
    if @to_coded then  return @name.to_code(compiler)
    else
      @to_coded = true
      ss = "#{arity}: #{funcname.to_code(compiler)}" for arity, funcname in @arity_body_map.items()
      return "{#{join(', ', ss)}}"

  toString: ( ) ->  "RulesDict(#{@arity_body_map})"

class il.Macro

class il.MacroLamda extends il.Lamda #, Macro):
  optimize_apply: (env, compiler, args) ->
    #args = (args[0], Tuple(*args[1:]))
    result = Lamda.optimize_apply(env, compiler, args)
    return result

  javascriptize: (env, compiler) ->
    body_exps = @body.javascriptize(env, compiler)
    global_vars = @find_assign_lefts()-set(@params)
    global_vars = set(x for x in global_vars when isinstance(x, Var)  and not isinstance(x, LocalVar)  and not isinstance(x, SolverVar))
    if global_vars
      body_exps = [GlobalDecl(global_vars)]+body_exps
      return [MacroFunction(Lamda(@params, begin(body_exps...)))]

  toString: ( ) ->  "il.MacroLamda((#{join(', ', x for x in @params)}), \n#{@body})"

class il.MacroRules extends il.Lamda #, Macro):
  optimize_apply: (env, compiler, args) ->
    result = Lamda.optimize_apply(env, compiler, args)
    return result

  javascriptize: (env, compiler) ->
    body_exps = @body.javascriptize(env, compiler)
    global_vars = @find_assign_lefts()-set(@params)
    global_vars = set()
    for x in global_vars
      if isinstance(x, Var) and not isinstance(x, LocalVar)  and not isinstance(x, SolverVar)
        global_vars.add(x)
    if global_vars
      body_exps = [GlobalDecl(global_vars)]+body_exps
    if not body_has_any_statemen
      return [MacroRulesFunction(@make_make_new(@params, begin(body_exps...)))]
    else
    name = compiler.new_var(LocalVar('fun'))
    body = begin(body_exps...).insert_return_statement()
    return [new il.Function(name, @params, body), MacroRulesFunction(name)]

class il.MacroFunction extends il.Element
  constructor: (@fun) ->
  to_code: (compiler) ->  "MacroFunction(#{@fun.to_code(compiler)})"
  toString: ( ) ->  "MacroFunction(#{@fun})"

class il.MacroRulesFunction extends il.Element
  constructor: (@fun) ->
  to_code: (compiler) ->  "MacroRules(#{@fun})"
  toString: ( ) ->  "MacroRulesFunction(#{@fun})"

class il.GlobalDecl extends il.Element
  constructor: (@args) ->
  side_effects: ( ) ->  false
  to_code: (compiler) ->  "global #{join(', ', x.to_code(compiler) for x in @args)}"
  toString: ( ) ->  "GlobalDecl(#{@args})"

class il.Apply extends il.Element
  constructor: (@caller, @args) ->
  find_assign_lefts: (exp) ->  set()

  analyse: (compiler) ->
    compiler.called_count[@caller] = compiler.called_count.setdefault(@caller, 0)+1
    @caller.analyse(compiler)
    for arg in @args
      arg.analyse(compiler)

  code_size: ( ) ->  @caller.code_size()+sum([x.code_size() for x in @args])

  side_effects: ( ) ->
    if isinstance(@caller, Lamda)
      if @caller.body.side_effects() then  return true
      else if isinstance(@caller, Var) then  return true
      else if @caller.has_side_effects() then return true
      else return false # after cps, all of value have been solved before called,
  # so have no side effects.

  subst: (bindings) ->  new @constructor(@caller.subst(bindings),
                                         (arg.subst(bindings) for arg in @args))

  free_vars: ( ) ->
    result = @caller.free_vars()
    for exp in @args
      result |= exp.free_vars()
    return result

  optimize: (env, compiler) ->
    args = optimize_args(@args, env, compiler)
    if isinstance(@caller, Var)
      if @caller not in compiler.recursive_call_path
        caller = @caller.optimize(env, compiler)
        if isinstance(caller, Lamda)
          compiler.recursive_call_path.push(@caller)
          result = caller.optimize_apply(env, compiler, args)
          compiler.recursive_call_path.pop()
          return result
        else
          return new @constructor(caller, args)
      else
        return new @constructor(@caller, args)
    else if isinstance(@caller, Lamda)
      return @caller.optimize_apply(env, compiler, args)
    else
      caller = @caller.optimize(env, compiler)
      if isinstance(caller, Lamda)
        return caller.optimize_apply(env, compiler, args)
      else
        return new @constructor(caller, args)

  insert_return_statement: ( ) ->  Return(@)
  replace_return_with_pyyield: ( ) ->  @
  javascriptize: (env, compiler) ->
    exps = @caller.javascriptize(env, compiler)
    caller = exps[-1]
    exps = exps[..-1]
    exps2 = javascriptize_args(@args, env, compiler)
    return exps+exps2+[new @constructor(caller,args)]

  to_code: (compiler) ->
    if isinstance(@caller, Lamda)
      "(#{@caller.to_code(compiler)})(#{join(', ', x.to_code(compiler) for x in @args)})"
    else
      @caller.to_code(compiler) + "(#{join(', ', [x.to_code(compiler) for x in @args])})"

  bool: ( ) ->  unknown
  __eq__: (x, y) ->  classeq(x, y) and x.caller==y.caller and x.args==y.args
  toString: ( ) ->  "#{@caller}(#{join(', ', x for x in @args)})"

class il.ExpressionWithCode extends il.Element
  constructor: (@exp, @fun) ->
  analyse: (compiler) -> @fun.analyse(compiler)
  side_effects: ( ) ->  false
  subst: (bindings) ->  ExpressionWithCode(@exp, @fun.subst(bindings))
  code_size: ( ) ->  1
  free_vars: ( ) ->  @fun.free_vars()
  optimize: (env, compiler) ->  ExpressionWithCode(@exp, @fun.optimize(env, compiler))

  javascriptize: (env, compiler) ->
    exps = @fun.javascriptize(env, compiler)
    [ExpressionWithCode(@exp, exps[0])]

  __eq__: (x, y) ->  classeq(x, y) and x.exp==y.exp
  to_code: (compiler) ->  "ExpressionWithCode((#{@exp.to_code(compiler)}), (#{ @fun.to_code(compiler)}))"
  toString: ( ) ->  "ExpressionWithCode(#{@exp}, #{@fun})"

class il.Var extends il.Element
  constructor: (@name) ->
  find_assign_lefts: ( ) ->  set()
  analyse: (compiler) -> compiler.ref_count[@] = compiler.ref_count.setdefault(0)+1
  code_size: ( ) ->  1
  side_effects: ( ) ->  false
  subst: (bindings) ->
    try bindings[@]
    catch e then @
  optimize: (env, compiler) ->
    try env[@]
    catch e then  @
  replace_assign: (compiler) ->
    try env[@]
    catch e then @
  insert_return_statement: ( ) ->  Return(@)
  replace_return_with_pyyield: ( ) ->  @
  javascriptize: (env, compiler) ->  [@]
  to_code: (compiler) ->  @name
  __eq__: (x, y) ->  classeq(x, y) and x.name==y.name
  callOn: (args...) ->  Apply(args)
  free_vars: ( ) ->  set([@])
  bool: ( ) ->  unknown
  __hash__: ( ) ->  hash(@name)
  toString: ( ) ->  @name

class il.RecursiveVar extends il.Var
class il.LocalVar extends il.Var
class il.ConstLocalVar extends il.LocalVar

class il.SolverVar extends il.Var
  constructor: (name) -> @name = 'solver.'+name
  toString: ( ) ->  "il.#{@name.split('.')[1]}"

class il.LogicVar extends il.Element
  constructor: (@name) ->
  find_assign_lefts: (exp) ->  set()
  analyse: (compiler) ->
  subst: (bindings) ->  @
  free_vars: ( ) ->  set()
  side_effects: ( ) ->  false
  optimize: (env, compiler) ->  @
  replace_assign: (compiler) ->  @
  insert_return_statement: ( ) ->  Return(@)
  replace_return_with_pyyield: ( ) ->  @
  javascriptize: (env, compiler) -> [@]

  deref: (bindings) ->
    # todo:
    # how to shorten the binding chain? need to change solver.fail_cont.
    # deref(solver) can help
    x = @
    while 1
      next = bindings[x]
      if not isinstance(next, LogicVar) or next==x
        return next
      else  x = next

  to_code: (compiler) ->   "LogicVar('#{@name}')"
  toString: ( ) ->  "LogicVar(%s)"%@name

class il.DummyVar extends il.LogicVar
  to_code: (compiler) ->   "DummyVar('#{@name}')"

class il.Assign extends il.Element
  constructor: (@var1, @exp) ->
  find_assign_lefts: ( ) ->  set([@var1])
  analyse: (compiler) -> @exp.analyse(compiler)
  insert_return_statement: ( ) ->  begin(Return(@var1))
  code_size: ( ) ->  code_size(@exp)+2
  side_effects: ( ) ->  true
  subst: (bindings) ->  Assign(@var1, @exp.subst(bindings))
  free_vars: ( ) ->  @exp.free_vars()
  right_value: ( ) ->  @exp

  optimize: (env, compiler) ->
    exp = @exp.optimize(env, compiler)
    result = Assign(@var1, exp)
    if isinstance(@var1, ConstLocalVar)
      if (exp instanceof ConstAtom) or (exp instanceof Cons) or (exp instanceof ExpressionWithCode) or (exp instanceof Lamda)
        env[@var1] = exp
        return None
      else if (exp instanceof RulesDict)
        env[@var1] = exp
        exp.name = @var1
        return result
    return result

  javascriptize: (env, compiler) ->
    if not @var1.name.startswith('solver.')
      if isinstance(@exp, Function)
        @exp.name = @var1
      fun = @exp
    else if isinstance(@exp, Lamda) and not isinstance(@exp, MacroLamda)
      fun = Function(@var1, @exp.params, @exp.body)
    else
      fun = None
    if fun isnt None
      result = fun.javascriptize(env, compiler)
      if isinstance(result[0][-1], Var)
        result = [result[0][..-1], result[1]]
      return result
    exps = @exp.javascriptize(env, compiler)
    [exps[..-1]+[Assign(@var1, exps[-1])], true]

  to_code: (compiler) ->
    if isinstance(@exp, RulesDict) and @exp.to_coded then ''
    else "#{@var1.to_code(compiler)} = #{@exp.to_code(compiler)}"

  __eq__: (x, y) ->  classeq(x, y) and x.var1==y.var1 and x.exp==y.exp

  toString: ( ) ->  "#il.Assign(#{@var1}, #{@exp})"

class il.AssignFromList extends il.Element
  constructor: (args...) ->
    @vars = args[..-1]
    @value = args[-1]

  side_effects: ( ) ->  true

  analyse: (compiler) ->
    for var1 in @vars
      var1.analyse(compiler)
    @value.analyse(compiler)

  subst: (bindings) ->  AssignFromList(((var1.subst(bindings) for var1 in @vars)+[@value.subst(bindings)])...)

  code_size: ( ) ->  1

  free_vars: ( ) ->
    result = set(@vars)
    result |= @value.free_vars()
    return result

  optimize: (env, compiler) ->
    value = @value.optimize(env, compiler)
    if isinstance(value, Tuple) or isinstance(value, List)
      if @value.item.length!=@vars.length
        throw new dao.CompileError
      else
        for var1, v in zip(@vars, value.item)
          if isinstance(var1, ConstLocalVar)
            env[var1] = v
          else
            assigns.push(Assign(var1, v))
        if assigns
          return begin((Assign(var1, v))...)
        else return None
      return AssignFromList((@vars+[value])...)

  find_assign_lefts: ( ) ->  set(@vars)

  javascriptize: (env, compiler) ->
    value_exps = @value.javascriptize(env, compiler)
    return value_exps[..-1]+[AssignFromList((@vars+[value_exps[-1]])...)]

  insert_return_statement: ( ) ->  Return(@)
  replace_return_with_pyyield: ( ) ->  @
  bool: ( ) ->  false

  to_code: (compiler) ->  "#{join(', ', [x.to_code(compiler) for x in @vars])} = #{@value.to_code(compiler)}"

  toString: ( ) ->  "il.AssignFromList(#{@vars}, #{@value})"

if_ = (test, then_, else_) -> new il.If(element(test), element(then_), element(else_))

class il.If extends il.Element
  constructor: (@test, @then_, @else_) ->

  find_assign_lefts: ( ) ->  @then_.find_assign_lefts() | @else_.find_assign_lefts()

  analyse: (compiler) ->
    @test.analyse(compiler)
    @then_.analyse(compiler)
    @else_.analyse(compiler)

  code_size: ( ) ->  3 + @test.code_size() + @then_.code_size() + @else_.code_size()

  side_effects: ( ) ->  not (@test.side_effects() or @then_.side_effects()  or  @else_.side_effects())

  subst: (bindings) ->  new il.If(@test.subst(bindings),  @then_.subst(bindings),  @else_.subst(bindings))

  free_vars: ( ) -> @test.free_vars().mergeAt(@then_.free_vars(), @else_.free_vars())

  optimize: (env, compiler) ->
    test = @test.optimize(env, compiler)
    test_bool = test.bool()
    if test_bool==true
      then_ = @then_.optimize(env, compiler)
    if isinstance(then_, il.If) and then_.test==test # (if a (if a b c) d)
      return then_.then_
    else if test_bool==false
      else_ = @else_.optimize(env, compiler)
    if isinstance(else_, il.If) and else_.test==test # (if a b (if a c d))
      return else_.else_
    then_ = @then_.optimize(env, compiler)
    else_ = @else_.optimize(env, compiler)
    if isinstance(then_, il.If) and then_.test==test # (if a (if a b c) d)
      then_ = then_.then_
    if isinstance(else_, il.If) and else_.test==test # (if a b (if a c d))
      else_ = else_.else_
    new il.If(test, then_, else_)

  insert_return_statement: ( ) ->  new il.If(@test,  @then_.insert_return_statement()  @else_.insert_return_statement())
  replace_return_with_pyyield: ( ) -> new il.If(@test, @then_.replace_return_with_pyyield(),  @else_.replace_return_with_pyyield())

  javascriptize: (env, compiler) ->
    test = @test.javascriptize(env, compiler)
    then_ = @then_.javascriptize(env, compiler)
    else_ = @else_.javascriptize(env, compiler)
    if_ = new il.If(test[test.length-1], il.begin(then_...), il.begin(else_...))
    test[...test.length-1].concat([if_])

  to_code: (compiler) ->
    result = "if #{@test.to_code(compiler)}: \n#{compiler.indent(@then_.to_code(compiler))}\n"
    if @else_ isnt il.pseudo_else
      result += "else\n#{compiler.indent(@else_.to_code(compiler))}\n"
    result
#        "(#{@then_.to_code(compiler)} if #{@test.to_code(compiler)} \nelse #{ @else_.to_code(compiler)})"
  __eq__: (x, y) ->  classeq(x, y) and x.test==y.test and x.then==y.then and x.else_==y.else_

  toString: ( ) ->
    if @else_!=il.pseudo_else then  "il.If(#{@test}, \n#{@then_}, \n#{@else_})"
    else  "il.If(#{@test}, \n#{@then_})"

il.if2 = (test, then_) ->  new il.If(test, then_, il.pseudo_else)

class il.PseudoElse extends il.ConstAtom
  constructor: ( ) ->
  code_size: ( ) ->  0
  insert_return_statement: ( ) ->  @
  replace_return_with_pyyield: ( ) ->  @
  to_code: (compiler) ->  ''
  __eq__: (x, y) ->  classeq(x, y)
  toString: ( ) ->  'il.pseudo_else'

il.pseudo_else = new il.PseudoElse()

class il.Cons extends il.ConstAtom
  constructor: (@head, @tail) ->
  code_size: ( ) ->  1
  insert_return_statement: ( ) ->  @
  replace_return_with_pyyield: ( ) ->  @
  to_code: (compiler) ->  "Cons(#{@head.to_code(compiler)}, #{@tail.to_code(compiler)})"
  __eq__: (x, y) ->  classeq(x, y) and x.head==y.head and x.tail==y.tail
  toString: ( ) ->  "il.Cons(#{@head}, #{@tail})"

il.while_ = (test, exps...) -> new il.While(test, begin([x for x in exps]...))

class il.While extends il.Element
  constructor: (@test, @body) ->
  find_assign_lefts: ( ) ->  @body.find_assign_lefts()
  analyse: (compiler) ->
    @test.analyse(compiler)
    @body.analyse(compiler)
  free_vars: ( ) ->  @test.free_vars() | @body.free_vars()
  code_size: ( ) ->  3 + @test.code_size() +@body.code_size()
  side_effects: ( ) ->  not @test.side_effects() and not @body.side_effects()
  subst: (bindings) ->  While(@test.subst(bindings), @body.subst(bindings))

  optimize: (env, compiler) ->
    free_vars = @free_vars()
    test = @test.optimize(env, compiler)
    body = @body.optimize(env, compiler)
    result = While(test,body)
    return result

  insert_return_statement: ( ) ->
    While(@test, @body.insert_return_statement())

  replace_return_with_pyyield: ( ) ->
    While(@test  @body.replace_return_with_pyyield())

  javascriptize: (env, compiler) ->
    test = @test.javascriptize(env, compiler)
    body = @body.javascriptize(env, compiler)
    test[..-1]+[While(test[-1], begin(body...))]

  to_code: (compiler) ->  "while #{@test.to_code(compiler)}:\n#{compiler.indent(@body.to_code(compiler))}\n"

  __eq__: (x, y) ->  classeq(x, y) and x.test==y.test and x.body==y.body
  toString: ( ) ->  "il.While(#{@test}, \n#{@body})"

il.for_ = (var1, range, exps...) -> new il.For(element(var1), element(range), begin([x for x in exps]...))

class il.For extends il.Element
  constructor: (@var1, @range, @body) ->
  find_assign_lefts: ( ) ->  @body.find_assign_lefts()
  analyse:(compiler) ->
    @var1.analyse(compiler)
    @range.analyse(compiler)
    @body.analyse(compiler)

  code_size: ( ) ->  3 + @var1.code_size() + @range.code_size() + @body.code_size()
  side_effects: ( ) ->  not @var1.side_effects() and not @range.side_effects() and not @body.side_effects()
  subst: (bindings) ->  For(@var1.subst(bindings), @range.subst(bindings), @body.subst(bindings))
  free_vars: ( ) ->  @var1.free_vars() | @range.free_vars() | @body.free_vars()
  optimize: (env, compiler) ->
    free_vars = @free_vars()
    assigns = []
    for var1 in free_vars
      value = env[var1]
      if value is undefined then continue
      assigns.push(Assign(var1, value))
      del env[var1]
    return begin(((assigns) + [For(@var1, @range.optimize(env, compiler), @body.optimize(env, compiler))])...)

  insert_return_statement: ( ) ->  For(@var1, @range, @body.insert_return_statement())
  replace_return_with_pyyield: ( ) ->  For(@var1, @range, @body.replace_return_with_pyyield())

  javascriptize: (env, compiler) ->
    var1 = @var1.javascriptize(env, compiler)
    range = @range.javascriptize(env, compiler)
    body = @body.javascriptize(env, compiler)
    return [For(var1[-1], range[-1], begin(body...))]

  to_code: (compiler) ->  "for #{@var1.to_code(compiler)} in #{@range.to_code(compiler)}:\n#{compiler.indent(@body.to_code(compiler))}\n"
  __eq__: (x, y) ->  classeq(x, y) and x.var1==y.var1 and x.range==y.range and x.body==y.body
  toString: ( ) ->  "il.For(#{@var1}, #{@range}, #{@body})"

class il.BinaryOperation extends il.Element
  constructor: (@name, @operator, @has_side_effects=true) ->
  analyse: (compiler) ->  @
  subst: (bindings) ->  @
  optimize: (env, compiler) ->  @
  code_size: ( ) ->  1
  javascriptize: (env, compiler) ->  [@]
  to_code: (compiler) ->  @operator
  callOn: (args...) ->  BinaryOperationApply(args)
  __eq__: (x, y) ->  classeq(x, y) and x.operator==y.operator
  __hash__: ( ) ->  hash(@operator)
  toString: ( ) ->  "il.#{@name}"

class il.BinaryOperationApply extends il.Apply
  constructor: (@caller, @args) ->

  analyse: (compiler) ->
    compiler.called_count[@caller] = compiler.called_count.setdefault(@caller, 0)+1
    @caller.analyse(compiler)
    arg.analyse(compiler) for arg in @args

  code_size: ( ) ->  @caller.code_size()+sum([x.code_size() for x in @args])

  side_effects: ( ) ->
    if isinstance(@caller, Var)  then true
    else if @caller.has_side_effects then return true
    else  return false # after cps, all of value have been solved before called,
  # so have no side effects.

  subst: (bindings) ->  new @constructor(@caller.subst(bindings), (arg.subst(bindings) for arg in @args))

  optimize: (env, compiler) ->
    caller = @caller
    args = optimize_args(@args, env, compiler)
    for arg in args
      if not isinstance(arg, Atom) then  break
      else element(caller.operator_fun((arg.item for arg in args))...)
    return new @constructor(caller, args)

  insert_return_statement: ( ) ->  new il.Return(@)

  javascriptize: (env, compiler) ->
    [exps, args] = javascriptize_args(@args, env, compiler)
    exps+[new @constructor(@caller, args)]

  free_vars: ( ) -> set().mergeAt(arg.free_vars() for arg in @args)

  to_code: (compiler) ->
    if not @caller.operator[0].isalpha()
      return "(#{@args[0].to_code(compiler)})#{ @caller.to_code(compiler)}(#{@args[1].to_code(compiler)})"
    else"(#{@args[0].to_code(compiler)}) #{@caller.to_code(compiler)} (#{ @args[1].to_code(compiler)})"

  toString: ( ) ->  "#{@caller}(#{join(', ', arg for arg in @args)})"

il.addop = new il.BinaryOperation('add', '+', false)
il.subop = new il.BinaryOperation('sub', '-', false)

      result.push(arg)
  (result)

javascriptize_args  = (args, env, compiler) ->
  # used in Apply, Return, Yield, VirtualOpteration
  result_args = []
  exps = []
