All files / src/core function.js

100% Statements 43/43
100% Branches 9/9
100% Functions 13/13
100% Lines 43/43
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 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153              23x 23x 23x 23x         23x                 90x   90x 90x         173x 173x   173x 173x 346x     173x                         173x     173x   173x   173x   90x     173x 173x   173x   90x       173x   173x   173x   63x   63x   63x                         173x     90x 247x 247x   247x                         90x       173x   173x 171x   173x     173x         1x             172x 172x                    
/* Core function plugin
 *
 * @flow
 *
 * This plugin only handles the basics of functions like vanilla function calls,
 * arguments and return statements
 */
import Syntax from 'walt-syntax';
import { current, enter, exit, signature } from 'walt-parser-tools/scope';
import walkNode from 'walt-parser-tools/walk-node';
import {
  FUNCTION_INDEX,
  FUNCTION_METADATA,
  LOCAL_INDEX,
} from '../semantics/metadata';
import { typeWeight } from '../types';
import type {
  SemanticPlugin,
  FunctionDeclaration,
  FunctionArguments,
  Context,
} from '../flow/types';
 
export default function coreFunctionPlugin(): SemanticPlugin {
  return {
    semantics() {
      return {
        [Syntax.FunctionDeclaration]: _ignore => (
          [fun, context]: [FunctionDeclaration, Context],
          transform
        ) => {
          // Enter a new scope, where all new declaration will go into
          context.scopes = enter(context.scopes, LOCAL_INDEX);
          const currentScope = current(context.scopes);
 
          const [argsNode, resultNode, block] = fun.params;
          const [args, result] = [argsNode, resultNode].map(p =>
            transform([p, context])
          );
 
          const ref = {
            ...fun,
            // This is set by the parsers below if necessary, defaults to null
            type: currentScope[signature].result,
            meta: {
              ...fun.meta,
              [FUNCTION_INDEX]: Object.keys(context.functions).length,
              [FUNCTION_METADATA]: {
                argumentsCount: currentScope[signature].arguments.length,
                locals: current(context.scopes),
              },
            },
          };
          context.functions[fun.value] = ref;
 
          // Parse the block last, so that they can self-reference the function
          ref.params = [args, result, transform([block, context])];
 
          context.scopes = exit(context.scopes);
 
          return ref;
        },
        [Syntax.FunctionResult]: _next => ([result, context]) => {
          // Function statements are sybligs of FunctionResult so we need to mutate
          // the parent context (FunctionDeclaration)
          const currentScope = current(context.scopes);
          currentScope[signature].result = result.type;
 
          return result;
        },
        [Syntax.FunctionArguments]: _next => (
          [args, context]: [FunctionArguments, Context],
          transform
        ) => {
          const currentScope = current(context.scopes);
 
          currentScope[signature].arguments = [];
 
          walkNode({
            [Syntax.Pair]: node => {
              const [identifier, typeNode] = node.params;
 
              currentScope[signature].arguments.push(node);
 
              transform([
                {
                  ...node,
                  value: identifier.value,
                  type: typeNode.value,
                  params: [],
                  Type: Syntax.Declaration,
                },
                context,
              ]);
            },
          })({ ...args, params: args.params.filter(Boolean) });
 
          return args;
        },
        // Regular function calls
        [Syntax.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 },
              params: call.params.slice(1),
            },
            context,
          ]);
        },
        [Syntax.ReturnStatement]: _next => (
          [returnNode, context],
          transform
        ) => {
          const currentScope = current(context.scopes);
 
          const [expression] = returnNode.params.map(p =>
            transform([p, context])
          );
          const { result } = currentScope[signature];
          // Constants 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,
          };
        },
      };
    },
  };
}