ESV¶
The Editor SerVice (ESV) language is a declarative meta-language for configuring the editor services of a language. For example, the following ESV code fragment configures the syntax highlighting for a language, based on the types of tokens:
module color
colorer
keyword : 153 51 153
identifier : black
string : 177 47 2
number : 17 131 22
operator : black
layout : 63 127 95 italic
We now look at how an ESV file is structured.
Structure¶
Each ESV file starts by defining a module for the file. Module identifiers must be unique. Modules can be imported with an import statement. For example, the following code fragment defines an ESV file with module Main
and imports module Syntax
and Analysis
:
module Main
imports
Syntax
Analysis
Importing modules means importing all the sections and definitions from that module.
The rest of the ESV file can contain sections for configuring editor services and other aspects of a language. Sections can be defined multiple times. For example, the following code fragment defines an ESV file with several sections:
module Main
language
extensions : ent
colorer
keyword : 153 51 153
identifier : default
language
line comment : "//"
It sets the extension in a language
section, adds coloring rules in a colorer
section, and sets the line comment to use in a language
section again.
Configuration Sections¶
Since editor services are a cross-cutting concern, ESV has a lot of cross-cutting configuration for these concerns. We now describe each configuration section in detail.
File Extension¶
The file extension of your language are set with the extensions
option under the language
section:
language
extensions : ent
Multiple extensions can be set with a comma-separated list:
language
extensions : ent, entity, entities
This registers the given extensions with Spoofax, which can then identify files to your language based on extensions. This is used to open an editor for your language in an IDE setting, and to process files of your language in a command-line setting.
Syntax¶
The SDF parse table file, and which start symbols to use, are set as follows:
language
table : target/metaborg/sdf.tbl
start symbols : Start
Multiple start symbols can be set with a comma-separated list:
language
start symbols : Start, Program
The parse table of your language is set with the table
option. By default, the parse table of an SDF specification is always produced at target/metaborg/sdf.tbl
. It is only neccessary to change this configuration when a custom parse table is used.
The start symbols
option determine which start symbols to use when an editor is opened. This must be a subset of the start symbols defined in the SDF3 specification of your language.
The syntax for comments are set as follows:
language
line comment : "//"
block comment : "/*" "*/"
The line comment
option determines how single-line comments are created. It is used by editors to toggle the comment for a single line. For example, in Eclipse, pressing Ctrl-/ (Cmd-/ on macOS), comments or uncomments the line.
The block comment
option determines how multi-line comments are created. It is used similarly, but when a whole block needs to be commented or uncommented. A block comment is determined by two strings denoting the start and end symbols of the block comment.
Fences for bracket matching are set as follows:
language
fences : [ ] ( ) { }
The fences
options determines which symbols to use and match for bracket matching. A single fence is defined by a starting and closing symbol. Multiple fences can be set with a space-separated list. Fences are used to do bracket matching in text editors.
Warning
Fences can contain multiple characters, but some implementations may not handle bracket matching with multiple fence characters. For example, Eclipse does not handle this case and ignores multi-character fences.
Syntax Highlighting¶
Token-based syntax highlighting is configured in a colorer
section. Such a section can contain style definitions and styling rules.
Style definitions bind an identifier to a style with syntax identifier = style
for reuse later. A style is a foreground (text) color, optional background color, and optional font attributes. For example, the following style definitions bind the red
, green
, and blue
colors:
colorer
red = 255 0 0
green = 0 255 0
blue = 0 0 255
A color is denoted by its RGB values, with values ranging from 0 to 255. An optional background color can be set by adding another RGB value:
colorer
redWithGreenBackground = 255 0 0 0 255 0
Optional font attributes can be used to make the font bold or italic:
colorer
redWithBold = 255 0 0 bold
redWithItalic = 255 0 0 italic
redWithGreenBackgroundWithBoldItalic = 255 0 0 0 255 0 bold italic
Style rules assign styles to matched tokens with syntax matcher : styleOrRef
. The left hand side of style rules match a token, whereas the right hand side assigns a style by referring to a previously defined style definition, or by directly assigning a style. For example, the following matches a token type and references a style definition:
colorer
operator : black
whereas the following matches a token with a sort and constructor, and directly assigns a style:
colorer
ClassBodyDec.MethodDec : 0 255 0
The following matchers on the left-hand side are supported:
Matching on built-in token types. The following types are supported:
identifier
- matches identifiers, found by lexical non-terminals without numbersstring
- matches strings, found by lexical non-terminals that include quotation marksnumber
- matches numbers, found by lexical non-terminals with numberskeyword
- matches keywords, found by terminals in the syntax definitionoperator
- matches operations, found by terminals that contain just symbols (no characters)layout
- matches layout, such as whitespace and comments, found by layout definitionunknown
- matches tokens which the parser was unable to infer a type for
For example, the following code defines a simple highlighting with token types:
colorer keyword : 153 51 153 identifier : black string : 177 47 2 number : 17 131 22 operator : black layout : 63 127 95 italic
Matching on sorts of tokens. For example:
colorer ID : darkblue TYPEID : blue JQTYPEID : blue PQTYPEID : blue FUNCID : 153 51 0 JFUNCID : 153 51 0 STRING : 177 47 2
Matching on sorts of tokens, and the constructor of the term that was created using the token. This uses the
Sort.Constructor
syntax. For example:colorer ClassBodyDec.MethodDec : yellow ClassBodyDec.FieldDec : red
Matching on the constructor only. This uses the
_.Constructor
syntax. For example:colorer _.Str : blue _.StrCong : blue _.QStr : blue _.QDollar : blue _.QBr : gray
Outline¶
An outline is a summary of a file that is shown in a separate view next to a textual editor. An outline is created by a Stratego strategy, but is configured in ESV under the views
section:
views
outline view: editor-outline
expand to level: 3
This configures the editor-outline
Stratego strategy to be used to create outlines, and that outline nodes should be expanded 3 levels deep by default.
Todo
Describe input and output signature of the outline strategy.
Hover Tooltips¶
Hover tooltips show a textual tooltip with extra information, when hovering part of the text. Hover tooltips are created by a Stratego strategy, but are configured in ESV under the references
section:
references
hover _ : editor-hover
The identifier after the colon refers to the Stratego strategy that creates the hover tooltip. The Stratego strategy takes an AST node, and either fails if no tooltip should be produced, or returns a tooltip string.
The string may contain a few simple HTML tag to style the output. The following tags are supported:
<br/>
- line break<b>text</b>
- bold<i>text</i>
- italics<pre>code</pre>
- preformatted (code) text
Compiler¶
The compiler strategy (frequently called the on-save handler) is used to transform files when they are saved in an editor. In an IDE setting, when a new project is opened, the compiler strategy is also executed on each file in the project, as well as when files change in the background. In a command-line batch compiler setting, it is used to transform all files.
The compiler strategy is configured in ESV with the on save
option:
language
on save : compile-file
The identifier after the colon refers to the Stratego strategy that performs the transformation. This strategy must have the exact same signature as the one for actions.
Analyzer and Context¶
The analyzer strategy is used to perform static analyses such as name and type analysis, on the AST that a parser produces. An analysis context provides a project-wide store to facilitate multi-file analysis and incrementality. There are four ways to configure the analysis, which set the analyzer strategy with option observer
and context with option context
.
No analysis. This disables analysis completely. Do not set an
observer
and set thecontext
to none:language context : none
Stratego-based analysis. This allows you to implement your analysis in Stratego:
language context : legacy observer : editor-analyze
The identifier after the colon refers to the Stratego strategy that performs the analysis. It must take as input a 3-tuple
(ast, path, projectPath)
. As output it must produce a 4-tuple(ast, error*, warning*, note*)
. The following Stratego code is an example of a strategy that implements this signature:editor-analyze: (ast, path, projectPath) -> (ast', errors, warnings, notes) with ast' := <analyze> ast ; errors := <collect-all(check-error)> ast' ; warnings := <collect-all(check-warning)> ast' ; notes := <collect-all(check-note)> ast'
NaBL/TS based analysis. This uses the NaBL and TS meta-languages for name and type analysis. Your project must have been generated with NaBL+TS as the analyzer. It will produce the following ESV configuration:
language context : taskengine observer : editor-analyze (multifile)
NaBL2 based analysis. This uses the NaBL2 meta-language for name and type analysis. Your project must have been generated with NaBL2 as the analyzer. It will produce the following ESV configuration:
language observer : editor-analyze (constraint)
By default, the NaBL2 analyzer works in single-file mode and does not consider multi-file name resolution. To enable that, add the
(multifile)
modifier:language observer : editor-analyze (constraint) (multifile)
Reference Resolution¶
Reference resolution takes an AST node containing a reference, and tries to resolve it to its definition. The resolution is performed by a Stratego strategy, but is configured in ESV under the references
section:
references
reference _ : editor-resolve
The identifier after the colon refers to the Stratego strategy that performs the resolution. The Stratego strategy takes an AST node, and either fails if it could not be resolved, or returns an AST node that has an origin location pointing to the definition site.
If you use the NaBL and TS or NaBL2 meta-language to implement name and type analysis, the provided editor-resolve
strategy implements resolution generically.
Stratego¶
The JAR and CTree files that will be loaded into the Stratego runtime for your language can be configured with the provider
option:
language
provider : target/metaborg/stratego.ctree
The extension of the provider should match the format in the metaborg.yaml
file of your language.
Multiple files can be set by setting the option multiple times:
language
provider : target/metaborg/stratego.ctree
provider : target/custom1.jar
provider : target/custom2.ctree
Main File¶
ESV currently does not have a configurable main file. The main ESV file of your language must be located at editor/Main.esv
or editor/main.esv
. Every ESV file that is (transitively) imported from the main ESV file is used.