Have you ever typed code and watched your editor instantly underline a mistake, suggest the next word, or let you jump straight to where a function was defined, all before running anything? That experience, which many developers now take for granted, is powered by something called a language server working quietly in the background. But for smaller, newer, or more unusual programming languages, building that kind of smart tooling has always been a massive effort. Researchers Stefan Marr, Humphrey Burchell, and Fabio Niephaus set out to change that. Their work explores a smarter, simpler way to build these tools, one that does not require the code to run at all.
What Is a Language Server?
When you code in an editor like Visual Studio Code, a language server handles the heavy lifting, powering features such as jumping to function definitions, highlighting variable usage, showing real-time errors, and offering word suggestions. Before 2016, developers had to create separate plugins for each editor, Atom, Eclipse, Vim, etc. The Language Server Protocol (LSP), introduced in June 2016, standardized communication between editors and language servers, allowing a single server to work across all editors. This was a major advancement and was rapidly adopted by language communities.
The Problem: You Have to Run the Code First
For dynamic languages, building a language server is difficult because types are only known at runtime. One approach, used by the Truffle framework, runs the code and observes behavior, such as variable types and function calls, to power IDE features. However, this requires the code to actually execute, meaning developers need clear entry points and thorough tests to cover all parts of the program. If some code doesn’t run, the IDE has no data about it. Additionally, runtimes often discard useful information like class locations, comments, and syntax details since they aren’t needed during execution.

A Simpler Idea: Read the Code, Don't Run It
The researchers propose a parse-based approach, where the IDE collects information during parsing instead of execution. Since parsing happens before running code—even if the code is incomplete, it can capture details like function names, parameters, and variable definitions as the parser reads the code. This information is stored in a simple structure that the language server can query. They designed a lightweight interface for parsers to report this data, using a central class called Structures to store elements, their locations, relationships, and any errors.
What This Makes Possible
Once the parser feeds information into the structure, many IDE features work immediately without executing code.
- Symbol outlines: Work instantly since the parser already identifies all classes, functions, and variables.
- Goto definition: Uses identifiers recorded during parsing to jump to definitions. In dynamic languages, it may return multiple candidates, but it reliably finds matches across parsed files.
- Error reporting: Works the same as execution-based approaches, since syntax errors are caught during parsing.
- Signature help: Extracted directly from function definitions during parsing; comments and documentation can also be included if the parser preserves them.
- Semantic highlighting: More accurate than common grammar-based methods because it relies on the real parser, allowing precise distinctions (e.g., local variables vs. object fields).
- Code completion: Suggests elements available in the current scope; when accessing object properties (e.g., after typing a dot), it switches to relevant fields and methods.
- Linting: Supported through checks like ensuring references have matching definitions and enforcing conventions (e.g., files ending with a newline).
How Much Work Does It Actually Take?
One of the most compelling aspects of the research is its efficiency. The parse-based approach was implemented for Newspeak, SOM, and SimpleLanguage, with the largest implementation (SOM) requiring only 920 lines of code, much of it boilerplate, leaving relatively little core logic. By comparison, a survey of 20 real-world language servers showed a range from 1,577 to nearly 96,000 lines, with a median of 12,055. This means the parse-based method delivers widely used IDE features at about one-tenth the typical cost. While the execution-based approach can require as little as 500 lines in ideal cases, the parse-based method demands only slightly more effort while offering broader coverage and eliminating the need to run the code.
How Fast Is It?
A key concern is whether compiler-designed parsers are fast enough for real-time editing, since every keystroke could trigger a new parse. The researchers tested this by generating files of various sizes and measuring parse times. On decade-old hardware, the slowest parser, SimpleLanguage, took 215 milliseconds for a 10,000-line file, while Newspeak and SOM parsers completed in 86 and 78 milliseconds. For typical developer files, these times are well within acceptable limits. For very large files or slower parsers, the language server can send partial results progressively to maintain editor responsiveness.
Parse and Execution Together
Rather than treating the two approaches as rivals, the researchers point out they work well together. The parse-based approach gives a reliable, always-available foundation. Even for code that cannot run, has errors halfway through, or lacks any tests, the IDE can still show symbols, highlight code, complete function names, and flag syntax errors.
The execution-based approach can then layer on top to improve precision where it matters most, for example, knowing the exact runtime type of an object to suggest the right properties when you type a dot. Combining both gives the best of each world: broad coverage from parsing and precision from execution.
Conclusion
Getting smart editor features for a programming language has long required a large investment of time and code. This research presents a more accessible path. By capturing structural information during parsing rather than execution, the parse-based language server framework makes it possible to support the most important IDE features, symbol navigation, error reporting, code completion, semantic highlighting, and linting, with far less effort, and without ever needing to run the program. For developers working on niche, research, or experimental languages, this could make the difference between having good tooling and having none at all. As the language server ecosystem continues to grow, frameworks like this one bring quality developer tools within reach for a much wider range of languages.