All files / src/core index.js

100% Statements 49/49
100% Branches 12/12
100% Functions 19/19
100% Lines 47/47
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 154 155 156 157 158 159 160 161 162 163 164 165 166                23x 23x           23x 23x 23x     23x   446x   895x 436x     459x       446x 895x         45x 36x               9x                           850x     446x                 90x     180x 209x 209x   209x                         209x     90x       90x 12x   24x   90x 431x   862x   90x 73x   146x 73x 73x 73x   73x                   90x 619x 619x 619x 612x               7x 2x               5x   90x 3x                          
/**
 * Core language plugin
 *
 * The parsers in here very closely mirror the underlying WebAssembly structure
 * and are used as the core language for every feature built on top.
 *
 * @flow
 */
import Syntax from 'walt-syntax';
import {
  current as currentScope,
  namespace,
  index as scopeIndex,
  find,
} from 'walt-parser-tools/scope';
import { extendNode } from '../utils/extend-node';
import { TYPE_CAST, TYPE_CONST } from '../semantics/metadata';
import { typeWeight } from '../types';
import type { SemanticPlugin } from '../flow/types';
 
const balanceTypesInMathExpression = expression => {
  // find the heaviest type in the expression
  const type = expression.params.reduce((acc, { type: childType }) => {
    // The way we do that is by scanning the top-level nodes in our expression
    if (typeWeight(acc) < typeWeight(childType)) {
      return childType;
    }
 
    return acc;
  }, expression.type);
 
  // iterate again, this time, patching any lighter types
  const params = expression.params.map(paramNode => {
    if (
      paramNode.type != null &&
      typeWeight(paramNode.type) !== typeWeight(type)
    ) {
      // Convert constants to the desired type directly
      if (paramNode.Type === Syntax.Constant) {
        return extendNode(
          {
            type,
          },
          paramNode
        );
      }
 
      return extendNode(
        {
          type,
          value: paramNode.value,
          Type: Syntax.TypeCast,
          meta: {
            [TYPE_CAST]: { to: type, from: paramNode.type },
          },
          params: [paramNode],
        },
        paramNode
      );
    }
 
    return paramNode;
  });
 
  return {
    ...expression,
    params,
    type,
  };
};
 
// Core plugin
export default function Core(): SemanticPlugin {
  return {
    semantics() {
      // Parse declaration node
      const declaration = next => ([node, context]) => {
        const scope = currentScope(context.scopes);
        const index = scopeIndex(scope, node.value);
 
        scope[node.value] = extendNode(
          {
            params: node.params.map(extendNode({ type: node.type })),
            meta: {
              ...node.meta,
              [scope[namespace]]: index,
              [TYPE_CONST]: node.Type === Syntax.ImmutableDeclaration,
            },
            Type: Syntax.Declaration,
          },
          node
        );
 
        return next([scope[node.value], context]);
      };
 
      return {
        [Syntax.Declaration]: declaration,
        [Syntax.ImmutableDeclaration]: declaration,
        // CharacterLiteral: next => ([node]) => next([mapCharacterLiteral(node)]),
        [Syntax.Select]: _ => ([node, context], transform) =>
          balanceTypesInMathExpression({
            ...node,
            params: node.params.map(child => transform([child, context])),
          }),
        [Syntax.BinaryExpression]: _ => ([node, context], transform) =>
          balanceTypesInMathExpression({
            ...node,
            params: node.params.map(child => transform([child, context])),
          }),
        [Syntax.Pair]: _next => (args, transform) => {
          const [typeCast, context] = args;
 
          const params = typeCast.params.map(p => transform([p, context]));
          const [targetNode, typeNode] = params;
          const { type: from } = targetNode;
          const { value: to } = typeNode;
 
          return {
            ...typeCast,
            type: to,
            value: targetNode.value,
            Type: Syntax.TypeCast,
            meta: { ...typeCast.meta, [TYPE_CAST]: { to, from } },
            // We need to drop the typeNode here, because it's not something we can generate
            params: [targetNode],
          };
        },
        [Syntax.Identifier]: next => args => {
          const [node, context] = args;
          let ref = find(context.scopes, node.value);
          if (ref) {
            return {
              ...node,
              meta: { ...node.meta, ...ref.meta },
              type: ref.type,
            };
          }
 
          // null-expr
          if (node.value === 'null') {
            return {
              ...node,
              value: '0',
              type: 'i32',
              Type: Syntax.Constant,
            };
          }
 
          return next(args);
        },
        [Syntax.TernaryExpression]: next => ([node, context]) => {
          return next([
            balanceTypesInMathExpression({
              ...node,
              // Flatten out the parameters, put the condition node last
              params: [...node.params[1].params, node.params[0]],
            }),
            context,
          ]);
        },
      };
    },
  };
}