# Begin statements

begin
end

begin end

begin
    foo
    bar
    baz
end

==>
Program(
  BeginStatement(begin, end),
  BeginStatement(begin, end),
  BeginStatement(begin, Identifier, Identifier, Identifier, end)
)


# Quote statements

quote end

quote
  x
  y
end

==>
Program(
  QuoteStatement(quote, end),
  QuoteStatement(quote, Identifier, Identifier, end)
)


# While statements

while true
end

while i < 5
  print(i)
  continue
  break
end

while check(x) update!(x) end

# while check(); end # BROKEN

==>
Program(
  WhileStatement(while, Condition(BoolLiteral), end),
  WhileStatement(
    while,
    Condition(BinaryExpression(Identifier, ComparisonOp, IntegerLiteral)),
    CallExpression(Identifier, Arguments(Identifier)),
    ContinueStatement(continue),
    BreakStatement(break),
    end
  ),
  WhileStatement(
    while,
    Condition(CallExpression(Identifier, Arguments(Identifier))),
    CallExpression(Identifier, Arguments(Identifier)),
    end
  ),
  LineComment,
)


# Let statements

let
end

let var1 = value1, var2, var3 = value3
    code
end

let (x, y) = tuple
    x
end

==>
Program(
  LetStatement(let, end),
  LetStatement(
    let,
    LetBinding(Assignment(Identifier, AssignmentOp, Identifier)),
    LetBinding(Identifier)
    LetBinding(Assignment(Identifier, AssignmentOp, Identifier)),
    Identifier,
    end
  ),
  LetStatement(
    let,
    LetBinding(Assignment(TupleExpression(Identifier, Identifier), AssignmentOp, Identifier)),
    Identifier,
    end,
  )
)


# Let statements - No bindings

let
    code
end

==>
Program(
  LetStatement(
    let,
    Identifier,
    end
  )
)


# Let statements - Single-line

let end
let x end
let x = 1 end
let a = 1; a end
let; 123 end
let
end

==>
Program(
  LetStatement(let, end),
  LetStatement(let, LetBinding(Identifier), end),
  LetStatement(let, LetBinding(Assignment(Identifier, AssignmentOp, IntegerLiteral)), end),
  LetStatement(let, LetBinding(Assignment(Identifier, AssignmentOp, IntegerLiteral)), Identifier, end),
  LetStatement(let, IntegerLiteral, end),
  LetStatement(let, end),
)




# For statements

for x in xs end

for x in xs foo(x) end

for i in [1, 2, 3]
  println(i)
end

for (a, b) in cs
  println(a, b)
end

==>
Program(
  ForStatement(
    for,
    ForBinding(Identifier, AssignmentOp(in), Identifier),
    end
  ),
  ForStatement(
    for,
    ForBinding(Identifier, AssignmentOp(in), Identifier),
    CallExpression(Identifier, Arguments(Identifier)),
    end
  ),
  ForStatement(
    for,
    ForBinding(Identifier, AssignmentOp(in), VectorExpression(IntegerLiteral, IntegerLiteral, IntegerLiteral)),
    CallExpression(Identifier,Arguments(Identifier)),
    end
  ),
  ForStatement(
    for,
    ForBinding(TupleExpression(Identifier, Identifier), AssignmentOp(in), Identifier),
    CallExpression(Identifier, Arguments(Identifier, Identifier)),
    end
  )
)


# For outer statements

n = 1
for outer n = range
  body
end

for outer x = iter, outer y = iter
  body
end

==>
Program(
  Assignment(Identifier, AssignmentOp, IntegerLiteral),
  ForStatement(
    for,
    ForBinding(
      outer,
      Identifier,
      AssignmentOp,
      Identifier,
    )
    Identifier,
    end,
  )
  ForStatement(
    for,
    ForBinding(
      outer,
      Identifier,
      AssignmentOp,
      Identifier,
    )
    ForBinding(
      outer,
      Identifier,
      AssignmentOp,
      Identifier,
    )
    Identifier,
    end,
  )
)


# If statements

if a
elseif b
else
end

if true 1 end
if true 1 else 0 end

if a
  b()
elseif c 
  d()
  d()
else
  e()
end

==>
Program(
  IfStatement(
    if,
    Condition(Identifier),
    ElseIfClause(elseif, Condition(Identifier)),
    ElseClause(else),
    end
  )
  IfStatement(
    if,
    Condition(BoolLiteral),
    IntegerLiteral,
    end
  )
  IfStatement(
    if,
    Condition(BoolLiteral),
    IntegerLiteral,
    ElseClause(else, IntegerLiteral),
    end
  )

  IfStatement(
    if,
    Condition(Identifier),
    CallExpression(Identifier, Arguments),
    ElseIfClause(
      elseif,
      Condition(Identifier),
      CallExpression(Identifier, Arguments),
      CallExpression(Identifier, Arguments)
    ),
    ElseClause(
      else,
      CallExpression(Identifier, Arguments)
    ),
    end
  )
)


# Try statements

try
    result = fallible_func(resource)
catch exception
    log(:err, exception)
else
    log(:ok, result)
finally
    close(resource)
end

try
catch e
 e
end

try catch e e end
try catch e end
try catch end
try finally end

try
catch e_with_a_space_behind_it 
end

try
catch e_with_a_space_behind_it 
  body
end

==>
Program(
  TryStatement(
    try
    Assignment(Identifier AssignmentOp CallExpression(Identifier Arguments(Identifier))),
    CatchClause(
      catch
      ExceptionCapture(Identifier)
      CallExpression(Identifier Arguments(Symbol(Identifier) Identifier))
    )
    ElseClause(
      else
      CallExpression(Identifier Arguments(Symbol(Identifier) Identifier))
    )
    FinallyClause(
      finally
      CallExpression(Identifier Arguments(Identifier))
    )
    end
  )
  TryStatement(
    try
    CatchClause(
      catch
      ExceptionCapture(Identifier)
      Identifier
    )
    end
  )
  
  TryStatement(
    try
    CatchClause(
      catch
      ExceptionCapture(Identifier)
      Identifier
    )
    end
  )
  
  TryStatement(
    try
    CatchClause(
      catch
      ExceptionCapture(Identifier)
    )
    end
  )
  
  TryStatement(
    try
    CatchClause(
      catch
    )
    end
  )
  
  TryStatement(
    try
    FinallyClause(
      finally
    )
    end
  )

  TryStatement(
    try
    CatchClause(
      catch
      ExceptionCapture(Identifier)
    )
    end
  )

  TryStatement(
    try
    CatchClause(
      catch
      ExceptionCapture(Identifier)
      Identifier
    )
    end
  )


)


# Return statements

return
return a
return a || b
return a, b, c

==>
Program(
  ReturnStatement(return),
  ReturnStatement(return, Identifier),
  ReturnStatement(return, BinaryExpression(Identifier, LazyOrOp, Identifier)),
  ReturnStatement(return, OpenTuple(Identifier, Identifier, Identifier))
)


# Export statements

export a
export a, b, +, (*)
public a
public a, b, +, (*)

==>
Program(
  ExportStatement(export, Identifier),
  ExportStatement(export, Identifier, Identifier, Operator(UnaryPlusOp), ParenExpression(Operator(TimesOp))),
  PublicStatement(public, Identifier),
  PublicStatement(public, Identifier, Identifier, Operator(UnaryPlusOp), ParenExpression(Operator(TimesOp))),
)


# Export statements - Macro identifiers

export @macroMcAtface
export somename, @andamacroname, andsomethingelse

==>
Program(
  ExportStatement(export, MacroIdentifier(Identifier)),
  ExportStatement(export, Identifier, MacroIdentifier(Identifier), Identifier),
)


# Import statements

using Lib
using A, B, C
using ..Foo, ..Bar

import Base.show

using BigLib: thing1, thing2
import .A: @b
import Module: ==, +

==>
Program(
  UsingStatement(using, ImportPath(Identifier)),
  UsingStatement(using, ImportPath(Identifier), ImportPath(Identifier), ImportPath(Identifier)),
  UsingStatement(using, ImportPath(EllipsisOp, Identifier), ImportPath(EllipsisOp, Identifier), ),

  ImportStatement(import, ImportPath(Identifier, Identifier)),
  UsingStatement(using, SelectedImport(ImportPath(Identifier), ImportPath(Identifier), ImportPath(Identifier))),
  ImportStatement(import, SelectedImport(ImportPath(Identifier), MacroIdentifier(Identifier))),
  ImportStatement(import, SelectedImport(ImportPath(Identifier), Operator(ComparisonOp), Operator(UnaryPlusOp)))
)


# Import statements - relative import dots and the `..` operator - issue #30

import ...A
import ....A.b
using Intervals: ..
import Base: .. as dd

==>
Program(
  ImportStatement(import, ImportPath(Identifier)),
  ImportStatement(import, ImportPath(Identifier, Identifier)),
  UsingStatement(using, SelectedImport(ImportPath(Identifier), Operator(EllipsisOp))),
  ImportStatement(import, SelectedImport(ImportPath(Identifier), ImportAlias(Operator(EllipsisOp), as, Identifier))),
)


# Import statements - Import aliases

import A as B
import A.B as C
import CSV: a as b

## Selected + Aliases
import A: a as b, b as a, c
using A: a as c

## More Aliases
import Pkg: Artifacts.archive_artifacts as aa
import Base: + as -
using Printf: @sprintf as @format

==>
Program(
  ImportStatement(import, ImportAlias(ImportPath(Identifier), as, Identifier)),
  ImportStatement(import, ImportAlias(ImportPath(Identifier, Identifier), as, Identifier)),
  ImportStatement(import, SelectedImport(ImportPath(Identifier), ImportAlias(ImportPath(Identifier), as, Identifier))),
  LineComment,
  ImportStatement(import, SelectedImport(ImportPath(Identifier), ImportAlias(ImportPath(Identifier), as, Identifier), ImportAlias(ImportPath(Identifier), as, Identifier), ImportPath(Identifier)))
  UsingStatement(using, SelectedImport(ImportPath(Identifier), ImportAlias(ImportPath(Identifier), as, Identifier))),
  LineComment,
  ImportStatement(import, SelectedImport(ImportPath(Identifier), ImportAlias(ImportPath(Identifier, Identifier), as, Identifier))),
  ImportStatement(import, SelectedImport(ImportPath(Identifier), ImportAlias(Operator(UnaryPlusOp), as, Operator(UnaryPlusOp)))),
  UsingStatement(using, SelectedImport(ImportPath(Identifier), ImportAlias(MacroIdentifier(Identifier), as, MacroIdentifier(Identifier))))
)


# Const statements

const x = 5
const x = 1, 2
const y, z = 3, 4

(0; const x, y = 1, 2)

==>
Program(
  ConstStatement(const, Assignment(Identifier, AssignmentOp, IntegerLiteral)),
  ConstStatement(const, Assignment(Identifier, AssignmentOp, OpenTuple(IntegerLiteral, IntegerLiteral))),
  ConstStatement(const, Assignment(OpenTuple(Identifier, Identifier), AssignmentOp, OpenTuple(IntegerLiteral, IntegerLiteral))),
  ParenExpression(
    IntegerLiteral,
    ConstStatement(const, Assignment(OpenTuple(Identifier, Identifier), AssignmentOp, OpenTuple(IntegerLiteral, IntegerLiteral))),
  )
)


# Const statements - typed const and const struct fields (Julia 1.8+)

const x::Int = 1

mutable struct Event
    const notify::Int
    const autoreset
    set::Bool
end

==>
Program(
  ConstStatement(const, Assignment(BinaryExpression(Identifier, SubTypeOp, Type(Identifier)), AssignmentOp, IntegerLiteral)),
  StructDefinition(
    mutable, struct, TypeHead(Identifier),
    ConstStatement(const, BinaryExpression(Identifier, SubTypeOp, Type(Identifier))),
    ConstStatement(const, Identifier),
    BinaryExpression(Identifier, SubTypeOp, Type(Identifier)),
    end
  )
)


# Public used as an identifier (soft keyword)

public a, b
public
x = public
f(public)
@public x

==>
Program(
  PublicStatement(public, Identifier, Identifier),
  Identifier,
  Assignment(Identifier, AssignmentOp, Identifier),
  CallExpression(Identifier, Arguments(Identifier)),
  MacrocallExpression(MacroIdentifier(Identifier), MacroArguments(Identifier))
)


# Local statements

local x
local y, z = 1, 2

local foo() = 3
local function bar() 4 end

==>
Program(
  LocalStatement(local, Identifier),
  LocalStatement(local, Assignment(OpenTuple(Identifier, Identifier), AssignmentOp, OpenTuple(IntegerLiteral, IntegerLiteral))),

  LocalStatement(local, Assignment(CallExpression(Identifier, Arguments), AssignmentOp, IntegerLiteral)),
  LocalStatement(local, FunctionDefinition(function, Signature(CallExpression(Identifier, Arguments)), IntegerLiteral, end)),
)


# Global statements

global X
global Y, Z = 11, 42
global foo() = 3
global function bar() 4 end

==>
Program(
  GlobalStatement(global, Identifier),
  GlobalStatement(global, Assignment(OpenTuple(Identifier, Identifier), AssignmentOp, OpenTuple(IntegerLiteral, IntegerLiteral))),

  GlobalStatement(global, Assignment(CallExpression(Identifier, Arguments), AssignmentOp, IntegerLiteral)),
  GlobalStatement(global, FunctionDefinition(function, Signature(CallExpression(Identifier, Arguments)), IntegerLiteral, end)),
)


# Trailing space before for loop - issue #36

function f()
  x()
  for i in z
  end
end

==>
Program(
  FunctionDefinition(
    function,
    Signature(CallExpression(Identifier, Arguments)),
    CallExpression(Identifier, Arguments),
    ForStatement(
      for,
      ForBinding(Identifier, AssignmentOp(in), Identifier),
      end
    ),
    end
  )
)


# Trailing space before for loop - issue #36 - with trailing space

function f()
  x() 
  for i in z
  end
end

==>
Program(
  FunctionDefinition(
    function,
    Signature(CallExpression(Identifier, Arguments)),
    CallExpression(Identifier, Arguments),
    ForStatement(
      for,
      ForBinding(Identifier, AssignmentOp(in), Identifier),
      end
    ),
    end
  )
)


# Trailing space - exact issue #36 example

@model function single_source_model()
    MvNormal() 
    for i in z
    end
end

==>
Program(
  MacrocallExpression(
    MacroIdentifier(Identifier),
    MacroArguments(
      FunctionDefinition(
        function,
        Signature(CallExpression(Identifier, Arguments)),
        CallExpression(Identifier, Arguments),
        ForStatement(
          for,
          ForBinding(Identifier, AssignmentOp(in), Identifier),
          end
        ),
        end
      )
    )
  )
)


# Trailing space - multiple statements in begin block

begin
  x 
  y
  z
end

==>
Program(
  BeginStatement(
    begin,
    Identifier,
    Identifier,
    Identifier,
    end
  )
)
