formatting

formatting

require 'cupoftea'
cg = require './codeGenerator/codeGenerator'
require './assertions.pogo'

int (n) =
  cg: integer (n)

loc = {first line 1, last line 1, first column 7, last column 13}

id (name) = cg: loc (cg: identifier (name), loc)

variable (name) = cg: variable [name]

parameter (name) = cg: parameter [name]

block = cg: block [] (cg: statements [variable 'x'])

string (value) = cg: string (value)

no arg punctuation = cg: no arg suffix?

expression (e) should contain fields (f) =
  (cg: complex expression (e): expression?) should contain fields (f)

spec 'complex expression'
  spec 'has arguments'
    expression (e) should have arguments =
      (cg: complex expression (e): has arguments?) should be truthy

    spec 'with arguments in head'
      expression [[id 'a', int 10]] should have arguments

    spec 'with no arg arguments'
      expression [[id 'a', no arg punctuation]] should have arguments

    spec 'with tail block'
      expression [[id 'a'], [id 'readonly', block]] should have arguments

  spec 'arguments'
    expression (e) should have arguments (a) =
      (cg: complex expression (e): arguments?) should contain fields (a)

    spec 'with arguments in head'
      expression [[id 'a', int 10]] should have arguments [{integer 10}]

    spec 'with tail block'
      expression [[id 'a'], [id 'readonly', block]] should have arguments [
        {is block}
      ]

  spec 'expression'
    spec 'with just one argument is that argument'
      expression [[int 9]] should contain fields {
        is integer
        integer 9
      }

    spec 'all arguments is function call, first argument is function'
      expression [[variable 'z', int 9]] should contain fields {
        is function call
        function {variable ['z']}
        arguments [{integer 9}]
      }

    spec 'one argument and call punctuation is function call'
      expression [[variable 'z', no arg punctuation]] should contain fields {
        is function call
        function {variable ['z']}
        arguments []
      }

    spec 'with name is variable'
      expression [[id 'a', id 'variable']] should contain fields {
        is variable
        variable ['a', 'variable']
      }

    spec 'with name and argument is function call'
      expression [[id 'a', id 'variable', int 10]] should contain fields {
        is function call
        function {is variable, variable ['a', 'variable']}
        arguments [{integer 10}]
      }

    spec 'finds macro'
      expression [[id 'if', variable 'x', block]] should contain fields {
        is if expression
      }

    spec 'with name and optional args is function call with optional args'
      expression [[id 'a', id 'variable'], [id 'port', int 80]] should contain fields {
        is function call
        function {is variable, variable ['a', 'variable']}
        arguments []
        optional arguments [{field ['port'], value {integer 80}}]
      }

    spec 'with block after optional arguments'
      expression [[id 'a', id 'variable'], [id 'port', int 80, block]] should contain fields {
        is function call
        function {is variable, variable ['a', 'variable']}
        arguments [
            {
                is block
                body {
                  statements [
                    {variable ['x']}
                  ]
                }
            }
        ]
        optional arguments [{field ['port'], value {integer 80}}]
      }

  spec 'object operation -> expression'
    expression (object, operation) should contain fields (fields) =
      (cg: complex expression (operation): object operation (object): expression?) should contain fields (fields)

    spec 'method call'
      expression (variable 'a') [[id 'method', int 10]] should contain fields {
        is method call
        object {variable ['a']}
        name ['method']
        arguments [{integer 10}]
      }

    spec 'method call with optional arguments'
      expression (variable 'a') [[id 'method', int 10], [id 'port', int 80]] should contain fields {
        is method call
        object {variable ['a']}
        name ['method']
        arguments [{integer 10}]
        optional arguments [{field ['port'], value {integer 80}}]
      }

    spec 'index'
      expression (variable 'a') [[int 10]] should contain fields {
        is indexer
        object {variable ['a']}
        indexer {integer 10}
      }

    spec 'index call with arguments'
      expression (variable 'a') [[variable 'z', int 10]] should contain fields {
        is function call
        function {
          is indexer
          object {variable ['a']}
          indexer {variable ['z']}
        }

        arguments [{integer 10}]
      }

    spec 'index call with no arguments'
      expression (variable 'a') [[variable 'z', no arg punctuation]] should contain fields {
        is function call
        function {
          is indexer
          object {variable ['a']}
          indexer {variable ['z']}
        }

        arguments []
      }

    spec 'field reference'
      expression (variable 'a') [[id 'field']] should contain fields {
        is field reference
        object {variable ['a']}
        name ['field']
      }

  spec 'hash entry'
    hash entry (expression) should contain fields (fields) =
      (cg: complex expression (expression): hash entry?) should contain fields (fields)

    spec 'if contains one component that is the hash entry'
      hash entry [[id 'field']] should contain fields {
        is hash entry
        field ['field']
        value = undefined
      }

    spec 'if contains more than component then semantic error'
      hash entry [[id 'field'], [id 'secondField']] should contain fields {
        is semantic failure
      }

  spec 'definition -> hash entry'
    spec 'string key'
      hash entry = cg: complex expression [[string 'port']]: definition (cg: variable ['a']): hash entry?

      (hashEntry) should contain fields {
        is hash entry
        field ['port']
        value {variable ['a']}
      }

    spec 'identifier key'
      hash entry = cg: complex expression [[id 'port']]: definition (cg: variable ['a']): hash entry?

      (hashEntry) should contain fields {
        is hash entry
        field ['port']
        value {variable ['a']}
      }

    spec 'can define a method as a hash key'
      hash entry = cg: complex expression [[id 'name', variable 'name']]: definition (cg: variable ['name']): hash entry?

      (hashEntry) should contain fields {
        is hash entry
        field ['name']
        value {
            is block

            body {
                statements [
                    {variable ['name']}
                ]
            }

            parameters [{variable ['name']}]

            redefines self
        }
      }

  spec 'object operation -> definition'
    definition (object, operation, source) should contain fields (fields) =
      (cg: complex expression (operation): object operation (object): definition (source): expression?) should contain fields (fields)

    spec 'method definition'
      definition (variable 'object') [[id 'method', variable 'x']] (block) should contain fields {
        is definition
        target {
          is field reference
          name ['method']
          object {variable ['object']}
        }

        source {
          is block
          parameters [{variable ['x']}]
        }
      }

    spec 'method definition without block'
      definition (variable 'object') [[id 'method', variable 'x']] (variable 'y') should contain fields {
        is definition
        target {
          is field reference
          name ['method']
          object {variable ['object']}
        }

        source {
          is block
          redefines self
          parameters [{variable ['x']}]
          body {statements [{variable ['y']}]}
        }
      }

    spec 'field definition'
      definition (variable 'object') [[id 'x']] (variable 'y') should contain fields {
        is definition
        target {
          is field reference
          name ['x']
          object {variable ['object']}
        }

        source {
          is variable
          variable ['y']
        }
      }

    spec 'index definition'
      definition (variable 'object') [[variable 'x']] (variable 'y') should contain fields {
        is definition
        target {
          is indexer
          indexer {variable ['x']}
          object {variable ['object']}
        }

        source {
          is variable
          variable ['y']
        }
      }

    spec 'index method definition'
      definition (variable 'object') [[cg: string 'xyz', variable 'p']] (variable 'y') should contain fields {
        is definition
        target {
          is indexer
          indexer {string 'xyz'}
          object {variable ['object']}
        }

        source {
          is block
          body {
            statements [
              {
                is variable
                variable ['y']
              }
            ]
          }

          parameters [
            {variable ['p']}
          ]
        }
      }

    spec 'index method definition with no args'
      definition (variable 'object') [[cg: string 'xyz', no arg punctuation]] (variable 'y') should contain fields {
        is definition
        target {
          is indexer
          indexer {string 'xyz'}
          object {variable ['object']}
        }

        source {
          is block
          body {
            statements [
              {
                is variable
                variable ['y']
              }
            ]
          }

          parameters [
          ]
        }
      }

  spec 'definition'
    definition (target, source) should contain fields (fields) =
      (cg: complex expression (target): definition (source): expression?) should contain fields (fields)

    spec 'function definition'
      definition [[id 'function', variable 'x']] (block) should contain fields {
        is definition
        target {
          is variable
          variable ['function']
        }

        source {
          is block
          parameters [{variable ['x']}]
          body {statements [{variable ['x']}]}
        }
      }

    spec 'function definition with optional parameter'
      definition [[id 'function', variable 'x'], [id 'port', int 80], [id 'name']] (variable 'y') should contain fields {
        is definition
        target {
          is variable
          variable ['function']
        }

        source {
          is block
          parameters [{variable ['x']}]
          optional parameters [
            {field ['port'], value {integer 80}}
            {field ['name'], value = undefined}
          ]
          body {statements [{variable ['y']}]}
        }
      }

    spec 'function definition without block'
      definition [[id 'function', variable 'x']] (variable 'y') should contain fields {
        is definition
        target {
          is variable
          variable ['function']
        }

        source {
          is block
          parameters [{variable ['x']}]
          body {statements [{variable ['y']}]}
        }
      }

    spec 'no arg function definition'
      definition [[id 'function', no arg punctuation]] (variable 'y') should contain fields {
        is definition
        target {
          is variable
          variable ['function']
        }

        source {
          is block
          parameters []
          body {statements [{variable ['y']}]}
        }
      }

    spec 'function definition with empty param list'
      definition [[id 'function', cg: argument list []]] (variable 'y') should contain fields {
        is definition
        target {
          is variable
          variable ['function']
        }

        source {
          is block
          parameters []
          body {statements [{variable ['y']}]}
        }
      }

    spec 'variable definition'
      definition [[id 'function']] (variable 'y') should contain fields {
        is definition
        target {
          is variable
          variable ['function']
        }

        source {
          is variable
          variable ['y']
        }
      }

    spec 'variable definition with scope'
      definition [[id 'function']] (cg: block [] (cg: statements [variable 'y'])) should contain fields {
        is definition
        target {
          is variable
          variable ['function']
        }

        source {
          is function call
          function {
            is block
            parameters []
            body {
              statements [{variable ['y']}]
            }
          }

          arguments []
        }
      }

    spec 'parameter'
        parameter (p) should contain fields (fields) =
            (cg: complex expression (p): expression? : parameter?) should contain fields (fields)

        spec 'variable'
            parameter [[id 'a']] should contain fields {variable ['a']}

require 'cupoftea'
cg = require './codeGenerator/codeGenerator'
require './assertions.pogo'

int (n) =
  cg: integer (n)

loc = {first line 1. last line 1. first column 7. last column 13}

id (name) = cg: loc (cg: identifier (name), loc)

variable (name) = cg: variable [name]

parameter (name) = cg: parameter [name]

block = cg: block [] (cg: statements [variable 'x'])

string (value) = cg: string (value)

no arg punctuation = cg: no arg suffix?

expression (e) should contain fields (f) =
  (cg: complex expression (e): expression?) should contain fields (f)

spec 'complex expression'
  spec 'has arguments'
    expression (e) should have arguments =
      (cg: complex expression (e): has arguments?) should be truthy

    spec 'with arguments in head'
      expression [[id 'a'. int 10]] should have arguments

    spec 'with no arg arguments'
      expression [[id 'a'. no arg punctuation]] should have arguments

    spec 'with tail block'
      expression [[id 'a']. [id 'readonly'. block]] should have arguments

  spec 'arguments'
    expression (e) should have arguments (a) =
      (cg: complex expression (e): arguments?) should contain fields (a)

    spec 'with arguments in head'
      expression [[id 'a'. int 10]] should have arguments [{integer 10}]

    spec 'with tail block'
      expression [[id 'a']. [id 'readonly'. block]] should have arguments [
        {is block}
      ]

  spec 'expression'
    spec 'with just one argument is that argument'
      expression [[int 9]] should contain fields {
        is integer
        integer 9
      }

    spec 'all arguments is function call, first argument is function'
      expression [[variable 'z'. int 9]] should contain fields {
        is function call
        function {variable ['z']}
        arguments [{integer 9}]
      }

    spec 'one argument and call punctuation is function call'
      expression [[variable 'z'. no arg punctuation]] should contain fields {
        is function call
        function {variable ['z']}
        arguments []
      }

    spec 'with name is variable'
      expression [[id 'a'. id 'variable']] should contain fields {
        is variable
        variable ['a'. 'variable']
      }

    spec 'with name and argument is function call'
      expression [[id 'a'. id 'variable'. int 10]] should contain fields {
        is function call
        function {is variable. variable ['a'. 'variable']}
        arguments [{integer 10}]
      }

    spec 'finds macro'
      expression [[id 'if'. variable 'x'. block]] should contain fields {
        is if expression
      }

    spec 'with name and optional args is function call with optional args'
      expression [[id 'a'. id 'variable']. [id 'port'. int 80]] should contain fields {
        is function call
        function {is variable. variable ['a'. 'variable']}
        arguments []
        optional arguments [{field ['port']. value {integer 80}}]
      }

    spec 'with block after optional arguments'
      expression [[id 'a'. id 'variable']. [id 'port'. int 80. block]] should contain fields {
        is function call
        function {is variable. variable ['a'. 'variable']}
        arguments [
            {
                is block
                body {
                  statements [
                    {variable ['x']}
                  ]
                }
            }
        ]
        optional arguments [{field ['port']. value {integer 80}}]
      }

  spec 'object operation -> expression'
    expression (object, operation) should contain fields (fields) =
      (cg: complex expression (operation): object operation (object): expression?) should contain fields (fields)

    spec 'method call'
      expression (variable 'a') [[id 'method'. int 10]] should contain fields {
        is method call
        object {variable ['a']}
        name ['method']
        arguments [{integer 10}]
      }

    spec 'method call with optional arguments'
      expression (variable 'a') [[id 'method'. int 10]. [id 'port'. int 80]] should contain fields {
        is method call
        object {variable ['a']}
        name ['method']
        arguments [{integer 10}]
        optional arguments [{field ['port']. value {integer 80}}]
      }

    spec 'index'
      expression (variable 'a') [[int 10]] should contain fields {
        is indexer
        object {variable ['a']}
        indexer {integer 10}
      }

    spec 'index call with arguments'
      expression (variable 'a') [[variable 'z'. int 10]] should contain fields {
        is function call
        function {
          is indexer
          object {variable ['a']}
          indexer {variable ['z']}
        }

        arguments [{integer 10}]
      }

    spec 'index call with no arguments'
      expression (variable 'a') [[variable 'z'. no arg punctuation]] should contain fields {
        is function call
        function {
          is indexer
          object {variable ['a']}
          indexer {variable ['z']}
        }

        arguments []
      }

    spec 'field reference'
      expression (variable 'a') [[id 'field']] should contain fields {
        is field reference
        object {variable ['a']}
        name ['field']
      }

  spec 'hash entry'
    hash entry (expression) should contain fields (fields) =
      (cg: complex expression (expression): hash entry?) should contain fields (fields)

    spec 'if contains one component that is the hash entry'
      hash entry [[id 'field']] should contain fields {
        is hash entry
        field ['field']
        value = undefined
      }

    spec 'if contains more than component then semantic error'
      hash entry [[id 'field']. [id 'secondField']] should contain fields {
        is semantic failure
      }

  spec 'definition -> hash entry'
    spec 'string key'
      hash entry = cg: complex expression [[string 'port']]: definition (cg: variable ['a']): hash entry?

      (hashEntry) should contain fields {
        is hash entry
        field ['port']
        value {variable ['a']}
      }

    spec 'identifier key'
      hash entry = cg: complex expression [[id 'port']]: definition (cg: variable ['a']): hash entry?

      (hashEntry) should contain fields {
        is hash entry
        field ['port']
        value {variable ['a']}
      }

    spec 'can define a method as a hash key'
      hash entry = cg: complex expression [[id 'name'. variable 'name']]: definition (cg: variable ['name']): hash entry?

      (hashEntry) should contain fields {
        is hash entry
        field ['name']
        value {
            is block

            body {
                statements [
                    {variable ['name']}
                ]
            }

            parameters [{variable ['name']}]

            redefines self
        }
      }

  spec 'object operation -> definition'
    definition (object, operation, source) should contain fields (fields) =
      (cg: complex expression (operation): object operation (object): definition (source): expression?) should contain fields (fields)

    spec 'method definition'
      definition (variable 'object') [[id 'method'. variable 'x']] (block) should contain fields {
        is definition
        target {
          is field reference
          name ['method']
          object {variable ['object']}
        }

        source {
          is block
          parameters [{variable ['x']}]
        }
      }

    spec 'method definition without block'
      definition (variable 'object') [[id 'method'. variable 'x']] (variable 'y') should contain fields {
        is definition
        target {
          is field reference
          name ['method']
          object {variable ['object']}
        }

        source {
          is block
          redefines self
          parameters [{variable ['x']}]
          body {statements [{variable ['y']}]}
        }
      }

    spec 'field definition'
      definition (variable 'object') [[id 'x']] (variable 'y') should contain fields {
        is definition
        target {
          is field reference
          name ['x']
          object {variable ['object']}
        }

        source {
          is variable
          variable ['y']
        }
      }

    spec 'index definition'
      definition (variable 'object') [[variable 'x']] (variable 'y') should contain fields {
        is definition
        target {
          is indexer
          indexer {variable ['x']}
          object {variable ['object']}
        }

        source {
          is variable
          variable ['y']
        }
      }

    spec 'index method definition'
      definition (variable 'object') [[cg: string 'xyz'. variable 'p']] (variable 'y') should contain fields {
        is definition
        target {
          is indexer
          indexer {string 'xyz'}
          object {variable ['object']}
        }

        source {
          is block
          body {
            statements [
              {
                is variable
                variable ['y']
              }
            ]
          }

          parameters [
            {variable ['p']}
          ]
        }
      }

    spec 'index method definition with no args'
      definition (variable 'object') [[cg: string 'xyz'. no arg punctuation]] (variable 'y') should contain fields {
        is definition
        target {
          is indexer
          indexer {string 'xyz'}
          object {variable ['object']}
        }

        source {
          is block
          body {
            statements [
              {
                is variable
                variable ['y']
              }
            ]
          }

          parameters [
          ]
        }
      }

  spec 'definition'
    definition (target, source) should contain fields (fields) =
      (cg: complex expression (target): definition (source): expression?) should contain fields (fields)

    spec 'function definition'
      definition [[id 'function'. variable 'x']] (block) should contain fields {
        is definition
        target {
          is variable
          variable ['function']
        }

        source {
          is block
          parameters [{variable ['x']}]
          body {statements [{variable ['x']}]}
        }
      }

    spec 'function definition with optional parameter'
      definition [[id 'function'. variable 'x']. [id 'port'. int 80]. [id 'name']] (variable 'y') should contain fields {
        is definition
        target {
          is variable
          variable ['function']
        }

        source {
          is block
          parameters [{variable ['x']}]
          optional parameters [
            {field ['port']. value {integer 80}}
            {field ['name']. value = undefined}
          ]
          body {statements [{variable ['y']}]}
        }
      }

    spec 'function definition without block'
      definition [[id 'function'. variable 'x']] (variable 'y') should contain fields {
        is definition
        target {
          is variable
          variable ['function']
        }

        source {
          is block
          parameters [{variable ['x']}]
          body {statements [{variable ['y']}]}
        }
      }

    spec 'no arg function definition'
      definition [[id 'function'. no arg punctuation]] (variable 'y') should contain fields {
        is definition
        target {
          is variable
          variable ['function']
        }

        source {
          is block
          parameters []
          body {statements [{variable ['y']}]}
        }
      }

    spec 'function definition with empty param list'
      definition [[id 'function'. cg: argument list []]] (variable 'y') should contain fields {
        is definition
        target {
          is variable
          variable ['function']
        }

        source {
          is block
          parameters []
          body {statements [{variable ['y']}]}
        }
      }

    spec 'variable definition'
      definition [[id 'function']] (variable 'y') should contain fields {
        is definition
        target {
          is variable
          variable ['function']
        }

        source {
          is variable
          variable ['y']
        }
      }

    spec 'variable definition with scope'
      definition [[id 'function']] (cg: block [] (cg: statements [variable 'y'])) should contain fields {
        is definition
        target {
          is variable
          variable ['function']
        }

        source {
          is function call
          function {
            is block
            parameters []
            body {
              statements [{variable ['y']}]
            }
          }

          arguments []
        }
      }

    spec 'parameter'
        parameter (p) should contain fields (fields) =
            (cg: complex expression (p): expression? : parameter?) should contain fields (fields)

        spec 'variable'
            parameter [[id 'a']] should contain fields {variable ['a']}


test for $

test for $

require 'cupoftea'
require './assertions.pogo'

parser = require './parser.pogo'
parse = parser: parse

assume @term is module with statements @action =
    if (term: is module)
        action (term: statements)
    else
        throw (new (Error ('expected module, but found ' + term)))

assume @statements has just one statement @action =
    if (statements: statements: length == 1)
        action (statements: statements: 0)
    else
        throw (new (Error ('expected statements to have just one statement, found ' + statements: statements: length)))

expression @source =
    assume (statements @source) has just one statement #statement
        statement

statements @source =
    term = parse @source
    assume @term is module with statements #statements
        statements

spec 'parser'
    spec 'terminals'
        spec 'integer'
            (expression '5') should contain fields {
                integer 5
            }

        spec 'float'
            (expression '5.6') should contain fields {
                float 5.6
            }

        spec 'variables'
            spec 'simple'
                (expression 'total weight') should contain fields {
                    variable ['total'. 'weight']
                }

            spec '$'
                (expression '$') should contain fields {
                    variable ['$']
                }

        spec 'strings'
            spec 'simple string'
                (expression '''a string''') should contain fields {
                    is string
                    string 'a string'
                }

            spec 'string with single quotes'
                (expression '''''''alright!'''' he said''') should contain fields {
                    is string
                    string '''alright!'' he said'
                }

            spec 'string with backslash'
                (expression "'one \\ two'") should contain fields {
                    is string
                    string "one \\ two"
                }

            spec 'multiline string'
                (expression "  'one\n   two'") should contain fields {
                    is string
                    string "one\ntwo"
                }

        spec 'interpolated strings'
            spec 'simple'
                (expression '"a string"') should contain fields {
                    is string
                    string 'a string'
                }

            spec 'with newline'
                (expression '"one\ntwo"') should contain fields {
                    is interpolated string
                    components [
                        {string "one\ntwo"}
                    ]
                }

            spec 'with indentation'
                (expression '"one\n  two"') should contain fields {
                    is interpolated string
                    components [
                        {string "one\n two"}
                    ]
                }

            spec 'indented string'
                (expression '  "one\n   two"') should contain fields {
                    is interpolated string
                    components [
                        {string "one\ntwo"}
                    ]
                }

            spec 'null string'
                (expression '""') should contain fields {
                    is interpolated string
                    components [
                    ]
                }

            spec 'with single identifier variable'
                (expression '"a boat @length meters in length"') should contain fields {
                    is interpolated string
                    components [
                        {string 'a boat '}
                        {variable ['length']}
                        {string ' meters in length'}
                    ]
                }

            spec 'with single variable expression'
                (expression '"a boat @(boat length) meters in length"') should contain fields {
                    is interpolated string
                    components [
                        {string 'a boat '}
                        {variable ['boat'. 'length']}
                        {string ' meters in length'}
                    ]
                }

            spec 'with complex expression'
                (expression '"a boat @(lookup boat length from (boat database)) meters in length"') should contain fields {
                    is interpolated string
                    components [
                        {string 'a boat '}
                        {
                            function {variable ['lookup'. 'boat'. 'length'. 'from']}
                            arguments [{variable ['boat'. 'database']}]
                        }
                        {string ' meters in length'}
                    ]
                }

            spec 'in block'
                (expression "abc =\n    \"\@(stuff)\"") should contain fields {
                    is definition
                    target {
                        is variable
                        variable ['abc']
                    }
                    source {
                        is function call
                        function {
                            is block
                            body {
                                statements [{
                                    is variable
                                    variable ['stuff']
                                }]
                            }
                        }
                    }
                }

            spec 'with inner interpolation'
                (expression '"a boat @("@(boat length) meters") in length"') should contain fields {
                    is interpolated string
                    components [
                        {string 'a boat '}
                        {
                            is interpolated string
                            components [
                                {variable ['boat'. 'length']}
                                {string ' meters'}
                            ]
                        }
                        {string ' in length'}
                    ]
                }

        spec 'lists'
            spec 'empty'
                (expression '[]') should contain fields {
                    is list
                    items []
                }

            spec 'one item'
                (expression '[1]') should contain fields {
                    is list
                    items [{integer 1}]
                }

            spec 'two items'
                (expression '[1. 2]') should contain fields {
                    is list
                    items [
                        {integer 1}
                        {integer 2}
                    ]
                }

        spec 'hashes'
            spec 'empty hash'
                (expression '{}') should contain fields {
                    is hash
                    entries []
                }

            spec 'hash with one entry'
                (expression '{port 1234}') should contain fields {
                    is hash
                    entries [
                        {
                            field ['port']
                            value {integer 1234}
                        }
                    ]
                }

            spec 'hash with two entries'
                (expression '{port 1234. ip address ''1.1.1.1''}') should contain fields {
                    is hash
                    entries [
                        {
                            field ['port']
                            value {integer 1234}
                        }
                        {
                            field ['ip'. 'address']
                            value {string '1.1.1.1'}
                        }
                    ]
                }

            spec 'hash with two entries on different lines'
                (expression "{port = 1234\nip address = '1.1.1.1'}") should contain fields {
                    is hash
                    entries [
                        {
                            field ['port']
                            value {integer 1234}
                        }
                        {
                            field ['ip'. 'address']
                            value {string '1.1.1.1'}
                        }
                    ]
                }

            spec 'hash with string with assignment'
                (expression "{'port' = 1234}") should contain fields {
                    is hash
                    entries [
                        {
                            field ['port']
                            value {integer 1234}
                        }
                    ]
                }

            spec 'should allow methods to be defined, redefining self'
                (expression '{say hi to @name, greeting = print @name}') should contain fields {
                    is hash
                    entries [
                        {
                            field ['say'. 'hi'. 'to']
                            value {
                                is block
                                redefines self

                                body {
                                    statements [{
                                        is function call

                                        function {variable ['print']}
                                    }]
                                }

                                parameters [{
                                    is parameter
                                    expression {variable ['name']}
                                }]

                                optional parameters [{
                                    is hash entry
                                    field ['greeting']
                                }]
                            }
                        }
                    ]
                }

            spec 'hash with true entry'
                (expression '{port 1234. readonly}') should contain fields {
                    is hash
                    entries [
                        {
                            field ['port']
                            value {integer 1234}
                        }
                        {
                            field ['readonly']
                            value @undefined
                        }
                    ]
                }

    spec 'function calls'
        spec 'function call'
            (expression 'touch @ file') should contain fields {
                function {variable ['touch']}
                arguments [{variable ['file']}]
            }

        spec 'function call with self argument'
            (expression 'touch @: file') should contain fields {
                function {variable ['touch']}
                arguments [
                    {
                        is field reference
                        object {variable ['self']}
                        name ['file']
                    }
                ]
            }

        spec 'function call with splat argument'
            (expression 'touch @files ...') should contain fields {
                function {variable ['touch']}
                arguments [
                  {variable ['files']}
                  {is splat}
                ]
            }

        spec 'function call with no argument'
            (expression 'delete everything!') should contain fields {
                function {variable ['delete'. 'everything']}
                arguments []
            }

        spec 'function call with block with parameters'
            (expression "with file \@file #stream\n  stream") should contain fields {
                function {variable ['with'. 'file']}
                arguments [
                    {variable ['file']}
                    {
                        body {statements [{variable ['stream']}]}
                        parameters [{
                            is parameter
                            expression {variable ['stream']}
                        }]
                    }
                ]
            }

        spec 'function call with block with long parameters'
            (expression "open database #(database connection)\n  database connection") should contain fields {
                function {variable ['open'. 'database']}
                arguments [
                    {
                        parameters [
                            {
                                is parameter
                                expression {variable ['database'. 'connection']}
                            }
                        ]
                        body {statements [{variable ['database'. 'connection']}]}
                    }
                ]
            }

        spec 'function call with two blocks with parameters'
            (expression 'name #x @{x} #y @ {y}') should contain fields {
                function {variable ['name']}
                arguments [
                    {
                        body {statements [{variable ['x']}]}
                        parameters [{
                            is parameter
                            expression {variable ['x']}
                        }]
                    }
                    {
                        body {statements [{variable ['y']}]}
                        parameters [{
                            is parameter
                            expression {variable ['y']}
                        }]
                    }
                ]
            }

        spec 'function call with two optional arguments'
            (expression 'name @a, port 34, server @s') should contain fields {
                function {variable ['name']}
                arguments [
                    {variable ['a']}
                ]
                optional arguments [
                    {
                        field ['port']
                        value {integer 34}
                    }
                    {
                        field ['server']
                        value {variable ['s']}
                    }
                ]
            }

        spec 'function call with no arguments and one optional argument'
            (expression 'start server, port 34') should contain fields {
                function {variable ['start'. 'server']}
                arguments []
                optional arguments [
                    {
                        field ['port']
                        value {integer 34}
                    }
                ]
            }

    spec 'object operations'
        spec 'method call'
            (expression 'object: method @argument') should contain fields {
                is method call
                object {variable ['object']}
                name ['method']
                arguments [{variable ['argument']}]
            }

        spec 'method call with optional arguments'
            (expression 'object: method @argument, view @view') should contain fields {
                is method call
                object {variable ['object']}
                name ['method']
                arguments [{variable ['argument']}]
                optional arguments [
                    {field ['view']. value {variable ['view']}}
                ]
            }

        spec 'field reference'
            (expression 'object: field') should contain fields {
                is field reference
                object {variable ['object']}
                name ['field']
            }

        spec 'field reference with newline'
            (expression "object:\nfield") should contain fields {
                is field reference
                object {variable ['object']}
                name ['field']
            }

        spec 'self field reference'
            (expression ':field') should contain fields {
                is field reference
                object {variable ['self']}
                name ['field']
            }

        spec 'indexer'
            (expression 'object: @x') should contain fields {
                is indexer
                object {variable ['object']}
                indexer {variable ['x']}
            }

    spec 'blocks'
        spec 'empty block'
            (expression '@{}') should contain fields {
                is block
                parameters []
                redefines self @false
                body {statements []}
            }

        spec 'block'
            (expression '@{x.y}') should contain fields {
                is block
                parameters []
                redefines self @false
                body {statements [
                    {variable ['x']}
                    {variable ['y']}
                ]}
            }

        spec 'block with parameter'
            (expression "#x\n  x.y") should contain fields {
                is block
                parameters [{is parameter. expression {variable ['x']}}]
                redefines self @false
                body {
                    statements [
                        {variable ['x']}
                        {variable ['y']}
                    ]
                }
            }

        spec 'block with parameter, redefining self'
            (expression '#x => @{x.y}') should contain fields {
                is block
                parameters [{is parameter. expression {variable ['x']}}]
                redefines self @true
                body {
                    statements [
                        {variable ['x']}
                        {variable ['y']}
                    ]
                }
            }

    spec 'operators'
        spec 'should be lower precedence than object operation'
            (expression 'o:m 2 +- o:x') should contain fields {
                is method call
                object {
                    is method call
                    object {variable ['o']}
                    name ['m']
                    arguments [{integer 2}]
                }

                name ['+-']
                arguments [
                    {
                        is field reference
                        object {variable ['o']}
                        name ['x']
                    }
                ]
            }

        spec 'parses backslash'
            (expression "2 +\\+ 1") should contain fields {
                is method call
                object {integer 2}

                name ["+\\+"]
                arguments [
                    {integer 1}
                ]
            }

        spec 'unary operators should be higher precedence than binary operators'
            (expression 'a && ! b') should contain fields {
                is operator
                operator '&&'

                arguments [
                    {variable ['a']}
                    {
                        is operator
                        operator '!'
                        arguments [{variable ['b']}]
                    }
                ]
            }

        spec 'can have newlines immediately after operator'
            (expression "a &&\nb") should contain fields {
                is operator
                operator '&&'

                arguments [
                    {variable ['a']}
                    {variable ['b']}
                ]
            }

    spec 'assignment'
        spec 'assignment'
            (expression 'x = y') should contain fields {
                is definition
                target {variable ['x']}
                source {variable ['y']}
            }

        spec 'function definition'
            spec 'function with one parameter'
                (expression 'func @x = x') should contain fields {
                    is definition
                    target {variable ['func']}
                    source {
                        parameters [{is parameter. expression {variable ['x']}}]
                        body {statements [{variable ['x']}]}
                    }
                }

            spec 'function with one parameter, and one optional parameter'
                (expression 'func @x, port 80 = x') should contain fields {
                    is definition
                    target {variable ['func']}
                    source {
                        parameters [{is parameter. expression {variable ['x']}}]
                        optional parameters [{field ['port']. value {integer 80}}]
                        body {statements [{variable ['x']}]}
                    }
                }

        spec 'field assignment'
            (expression 'o: x = y') should contain fields {
                is definition
                target {
                    is field reference
                    object {variable ['o']}
                    name ['x']
                }

                source {variable ['y']}
            }

        spec 'index assignment'
            (expression 'o: @x = y') should contain fields {
                is definition
                target {
                    is indexer
                    object {variable ['o']}
                    indexer {variable ['x']}
                }

                source {variable ['y']}
            }

        spec 'assignment from field'
            (expression 'x = y: z') should contain fields {
                is definition
                target {variable ['x']}
                source {
                    is field reference
                    object {
                        variable ['y']
                    }

                    name ['z']
                }
            }

        spec 'assignment of command'
            (expression 'x! = 8') should contain fields {
                is definition
                target {variable ['x']}
                source {
                    is block
                    parameters []
                    body {
                        statements [{integer 8}]
                    }
                }
            }

        spec 'assignment of query'
            (expression 'x? = 8') should contain fields {
                is definition
                target {variable ['x']}
                source {
                    is block
                    parameters []
                    body {
                        statements [{integer 8}]
                    }
                }
            }

        spec 'assignment from method call'
            (expression 'x = y: z @a') should contain fields {
                is definition
                target {variable ['x']}
                source {
                    is method call
                    object {
                        variable ['y']
                    }

                    name ['z']
                    arguments [{variable ['a']}]
                }
            }

        spec 'field assignment from method call'
            (expression 'i: x = y: z @a') should contain fields {
                is definition
                target {
                    is field reference
                    object {variable ['i']}
                    name ['x']
                }

                source {
                    is method call
                    object {
                        variable ['y']
                    }

                    name ['z']
                    arguments [{variable ['a']}]
                }
            }

    spec 'regexps'
        spec 'simple'
            (expression '`abc`') should contain fields {
                is reg exp
                pattern 'abc'
            }

        spec 'with options'
            (expression '`abc`img') should contain fields {
                is reg exp
                pattern 'abc'
                options 'img'
            }

        spec 'with escaped back ticks'
            (expression '`abc\`def\`ghi`') should contain fields {
                is reg exp
                pattern 'abc`def`ghi'
            }

        spec 'with newline'
            (expression "a = `abc\n     def`") should contain fields {
                is definition
                target {
                    is variable
                    variable ['a']
                }
                source {
                    is reg exp
                    pattern "abc\\ndef"
                }
            }

    spec 'comments'
        spec 'should allow one-line C++ style comments, as in: // this is a comment'
            spec 'when between lines'
                (statements "a // this is a comment\nb") should contain fields {
                    is statements
                    statements [
                        {variable ['a']}
                        {variable ['b']}
                    ]
                }

            spec 'when before an indented block'
                (statements "a // this is a comment\n  b") should contain fields {
                    is statements
                    statements [{
                        is function call
                        function {variable ['a']}
                        arguments [{
                            is block
                            body {
                                statements [
                                    {variable ['b']}
                                ]
                            }
                        }]
                    }]
                }

            spec 'when at end of file'
                (statements "a // this is a comment") should contain fields {
                    is statements
                    statements [
                        {variable ['a']}
                    ]
                }

        spec 'should allow multi-line C style comments, as in: /* this is a comment */'
            spec 'when on one line'
                (statements "a /* comment */ b") should contain fields {
                    statements [
                        {variable ['a'. 'b']}
                    ]
                }

            spec 'when there are two'
                (statements "a /* comment */ b /* another comment */ c") should contain fields {
                    statements [
                        {variable ['a'. 'b'. 'c']}
                    ]
                }

            spec 'when it contains a * character'
                (statements "a /* sh*t */ b") should contain fields {
                    statements [
                        {variable ['a'. 'b']}
                    ]
                }

            spec 'when it covers two lines'
                (statements "a /* line one\nline two */ b") should contain fields {
                    statements [
                        {variable ['a'. 'b']}
                    ]
                }

            spec 'when it extends to the end of the file'
                (statements "a /* comment to eof") should contain fields {
                    statements [
                        {variable ['a']}
                    ]
                }

    spec 'lexer'
        tokens = parser: lex 'a b'
        @tokens should contain fields [
            ['a'. 'identifier'. 51]
            ['b'. 'identifier'. 51]
            [''. undefined. 'eof']
        ]

require 'cupoftea'
require './assertions.pogo'

parser = require './parser.pogo'
parse = parser: parse

assume @term is module with statements @action =
    if (term: is module)
        action (term: statements)
    else
        throw (new (Error ('expected module, but found ' + term)))

assume @statements has just one statement @action =
    if (statements: statements: length == 1)
        action (statements: statements: 0)
    else
        throw (new (Error ('expected statements to have just one statement, found ' + statements: statements: length)))

expression @source =
    assume (statements @source) has just one statement #statement
        statement

statements @source =
    term = parse @source
    assume @term is module with statements #statements
        statements

spec 'parser'
    spec 'terminals'
        spec 'integer'
            (expression '5') should contain fields {
                integer 5
            }

        spec 'float'
            (expression '5.6') should contain fields {
                float 5.6
            }

        spec 'variables'
            spec 'simple'
                (expression 'total weight') should contain fields {
                    variable ['total'. 'weight']
                }

        spec 'strings'
            spec 'simple string'
                (expression '''a string''') should contain fields {
                    is string
                    string 'a string'
                }

            spec 'string with single quotes'
                (expression '''''''alright!'''' he said''') should contain fields {
                    is string
                    string '''alright!'' he said'
                }

            spec 'string with backslash'
                (expression "'one \\ two'") should contain fields {
                    is string
                    string "one \\ two"
                }

            spec 'multiline string'
                (expression "  'one\n   two'") should contain fields {
                    is string
                    string "one\ntwo"
                }

        spec 'interpolated strings'
            spec 'simple'
                (expression '"a string"') should contain fields {
                    is string
                    string 'a string'
                }

            spec 'with newline'
                (expression '"one\ntwo"') should contain fields {
                    is interpolated string
                    components [
                        {string "one\ntwo"}
                    ]
                }

            spec 'with indentation'
                (expression '"one\n  two"') should contain fields {
                    is interpolated string
                    components [
                        {string "one\n two"}
                    ]
                }

            spec 'indented string'
                (expression '  "one\n   two"') should contain fields {
                    is interpolated string
                    components [
                        {string "one\ntwo"}
                    ]
                }

            spec 'null string'
                (expression '""') should contain fields {
                    is interpolated string
                    components [
                    ]
                }

            spec 'with single identifier variable'
                (expression '"a boat @length meters in length"') should contain fields {
                    is interpolated string
                    components [
                        {string 'a boat '}
                        {variable ['length']}
                        {string ' meters in length'}
                    ]
                }

            spec 'with single variable expression'
                (expression '"a boat @(boat length) meters in length"') should contain fields {
                    is interpolated string
                    components [
                        {string 'a boat '}
                        {variable ['boat'. 'length']}
                        {string ' meters in length'}
                    ]
                }

            spec 'with complex expression'
                (expression '"a boat @(lookup boat length from (boat database)) meters in length"') should contain fields {
                    is interpolated string
                    components [
                        {string 'a boat '}
                        {
                            function {variable ['lookup'. 'boat'. 'length'. 'from']}
                            arguments [{variable ['boat'. 'database']}]
                        }
                        {string ' meters in length'}
                    ]
                }

            spec 'in block'
                (expression "abc =\n    \"\@(stuff)\"") should contain fields {
                    is definition
                    target {
                        is variable
                        variable ['abc']
                    }
                    source {
                        is function call
                        function {
                            is block
                            body {
                                statements [{
                                    is variable
                                    variable ['stuff']
                                }]
                            }
                        }
                    }
                }

            spec 'with inner interpolation'
                (expression '"a boat @("@(boat length) meters") in length"') should contain fields {
                    is interpolated string
                    components [
                        {string 'a boat '}
                        {
                            is interpolated string
                            components [
                                {variable ['boat'. 'length']}
                                {string ' meters'}
                            ]
                        }
                        {string ' in length'}
                    ]
                }

        spec 'lists'
            spec 'empty'
                (expression '[]') should contain fields {
                    is list
                    items []
                }

            spec 'one item'
                (expression '[1]') should contain fields {
                    is list
                    items [{integer 1}]
                }

            spec 'two items'
                (expression '[1. 2]') should contain fields {
                    is list
                    items [
                        {integer 1}
                        {integer 2}
                    ]
                }

        spec 'hashes'
            spec 'empty hash'
                (expression '{}') should contain fields {
                    is hash
                    entries []
                }

            spec 'hash with one entry'
                (expression '{port 1234}') should contain fields {
                    is hash
                    entries [
                        {
                            field ['port']
                            value {integer 1234}
                        }
                    ]
                }

            spec 'hash with two entries'
                (expression '{port 1234. ip address ''1.1.1.1''}') should contain fields {
                    is hash
                    entries [
                        {
                            field ['port']
                            value {integer 1234}
                        }
                        {
                            field ['ip'. 'address']
                            value {string '1.1.1.1'}
                        }
                    ]
                }

            spec 'hash with two entries on different lines'
                (expression "{port = 1234\nip address = '1.1.1.1'}") should contain fields {
                    is hash
                    entries [
                        {
                            field ['port']
                            value {integer 1234}
                        }
                        {
                            field ['ip'. 'address']
                            value {string '1.1.1.1'}
                        }
                    ]
                }

            spec 'hash with string with assignment'
                (expression "{'port' = 1234}") should contain fields {
                    is hash
                    entries [
                        {
                            field ['port']
                            value {integer 1234}
                        }
                    ]
                }

            spec 'should allow methods to be defined, redefining self'
                (expression '{say hi to @name, greeting = print @name}') should contain fields {
                    is hash
                    entries [
                        {
                            field ['say'. 'hi'. 'to']
                            value {
                                is block
                                redefines self

                                body {
                                    statements [{
                                        is function call

                                        function {variable ['print']}
                                    }]
                                }

                                parameters [{
                                    is parameter
                                    expression {variable ['name']}
                                }]

                                optional parameters [{
                                    is hash entry
                                    field ['greeting']
                                }]
                            }
                        }
                    ]
                }

            spec 'hash with true entry'
                (expression '{port 1234. readonly}') should contain fields {
                    is hash
                    entries [
                        {
                            field ['port']
                            value {integer 1234}
                        }
                        {
                            field ['readonly']
                            value @undefined
                        }
                    ]
                }

    spec 'function calls'
        spec 'function call'
            (expression 'touch @ file') should contain fields {
                function {variable ['touch']}
                arguments [{variable ['file']}]
            }

        spec 'function call with self argument'
            (expression 'touch @: file') should contain fields {
                function {variable ['touch']}
                arguments [
                    {
                        is field reference
                        object {variable ['self']}
                        name ['file']
                    }
                ]
            }

        spec 'function call with splat argument'
            (expression 'touch @files ...') should contain fields {
                function {variable ['touch']}
                arguments [
                  {variable ['files']}
                  {is splat}
                ]
            }

        spec 'function call with no argument'
            (expression 'delete everything!') should contain fields {
                function {variable ['delete'. 'everything']}
                arguments []
            }

        spec 'function call with block with parameters'
            (expression "with file \@file #stream\n  stream") should contain fields {
                function {variable ['with'. 'file']}
                arguments [
                    {variable ['file']}
                    {
                        body {statements [{variable ['stream']}]}
                        parameters [{
                            is parameter
                            expression {variable ['stream']}
                        }]
                    }
                ]
            }

        spec 'function call with block with long parameters'
            (expression "open database #(database connection)\n  database connection") should contain fields {
                function {variable ['open'. 'database']}
                arguments [
                    {
                        parameters [
                            {
                                is parameter
                                expression {variable ['database'. 'connection']}
                            }
                        ]
                        body {statements [{variable ['database'. 'connection']}]}
                    }
                ]
            }

        spec 'function call with two blocks with parameters'
            (expression 'name #x @{x} #y @ {y}') should contain fields {
                function {variable ['name']}
                arguments [
                    {
                        body {statements [{variable ['x']}]}
                        parameters [{
                            is parameter
                            expression {variable ['x']}
                        }]
                    }
                    {
                        body {statements [{variable ['y']}]}
                        parameters [{
                            is parameter
                            expression {variable ['y']}
                        }]
                    }
                ]
            }

        spec 'function call with two optional arguments'
            (expression 'name @a, port 34, server @s') should contain fields {
                function {variable ['name']}
                arguments [
                    {variable ['a']}
                ]
                optional arguments [
                    {
                        field ['port']
                        value {integer 34}
                    }
                    {
                        field ['server']
                        value {variable ['s']}
                    }
                ]
            }

        spec 'function call with no arguments and one optional argument'
            (expression 'start server, port 34') should contain fields {
                function {variable ['start'. 'server']}
                arguments []
                optional arguments [
                    {
                        field ['port']
                        value {integer 34}
                    }
                ]
            }

    spec 'object operations'
        spec 'method call'
            (expression 'object: method @argument') should contain fields {
                is method call
                object {variable ['object']}
                name ['method']
                arguments [{variable ['argument']}]
            }

        spec 'method call with optional arguments'
            (expression 'object: method @argument, view @view') should contain fields {
                is method call
                object {variable ['object']}
                name ['method']
                arguments [{variable ['argument']}]
                optional arguments [
                    {field ['view']. value {variable ['view']}}
                ]
            }

        spec 'field reference'
            (expression 'object: field') should contain fields {
                is field reference
                object {variable ['object']}
                name ['field']
            }

        spec 'field reference with newline'
            (expression "object:\nfield") should contain fields {
                is field reference
                object {variable ['object']}
                name ['field']
            }

        spec 'self field reference'
            (expression ':field') should contain fields {
                is field reference
                object {variable ['self']}
                name ['field']
            }

        spec 'indexer'
            (expression 'object: @x') should contain fields {
                is indexer
                object {variable ['object']}
                indexer {variable ['x']}
            }

    spec 'blocks'
        spec 'empty block'
            (expression '@{}') should contain fields {
                is block
                parameters []
                redefines self @false
                body {statements []}
            }

        spec 'block'
            (expression '@{x.y}') should contain fields {
                is block
                parameters []
                redefines self @false
                body {statements [
                    {variable ['x']}
                    {variable ['y']}
                ]}
            }

        spec 'block with parameter'
            (expression "#x\n  x.y") should contain fields {
                is block
                parameters [{is parameter. expression {variable ['x']}}]
                redefines self @false
                body {
                    statements [
                        {variable ['x']}
                        {variable ['y']}
                    ]
                }
            }

        spec 'block with parameter, redefining self'
            (expression '#x => @{x.y}') should contain fields {
                is block
                parameters [{is parameter. expression {variable ['x']}}]
                redefines self @true
                body {
                    statements [
                        {variable ['x']}
                        {variable ['y']}
                    ]
                }
            }

    spec 'operators'
        spec 'should be lower precedence than object operation'
            (expression 'o:m 2 +- o:x') should contain fields {
                is method call
                object {
                    is method call
                    object {variable ['o']}
                    name ['m']
                    arguments [{integer 2}]
                }

                name ['+-']
                arguments [
                    {
                        is field reference
                        object {variable ['o']}
                        name ['x']
                    }
                ]
            }

        spec 'parses backslash'
            (expression "2 +\\+ 1") should contain fields {
                is method call
                object {integer 2}

                name ["+\\+"]
                arguments [
                    {integer 1}
                ]
            }

        spec 'unary operators should be higher precedence than binary operators'
            (expression 'a && ! b') should contain fields {
                is operator
                operator '&&'

                arguments [
                    {variable ['a']}
                    {
                        is operator
                        operator '!'
                        arguments [{variable ['b']}]
                    }
                ]
            }

        spec 'can have newlines immediately after operator'
            (expression "a &&\nb") should contain fields {
                is operator
                operator '&&'

                arguments [
                    {variable ['a']}
                    {variable ['b']}
                ]
            }

    spec 'assignment'
        spec 'assignment'
            (expression 'x = y') should contain fields {
                is definition
                target {variable ['x']}
                source {variable ['y']}
            }

        spec 'function definition'
            spec 'function with one parameter'
                (expression 'func @x = x') should contain fields {
                    is definition
                    target {variable ['func']}
                    source {
                        parameters [{is parameter. expression {variable ['x']}}]
                        body {statements [{variable ['x']}]}
                    }
                }

            spec 'function with one parameter, and one optional parameter'
                (expression 'func @x, port 80 = x') should contain fields {
                    is definition
                    target {variable ['func']}
                    source {
                        parameters [{is parameter. expression {variable ['x']}}]
                        optional parameters [{field ['port']. value {integer 80}}]
                        body {statements [{variable ['x']}]}
                    }
                }

        spec 'field assignment'
            (expression 'o: x = y') should contain fields {
                is definition
                target {
                    is field reference
                    object {variable ['o']}
                    name ['x']
                }

                source {variable ['y']}
            }

        spec 'index assignment'
            (expression 'o: @x = y') should contain fields {
                is definition
                target {
                    is indexer
                    object {variable ['o']}
                    indexer {variable ['x']}
                }

                source {variable ['y']}
            }

        spec 'assignment from field'
            (expression 'x = y: z') should contain fields {
                is definition
                target {variable ['x']}
                source {
                    is field reference
                    object {
                        variable ['y']
                    }

                    name ['z']
                }
            }

        spec 'assignment of command'
            (expression 'x! = 8') should contain fields {
                is definition
                target {variable ['x']}
                source {
                    is block
                    parameters []
                    body {
                        statements [{integer 8}]
                    }
                }
            }

        spec 'assignment of query'
            (expression 'x? = 8') should contain fields {
                is definition
                target {variable ['x']}
                source {
                    is block
                    parameters []
                    body {
                        statements [{integer 8}]
                    }
                }
            }

        spec 'assignment from method call'
            (expression 'x = y: z @a') should contain fields {
                is definition
                target {variable ['x']}
                source {
                    is method call
                    object {
                        variable ['y']
                    }

                    name ['z']
                    arguments [{variable ['a']}]
                }
            }

        spec 'field assignment from method call'
            (expression 'i: x = y: z @a') should contain fields {
                is definition
                target {
                    is field reference
                    object {variable ['i']}
                    name ['x']
                }

                source {
                    is method call
                    object {
                        variable ['y']
                    }

                    name ['z']
                    arguments [{variable ['a']}]
                }
            }

    spec 'regexps'
        spec 'simple'
            (expression '`abc`') should contain fields {
                is reg exp
                pattern 'abc'
            }

        spec 'with options'
            (expression '`abc`img') should contain fields {
                is reg exp
                pattern 'abc'
                options 'img'
            }

        spec 'with escaped back ticks'
            (expression '`abc\`def\`ghi`') should contain fields {
                is reg exp
                pattern 'abc`def`ghi'
            }

        spec 'with newline'
            (expression "a = `abc\n     def`") should contain fields {
                is definition
                target {
                    is variable
                    variable ['a']
                }
                source {
                    is reg exp
                    pattern "abc\\ndef"
                }
            }

    spec 'comments'
        spec 'should allow one-line C++ style comments, as in: // this is a comment'
            spec 'when between lines'
                (statements "a // this is a comment\nb") should contain fields {
                    is statements
                    statements [
                        {variable ['a']}
                        {variable ['b']}
                    ]
                }

            spec 'when before an indented block'
                (statements "a // this is a comment\n  b") should contain fields {
                    is statements
                    statements [{
                        is function call
                        function {variable ['a']}
                        arguments [{
                            is block
                            body {
                                statements [
                                    {variable ['b']}
                                ]
                            }
                        }]
                    }]
                }

            spec 'when at end of file'
                (statements "a // this is a comment") should contain fields {
                    is statements
                    statements [
                        {variable ['a']}
                    ]
                }

        spec 'should allow multi-line C style comments, as in: /* this is a comment */'
            spec 'when on one line'
                (statements "a /* comment */ b") should contain fields {
                    statements [
                        {variable ['a'. 'b']}
                    ]
                }

            spec 'when there are two'
                (statements "a /* comment */ b /* another comment */ c") should contain fields {
                    statements [
                        {variable ['a'. 'b'. 'c']}
                    ]
                }

            spec 'when it contains a * character'
                (statements "a /* sh*t */ b") should contain fields {
                    statements [
                        {variable ['a'. 'b']}
                    ]
                }

            spec 'when it covers two lines'
                (statements "a /* line one\nline two */ b") should contain fields {
                    statements [
                        {variable ['a'. 'b']}
                    ]
                }

            spec 'when it extends to the end of the file'
                (statements "a /* comment to eof") should contain fields {
                    statements [
                        {variable ['a']}
                    ]
                }

    spec 'lexer'
        tokens = parser: lex 'a b'
        @tokens should contain fields [
            ['a'. 'identifier'. 51]
            ['b'. 'identifier'. 51]
            [''. undefined. 'eof']
        ]


removed unused line

removed unused line

require './runtime'

exports: create indent stack = create indent stack! =
    object =>
        :indents = [0]
        :indentation regex = new (RegExp '\n( *)$')
        :multi new line regex = new (RegExp '\n *\n')
        :new line regex = new (RegExp '\n')

        :is @text multi new line =
            :multi new line regex: test @text

        : @text has new line? =
            :new line regex: test @text

        :indentation (new line) =
            :indentation regex: exec (new line): 1: length

        :current indentation? =
            :indents: 0

        :set indentation @text =
            if (: @text has new line?)
                :indents:unshift (:indentation @text)
            else
                :indents:unshift (:current indentation?)

        :unset indentation! =
            :indents:shift!

        :tokens for eof? =
            tokens = []
            indents = :indents: length

            while @{indents > 1}
                tokens: push '}'
                indents = indents - 1

            tokens: push 'eof'

            tokens

        :tokens for new line @text =
            current indentation = :current indentation?
            indentation = :indentation @text

            if (current indentation == indentation)
                ['.']
            else if (current indentation < indentation)
                :indents: unshift @indentation
                ['@{']
            else
                tokens = []

                while @{:indents: 0 > indentation}
                    tokens: push '}'
                    :indents: shift!

                if (:is @text multi new line)
                    tokens: push '.'

                if (:indents: 0 < indentation)
                    tokens: push '@{'
                    :indents: unshift @indentation

                tokens

require './runtime'

exports: create indent stack = create indent stack! =
    peek @array = array: (array: length - 1)

    object =>
        :indents = [0]
        :indentation regex = new (RegExp '\n( *)$')
        :multi new line regex = new (RegExp '\n *\n')
        :new line regex = new (RegExp '\n')

        :is @text multi new line =
            :multi new line regex: test @text

        : @text has new line? =
            :new line regex: test @text

        :indentation (new line) =
            :indentation regex: exec (new line): 1: length

        :current indentation? =
            :indents: 0

        :set indentation @text =
            if (: @text has new line?)
                :indents:unshift (:indentation @text)
            else
                :indents:unshift (:current indentation?)

        :unset indentation! =
            :indents:shift!

        :tokens for eof? =
            tokens = []
            indents = :indents: length

            while @{indents > 1}
                tokens: push '}'
                indents = indents - 1

            tokens: push 'eof'

            tokens

        :tokens for new line @text =
            current indentation = :current indentation?
            indentation = :indentation @text

            if (current indentation == indentation)
                ['.']
            else if (current indentation < indentation)
                :indents: unshift @indentation
                ['@{']
            else
                tokens = []

                while @{:indents: 0 > indentation}
                    tokens: push '}'
                    :indents: shift!

                if (:is @text multi new line)
                    tokens: push '.'

                if (:indents: 0 < indentation)
                    tokens: push '@{'
                    :indents: unshift @indentation

                tokens


refactoring

refactoring

fib @i =
    if ((i == 0) || (i == 1))
        1
    else
        fib (i - 1) + fib (i - 2)

print fib @n = console : log "fib @n: @(fib @n)"

print fib 6

fib @i =
    if (i == 0)
        1
    else if (i == 1)
        1
    else
        fib (i - 1) + fib (i - 2)

print fib @n = console : log "fib @n: @(fib @n)"

print fib 6


old term spec no longer needed

old term spec no longer needed

cg = require '../src/bootstrap/codeGenerator/codeGenerator'.code generator ()
require './assertions'

terms (subterms) ... =
    cg.term =>
        self.terms = subterms

        self.subterms 'terms'

branch (left, right) =
    cg.term =>
        self.left = left
        self.right = right

        self.subterms 'left' 'right'

leaf (name) =
    cg.term =>
        self.name = name

location (fl, ll, fc, lc) = {
    first_line = fl
    last_line = ll
    first_column = fc
    last_column = lc
}

describe 'old terms'
    it 'subterms'
        term = cg.term =>
            self.a = cg.identifier 'a'
            self.b = cg.identifier 'b'
            self.subterms 'a' 'b'

        (term.all subterms ()) should contain fields [
            {identifier 'a'}
            {identifier 'b'}
        ]

    describe 'rewriting'
        it 'can rewrite subterms'
            term = cg.term =>
                self.a = cg.identifier 'a'
                self.b = cg.identifier 'b'

                self.subterms 'a' 'b'

            term.rewrite @(term)
                if (term.is identifier && (term.identifier == 'b'))
                    cg.identifier 'z'

            (term) should contain fields {
                a {identifier 'a'}
                b {identifier 'z'}
            }

        it 'can rewrite subterms in lists'
            term = cg.term =>
                self.things = [cg.identifier 'a', cg.identifier 'b']

                self.subterms 'things'

            term.rewrite @(term)
                if (term.is identifier && (term.identifier == 'b'))
                    cg.identifier 'z'

            (term) should contain fields {
                things [{identifier 'a'}, {identifier 'z'}]
            }

        it "doesn't rewrite subterms that aren't objects"
            term = cg.term =>
                self.things = [cg.identifier 'a', null, nil, 0, 1.1, "one", ""]
                self.a = 6
                self.b = {x = 5}

                self.subterms 'things' 'a' 'b'

            terms rewritten = []

            term.rewrite @(term)
                terms rewritten.push (term)

            (terms rewritten) should contain fields [
                {identifier 'a'}
                {x = 5}
            ]

        it "rewrites lists of lists of terms"
            term = cg.term =>
                self.things = [cg.identifier 'a', [cg.identifier 'b', [cg.identifier 'c']]]

                self.subterms 'things'

            term.rewrite @(term)
                if (term.is identifier && (term.identifier == 'c'))
                    cg.identifier 'z'

            (term) should contain fields {
                things [{identifier 'a'}, [{identifier 'b'}, [{identifier 'z'}]]]
            }

        it 'rewrites deep into the graph'
            subterm = cg.term =>
                self.a = cg.identifier 'a'
                self.subterms 'a'

            term = cg.term =>
                self.thing = subterm
                self.subterms 'thing'

            term.rewrite @(term)
                if (term.is identifier && (term.identifier == 'a'))
                    cg.identifier 'z'

            (term) should contain fields {
                thing {
                    a = {identifier 'z'}
                }
            }

        it "doesn't rewrite beyond limit"
            subterm = cg.term =>
                self.is subterm = true
                self.a = cg.identifier 'a'
                self.subterms 'a'

            term = cg.term =>
                self.thing = subterm
                self.b = cg.identifier 'b'
                self.subterms 'thing' 'b'

            term.rewrite (limit (term) if: term.is subterm) @(term)
                if (term.is identifier)
                    cg.identifier 'z'

            (term) should contain fields {
                thing {
                    a = {identifier 'a'}
                }
                b = {identifier 'z'}
            }

    describe 'locations'
        it 'location'
            id = cg.loc (cg.identifier 'a', location 1 2 3 4)

            (id.location ()) should contain fields {
                first line 1
                last line 2
                first column 3
                last column 4
            }

        it "aggregates locations of subterms if it doesn't have a location itself"
            term = cg.term
                this.a = cg.loc (cg.identifier 'a', location 1 1 3 10)
                this.b = cg.loc (cg.identifier 'b', location 1 1 2 12)
                this.subterms 'a' 'b'

            (term.location ()) should contain fields {
                first line 1
                last line 1
                first column 2
                last column 12
            }

    it 'derived term'
        a = cg.loc (leaf 'a', location 1 1 2 8)
        b = cg.loc (leaf 'b', location 2 2 2 8)

        term = branch (a, b)
        c = term.derived term (leaf 'c')

        (c.location ()) should contain fields {
            first line 1
            last line 2
            first column 2
            last column 8
        }

    describe 'depth first walk'
        root = terms (branch (leaf 'a', leaf 'b'), branch (leaf 'c', branch (leaf 'd', leaf 'e')))

        it 'walks all terms depth first'
            leaf terms = []

            root.walk descendants @(term)
                if (term.name)
                    leaf terms.push (term.name)

            (leaf terms) should contain fields ['a', 'b', 'c', 'd', 'e']

        it "doesn't walk undefined subterms"
            term = branch (leaf 'a', undefined)

            leaf terms = []

            term.walk descendants @(term)
                leaf terms.push (term)

            (leaf terms) should contain fields [{name 'a'}]


printing hosting url on webServer.pogo

printing hosting url on webServer.pogo

express = require 'express'
require '../bootstrap/runtime.pogo'

web server @methods, port 3000 =
    app = express: create server?

    app context = object =>
        :get @path @action =
            app: get @path #req #res
                request context = object =>
                    :request = req
                    :response = res
                    :stuff = 'stuff'

                res: send (action: call (request context))

    methods: call (app context)
    app: listen @port
    console: log "hosting at http://localhost:@port/"

web server, port 4567 =>
    :get '/' =>
        "Hello World\n"

express = require 'express'
require '../bootstrap/runtime.pogo'

web server @methods, port 3000 =
    app = express: create server?

    app context = object =>
        :get @path @action =
            app: get @path #req #res
                request context = object =>
                    :request = req
                    :response = res
                    :stuff = 'stuff'

                res: send (action: call (request context))

    methods: call (app context)
    app: listen @port

web server, port 4567 =>
    :get '/' =>
        "Hello World\n"


new syntax for preparser.pogo

new syntax for preparser.pogo

fs = require 'fs'
ms = require '../lib/memorystream'
require './runtime.pogo'

exports: new line parser = new line parser? =
  last indentation = ''
  indentation pattern = new (RegExp '^( *)(.*)$')
  is empty line pattern = new (RegExp '^\s*$')
  line ends with bracket pattern = new (RegExp '[{([]\s*$')
  line starts with bracket pattern = new (RegExp '^\s*[\])}]')
  is first line = true

  @line is empty =
    is empty line pattern: test @line

  @line starts with bracket =
    line starts with bracket pattern: test @line

  @line ends with bracket =
    line ends with bracket pattern: test @line

  ?line
      if (@line is empty)
        line = {is empty, line @line, is first line (is first line)}
        line
      else
        indentation match = indentation pattern: exec @line
        indentation = indentation match: 1
        code = indentation match: 2

        line = {
          line @line
          code @code
          indentation @indentation
          is indent (indentation > last indentation)
          is unindent (indentation < last indentation)
          is new line (indentation == last indentation)
          ends with bracket (@line ends with bracket)
          starts with bracket (@line starts with bracket)
          is first line (is first line)
        }

        is first line = false
        last indentation = indentation

        line

exports: new indent stack = new indent stack? =
  indents = ['']
  peek @array = array: (array: length - 1)

  object =>
    this: indent to @i =
      indents: push @i

    this: count unindents while unwinding to @i =
      unindent count = 0

      while @{peek @indents != i}
        unindent count = unindent count + 1
        indents: pop!

      unindent count

exports: new file parser? =
  ?source
    lines = source: split "\n"
    last line = {no line}
    parse = new line parser?

    stream = new (ms: MemoryStream!)

    indent stack = new indent stack?

    @s plus @a if @c =
      if @c
        s + a
      else
        s

    write @l =
      if (not (l: no line))
        stream: write (l: line: replace (new (RegExp '\\' 'g')) '\\' + "\n")

    write @l appending @s =
      if (not (l: no line))
        stream: write (l: line: replace (new (RegExp '\\' 'g')) '\\' + s + "\n")

    concat @s @n times =
      r = ''
      while @{n > 0}
        r = r + s
        n = n - 1

      r

    for each ?sline in @lines
      line = parse @sline

      if (line: is new line)
        write (last line) appending ('' plus '\.' if (not (((line: is first line) or (last line: ends with bracket)) or (line: starts with bracket))))

      if (line: is empty)
        write (last line)

      if (line: is indent)
        write (last line) appending ('' plus '\@{' if (not (last line: ends with bracket)))
        indent stack: indent to (line: indentation)

      if (line: is unindent)
        number of unwind brackets = indent stack: count unindents while unwinding to (line: indentation)

        if (line: starts with bracket)
          number of unwind brackets = number of unwind brackets - 1

        last line ending = concat '\}' (number of unwind brackets) times

        write (last line) appending ((last line ending) plus '\.' if (last line: is empty))

      last line = line

    number of unwind brackets = indent stack: count unindents while unwinding to ''
    write (last line) appending (concat '\}' (number of unwind brackets) times)

    stream: to string?

fs = require 'fs'
ms = require '../lib/memorystream'
require './runtime.pogo'

exports: new line parser = new line parser? =
  last indentation = ''
  indentation pattern = new (RegExp '^( *)(.*)$')
  is empty line pattern = new (RegExp '^\\s*$')
  line ends with bracket pattern = new (RegExp '[{([]\\s*$')
  line starts with bracket pattern = new (RegExp '^\\s*[\\])}]')
  is first line = true

  @line is empty =
    is empty line pattern: test @line

  @line starts with bracket =
    line starts with bracket pattern: test @line

  @line ends with bracket =
    line ends with bracket pattern: test @line

  ?line
      if (@line is empty)
        line = #{is empty, line @line, is first line (is first line)}
        line
      else
        indentation match = indentation pattern: exec @line
        indentation = indentation match: 1
        code = indentation match: 2

        line = #
          line @line
          code @code
          indentation @indentation
          is indent (indentation > last indentation)
          is unindent (indentation < last indentation)
          is new line (indentation == last indentation)
          ends with bracket (@line ends with bracket)
          starts with bracket (@line starts with bracket)
          is first line (is first line)

        is first line = false
        last indentation = indentation

        line

exports: new indent stack = new indent stack? =
  indents = ['']
  peek @array = array: (array: length - 1)

  object =>
    this: indent to @i =
      indents: push @i

    this: count unindents while unwinding to @i =
      unindent count = 0

      while {peek @indents != i}
        unindent count = unindent count + 1
        indents: pop!

      unindent count

exports: new file parser? =
  ?source
    lines = source: split '\n'
    last line = #{no line}
    parse = new line parser?

    stream = new (ms: MemoryStream!)

    indent stack = new indent stack?

    @s plus @a if @c =
      if @c
        s + a
      else
        s

    write @l =
      if (not (l: no line))
        stream: write (l: line: replace (new (RegExp '\\\\' 'g')) '\\\\' + '\n')

    write @l appending @s =
      if (not (l: no line))
        stream: write (l: line: replace (new (RegExp '\\\\' 'g')) '\\\\' + s + '\n')

    concat @s @n times =
      r = ''
      while {n > 0}
        r = r + s
        n = n - 1

      r

    for each ?sline in @lines
      line = parse @sline

      if (line: is new line)
        write (last line) appending ('' plus '\\.' if (not (((line: is first line) or (last line: ends with bracket)) or (line: starts with bracket))))

      if (line: is empty)
        write (last line)

      if (line: is indent)
        write (last line) appending ('' plus '\\@{' if (not (last line: ends with bracket)))
        indent stack: indent to (line: indentation)

      if (line: is unindent)
        number of unwind brackets = indent stack: count unindents while unwinding to (line: indentation)

        if (line: starts with bracket)
          number of unwind brackets = number of unwind brackets - 1

        last line ending = concat '\\}' (number of unwind brackets) times

        write (last line) appending ((last line ending) plus '\\.' if (last line: is empty))

      last line = line

    number of unwind brackets = indent stack: count unindents while unwinding to ''
    write (last line) appending (concat '\\}' (number of unwind brackets) times)

    stream: to string?