val 0.4.0

An arbitrary precision calculator language
Documentation
import type { AstNode, ValError } from '@/lib/types';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { parse } from 'val-wasm';

interface UseValAstOptions {
  code: string;
  loaded: boolean;
}

interface UseValAst {
  errors: ValError[];
  expandedNodes: Set<AstNode>;
  root: AstNode | undefined;
  toggleExpand: (node: AstNode) => void;
}

export function useValAst({ code, loaded }: UseValAstOptions): UseValAst {
  const { root, errors } = useMemo(() => {
    if (!loaded) {
      return { root: undefined, errors: [] };
    }

    try {
      return { root: parse(code) as AstNode, errors: [] };
    } catch (error) {
      return { root: undefined, errors: parseErrors(error) };
    }
  }, [code, loaded]);

  const [expandedNodes, setExpandedNodes] = useState<Set<AstNode>>(
    () => new Set()
  );

  useEffect(() => {
    if (!root) {
      setExpandedNodes(new Set());
      return;
    }

    const all = new Set<AstNode>();

    const walk = (node: AstNode) => {
      all.add(node);
      node.children.forEach(walk);
    };

    walk(root);

    setExpandedNodes(all);
  }, [root]);

  const toggleExpand = useCallback((node: AstNode) => {
    setExpandedNodes((previous) => {
      const next = new Set(previous);

      if (next.has(node)) {
        next.delete(node);
      } else {
        next.add(node);
      }

      return next;
    });
  }, []);

  return { root, errors, expandedNodes, toggleExpand };
}

function parseErrors(error: unknown): ValError[] {
  if (Array.isArray(error)) {
    return error as ValError[];
  }

  return [
    {
      kind: 'Parser',
      message: error instanceof Error ? error.message : String(error),
      range: {
        start: 0,
        end: 0,
      },
    },
  ];
}