Dealing with various node types in a parse tree using TypeScript: Tips and Tricks

I am in the process of converting my lexer and parser to TypeScript. You can find the current JavaScript-only code here. To simplify, I have created an example pseudocode:

type X = {
  type: string
}

type A = X & {
  list: Array<A | B | C>
}

type B = X & {
  value: A | B | number
}

type C = X & {
  value: string
}

const a1: A = { type: 'a', list: [] }
const a2: A = { type: 'a', list: [] }
const a3: A = { type: 'a', list: [] }

const b1: B = { type: 'b', value: a1 }
const b2: B = { type: 'b', value: b1 }
const b3: B = { type: 'b', value: 200 }

const c1: C = { type: 'c', value: 'foo' }
const c2: C = { type: 'c', value: 'bar' }
const c3: C = { type: 'c', value: 'baz' }

a1.list.push(b1, a2, b2, b3)
a2.list.push(a1, a3, b3, c1)
a3.list.push(b2, c2, c3)

const x = { type: 'a', list: [a1, a2, a3] }

handle(x)

function handle(x: A | B) {
  if (x.type === 'a') {
    x.list.forEach(handle)
  } else if (x.type === 'b') {
    if (typeof x.value === 'number') {
      console.log(x.value)
    } else {
      handle(x.value)
    }
  } else { // c
    console.log(x.value)
  }
}

In the handle function, no typing has been implemented. How do you approach this situation?

In the parser code (which also includes a separate lexer module), certain operations are carried out like:

while (i < list.length) {
  const token = list[i++]
  // console.log(token.form, stack)
  switch (token.form) {
    case `term-open`: {
      const node = stack[stack.length - 1]
      const term = {
        form: 'term',
        link: []
      }
      node.leaf.push(term)
      stack.push(term)
      break
    }
    case `term-close`: {
      stack.pop()
      break
    }
    case `open-parenthesis`: {
      const node = stack[stack.length - 1]
      const site = {
        form: 'site',
        leaf: [],
        site: []
      }
      node.site.push(site)
      stack.push(site)
      break
    }
    case `close-parenthesis`: {
      stack.pop()
      break
    }
  }

The important aspect is

const node = stack[stack.length - 1]
, where the context determines the type of node. How would you appropriately handle such scenarios in TypeScript? Is casting as a specific type the correct approach?

What would be the proper way to iterate through the pseudocode above (TypeScript) and accurately log the leaf values with the correct typing?

Answer №1

The most effective approach, according to TypeScript guidelines, is to define your base node type X as a discriminated union. This means assigning a string literal type to the type property that helps differentiate it from other members of the union. In the context of your code snippet, this concept can be implemented as follows:

interface A {
    type: 'a'; 
    list: Array<A | B | C>
}

interface B {
    type: 'b'; 
    value: A | B | number
}

interface C {
    type: 'c'; 
    value: string
}

type X = A | B | C;

const x: X = { type: 'a', list: [a1, a2, a3] } // okay

After defining the types and structure accordingly, the handle() function will compile without errors:

function handle(x: X) {
    if (x.type === 'a') {
        x.list.forEach(handle)
    } else if (x.type === 'b') {
        if (typeof x.value === 'number') {
            console.log(x.value)
        } else {
            handle(x.value)
        }
    } else { // c
        console.log(x.value)
    }
}

This setup works well because conditions like x.type === "a" allow the compiler to narrow down the possible type of x, making it easier to work with specific properties based on the defined type.

Interactive code link

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Implement a class in Typescript that allows for the addition of properties at runtime

I'm currently in the process of incorporating Typescript definitions into an existing codebase that utilizes the Knockout library. Within the code, there is a prevalent pattern that appears as follows: interface SomeProperties { // Assorted prope ...

The revalidation process in react-hook-form doesn't seem to initiate

Stumbled upon a code example here Decided to fork a sandbox version (original had bugs and errors) I am trying to implement custom validation callbacks for each form input element by providing options in the register function, but the validate is only tr ...

`When attempting to use Typescript with Electron, the error 'exports is not defined

Trying to launch my basic electron application, I utilize Typescript for development which then compiles into JavaScript. However, upon running the app, an error is encountered: ReferenceError: exports is not defined[Learn More] file:///Users/ahmet/Docume ...

The variable "$" cannot be found within the current context - encountering TypeScript and jQuery within a module

Whenever I attempt to utilize jQuery within a class or module, I encounter an error: /// <reference path="../jquery.d.ts" /> element: jQuery; // all is good elementou: $; // all is fine class buggers{ private element: jQuery; // The nam ...

The element 'Component' is not eligible to be utilized as a JSX component (typed layout)

I encountered a type error while working with the following code snippet: {getLayout(<Component {...pageProps} />)} The error message states that 'Component' cannot be used as a JSX component. This is because its element type 'Compo ...

Creating a global variable in Angular that can be accessed by multiple components is a useful technique

Is there a way to create a global boolean variable that can be used across multiple components without using a service? I also need to ensure that any changes made to the variable in one component are reflected in all other components. How can this be ac ...

Trigger the rowContextMenu in Tabulator Table by clicking a Button

Is there a way to add a button at the end of a table that, when clicked, opens a rowContextMenu below the button? Additionally, can the rowContextMenu pop up when right-clicking anywhere on the row? I have attempted some solutions without success. Here is ...

What causes the "Method Not Allowed" error while trying to restore the package.json package in VS2015?

When trying to restore a package.json file in VS2015, I am encountering a "Method Not Allowed" error. https://i.stack.imgur.com/OgK5P.png https://i.stack.imgur.com/AAkoQ.png The error log displays the following: npm ERR! Error: Method Not Allowed npm ER ...

Utilizing a TypeScript definition file (.d.ts) for typings in JavaScript code does not provide alerts for errors regarding primitive types

In my JavaScript component, I have a simple exporting statement: ./component/index.js : export const t = 'string value'; This component also has a TypeScript definition file: ./component/index.d.ts : export const t: number; A very basic Typ ...

Setting up a variable with no operation assigned to it

As I delved into the Angular utils source code, I stumbled upon this interesting line: export const NOOP: any = () => {}; It seems pretty straightforward - a variable that doesn't perform any operation. But then, within the same library, there is ...

Script for running a React app with Prettier and eslint in a looping fashion

My Create React App seems to be stuck in a compile loop, constantly repeating the process. Not only is this behavior unwanted, but it's also quite distracting. The constant compiling occurs when running the default npm run start command. I suspect t ...

Typescript-enabled NodeJS REST client

I'm currently working on a nodejs web application using express and I want to access my API. I have experimented with various options, such as restangular and jquery ajax calls. Can anyone recommend some reliable REST client libraries with TypeScrip ...

Choose particular spreadsheets from the office software

My workbook contains sheets that may have the title "PL -Flat" or simply "FLAT" I currently have code specifically for the "PL -Flat" sheets, but I want to use an if statement so I can choose between either sheet since the rest of the code is identical fo ...

What is the best way to implement a Promise Function within a For loop?

Here is a function called sendEmail: public async sendEmail (log: LogMessage): Promise<void> { nodemailer.createTestAccount(async () => { return ServiceFactory.getSystemService().getNetworkPreferences().then(async (networkPreferences) => ...

Tips for showing an image through a button in Angular 4

Currently, I am in the process of creating a simple website and I have encountered an issue. When I click on a button to display an image, it works fine. However, when I click on another button to display a different image, the previous image remains visib ...

Troubleshooting Angular Build Errors: Integrating Three.js

Upon setting up a new Angular application and integrating three along with @types/three, I proceeded to create a basic component. However, upon executing ng build --prod, the following errors are displayed: ERROR in node_modules/three/src/core/BufferAttri ...

What is the best way to retrieve props for computed properties using Vue with Typescript?

Seeking help on accessing props data for my computed property. Here is the code snippet: <script lang="ts"> import { defineComponent } from 'vue' export default defineComponent({ props: { color: String, shape: String, ...

"I am having trouble calling the useStyles function in React with Typescript and Material-

There seems to be a problem with calling the useStyles function as it is throwing the following error message: This expression is not callable. Type 'never' has no call signatures.ts(2349) const useStyles: never Below is the complete code snip ...

Error encountered with react-query and UseQueryResult due to incorrect data type

I'm currently implementing react-query in my TypeScript project: useOrderItemsForCardsInList.ts: import { getToken } from '../../tokens/getToken'; import { basePath } from '../../config/basePath'; import { getTokenAuthHeaders } fr ...

Encountering an issue when utilizing inversifyJS inject with the error message "Reading '_userService' cannot be done as it is undefined."

I'm currently working on implementing a DI system, but it seems like I may be missing some key concepts of Inversify. Do I need to create a "get" method for the "user.controller" and then "bind" it to the routes function? Link to complete code reposi ...