# All files / src/core function.js

100% Statements 42/42
90.91% Branches 10/11
100% Functions 15/15
100% Lines 41/41

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132

    23x
    23x
    23x

    87x

    87x
    87x
    179x

    179x
    179x
    358x

    179x

    179x

    437x

    178x
    178x

    178x

    87x

    179x
    179x

    87x
    179x

    87x
    132x
    132x
    79x

    79x

    79x

    79x

    53x

    87x
    174x
    174x

    174x

    87x
    176x
    176x

    175x

    175x

    1x

    174x
    174x

     /* Core function plugin
     *
     * This plugin only handles the basics of functions like vanilla funciton calls,
     * arguments and return statements
     */
    import Syntax from '../Syntax';
    import { FUNCTION_INDEX, FUNCTION_METADATA } from '../semantics/metadata';
    import { typeWeight } from '../types';

    export default function coreFunctionPlugin() {
      return {
        semantics() {
          return {
            FunctionDeclaration: _ignore => ([fun, context], transform) => {
              context = {
                ...context,
                result: fun.result,
                locals: {},
                arguments: [],
              };

              // first two parameters to a function node are the arguments and result
              const [argsNode, resultNode, ...rest] = fun.params;
              const [args, result] = [argsNode, resultNode].map(p =>
                transform([p, context])
              );

              const ref = {
                ...fun,
                // This is set by the parsers below if necessary
                type: context.result,
                meta: {
                  ...fun.meta,
                  [FUNCTION_INDEX]: Object.keys(context.functions).length,
                  [FUNCTION_METADATA]: {
                    argumentsCount: context.arguments.length,
                  },
                },
              };
              context.functions[fun.value] = ref;

              // Parse the statements last, so that they can self-reference the function
              const statements = rest.map(p => transform([p, context]));

              ref.meta[FUNCTION_METADATA].locals = context.locals;
              ref.params = [args, result, ...statements];

              return ref;
            },
            FunctionResult: _ignore => ([result, context]) => {
              // Function statements are sybligs of FuncionResult so we need to mutate
              // the parent context (FunctionDeclaration)
              context.result = result.type;
              return result;
            },
            FunctionArguments: next => ([args, context]) => {
              return next([
                { ...args, params: args.params.filter(Boolean) },
                { ...context, isParsingArguments: true },
              ]);
            },
            Pair: next => (args, transform) => {
              const [node, context] = args;
              if (context.isParsingArguments) {
                const [identifier, typeNode] = node.params;

                context.arguments.push(node);

                transform([
                  {
                    ...node,
                    value: identifier.value,
                    type: typeNode.value,
                    params: [],
                    Type: Syntax.Declaration,
                  },
                  context,
                ]);

                return node;
              }

              return next(args);
            },
            // Regular function calls
            FunctionCall: next => ([call, context]) => {
              const { functions } = context;
              const index = Object.keys(functions).indexOf(call.value);

              return next([
                {
                  ...call,
                  type:
                    functions[call.value] != null
                      ? functions[call.value].type
                      : null,
                  meta: { [FUNCTION_INDEX]: index },
                },
                context,
              ]);
            },
            ReturnStatement: _ignore => ([returnNode, context], transform) => {
              const [expression] = returnNode.params.map(p =>
                transform([p, context])
              );
              const { result } = context;
              // Consants as return values need to be assigned a correct type
              // (matching the result expected)
              if (
                expression != null &&
                expression.Type === Syntax.Constant &&
                typeWeight(expression.type) !== typeWeight(result)
              ) {
                return {
                  ...returnNode,
                  type: result,
                  params: [{ ...expression, type: result }],
                };
              }

              const type = expression ? expression.type : null;
              return {
                ...returnNode,
                params: [expression],
                type,
              };
            },
          };
        },
      };
    }