Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: Analysis Service
---

(((TODO)))
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: Build Service
---

(((TODO)))
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: Call Hierarchy Service
---

(((TODO)))
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: Code Action Service
---

(((TODO)))
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: Code Lens Service
---

(((TODO)))
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: Definition Service
---

(((TODO)))
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: Document Symbol Service
---

(((TODO)))
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: Execution Service
---

(((TODO)))
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: Hover Service
---

(((TODO)))
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: Implementation Service
---

(((TODO)))
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: Inlay Hint Service
---

(((TODO)))
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
title: "VScode and the Language Server Protocol"
keywords:
- LSP
- VScode
- "Visual Studio Code"
- "Language Server Protocol"
---

#### Synopsis

Recipes for creating an IDE for your language based on the Language Server Protocol using ((util::LanguageServer)) and ((util::IDEServices))

#### Syntax

#### Types

#### Function

#### Description

Rascal's VScode extension comes with a high-level [Language Server Protocol API]((org.rascalmpl.rascal-lsp)) builtin. The library module ((util::LanguageServer)) can be used to rapidly develop an IDE for your own domain specific language or programming language.

You can work in small steps:
1. First create a ((ParsingService)) and then ((RegisterYourLanguage)). This gives your users:
* ((SyntaxHighlighting)) which can be further [configured]((SyntaxHighlighting)).
* Parse error diagnostics and/or error recovery (see ((ParsingService)))
2. Then you can **optionally** and **independently** add editor services one-by-one:
* the ((SelectionRangeService)) provides an easy and quick way to select the right pieces of DSL code for the user.
* the ((DocumentSymbolService)) provides a linked outline view and symbol based search in the editor.
* the ((HoverService)) provides quick (on-demand) documentation in the editor with a tooltip. See also later ((AnalysisService)) and ((BuildService)) for pre-computing documentation information.
* the ((ReferencesSevice)) and
* ((DefinitionService)) and
* ((ImplementationService)) provide quick (on-demand) links to either all references, all definitions of a symbol, or all implementations of a symbol in the editor (with a hyperlink). See also later ((AnalysisService)) and ((BuildService)) for pre-computing reference, definition and implementation information.
* the ((AnalysisService)) services provides errors and warnings for the user as diagnostics in the IDE (e.g. type checking), while the user is typing in the editor.
* the ((BuildService)) triggers a compiler or another language processor. It also produces errors and warnings for the user, but only when a file has been saved.
* ((ExecutionService)) provides an execution mechanism for your own editor commands that can be triggered by the user:
* Using ((AnalysisService)) or ((BuildService)) you can attach them to error messages to provide _quick fixes_.
* Using ((CodeLensService)) or ((InlayHintService)) which both provides information "in between the lines" of your DSL code, which actionable hyperlinks to your ((ExecutionService)) commands.
* Using ((CodeActionService)) which provides a low-key lightbulb menu of context-specific actions (like quick-fixes and refactoring)
* ((CallHierarchyService)) provides an on-demand, lazy browser for the "call graph" of a PL or DSL.
* ((RenameService)) offers language-specific renaming to your users.

So start with the ((ParsingService)) and ((RegisterYourLanguage)), then pick which IDE feature you'd like to provide to your users first, and just go with that.

#### Examples

#### Benefits

* There are only two small API modules relevant for constructing full featured IDEs:
* ((util::LanguageServer)) for building an LSP server that connects to the VScode client.
* ((util::IDEServices)) for programmatically calling IDE effects (like opening editors and starting web views).
* There is no need for a "second level" (starting up another VS code instance) to test your new extension. All you need is to ((RegisterYourLanguage)) and your language will be added to your IDE here and now.
* Your services code "sees" always the code that the user sees in their editor. Even if the file is unsaved, or the file comes from `aVeryWeirdURIScheme:///what?`, all of Rascal's IO features are rerouted implicitly to see the editor contents.
* Your LSP services are based on the metaprogramming facilities of Rascal, and IDE construction is just a form of meta-programming (code in -> UI information out).
* The Rascal LSP API functions are pure, and work with only immutable data-types. This makes it easy to test and/or debug independently of any editor client. Managing editor state and implementing the asynchronous LSP protocol is hidden under-the-hood.
* Your LSP services once made for VScode, will work for any IDE client we port the [LSP bridge]((org.rascalmpl.rascal-lsp)) to.
* You do not have to write a full type-checker or a full compiler, to start giving your users valuable features like error checking, overviews and hyperlinks. Start small. Dream big.
* All changes to DSL files are applied via collecting ((((TextEdits-FileSystemChange))))s, which is a kind of `diff` format. If you stick with this protocol rather then writing to disk yourself, then the IDE will integrate all changes into the undo/redo stack, and can also provide confirmation dialogs and/or previews.
* Later you can decide to deploy your own VScode extension with no changes to your ((util::LanguageServer)) services code.

#### Pitfalls

* It is easy to add many useful commands and features for yours users, but all of them have to have a clear and predictable semantics and all of them must be maintained in the future. It makes sense to create services "on demand", as your users ask for them. Too many options is confusing. Also not every DSL needs every programming language feature, even though it is easy to construct it with Rascal and the ((util::LanguageServer)) API.
* In VScode, if your ((ExecutionService)) changes any DSL files on a project, which are not in an open editor, then editors are opened for each file and it is up to the user to "save" them and commit to the changes. It's the same for ((RenameService)) and any other side-effect applied to code files you build into your Rascal code.
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
---
title: Parsing Service
sidebar_position: 1
---

#### Synopsis

A parser is the first and foremost thing you need for constructing an LSP server/IDE.

#### Examples

Type in a ((Rascal:SyntaxDefinition)), also known as a context-free grammar, for your language.

Here is an example that defines a very small programming language called "Pico". We will use
this language throughout all recipes for the ((LanguageServerProtocol)).

```rascal-include
demo::lang::Pico::Syntax
```

It's important that you import the `ParseTree` module to be able to call the ((ParseTree-parse)) function:
```rascal-commands
import ParseTree;
```

and then you can write your own parser function that wraps the `#start[Program]` non-terminal:
```rascal-commands,continue
import demo::lang::Pico::Syntax;
start[Program] parsePico(str contents, loc origin)
= parse(#start[Program], contents, origin);
```

The parse function must take a `start` non-terminal as parameter (so not just `#Program`), because otherwise
spaces, newlines and comments before and after the main `Program` content will lead to parse errors.

With the above function every parse error will lead to an error diagnostic in the editor, _and_
syntax highlighting only works if there is no parse error. To help your users a bit, you can
activate parse error recovery:

```rascal-commands
import ParseTree;
import demo::lang::Pico::Syntax;
start[Program] parsePicoWithRecovery(str contents, loc origin)
= parse(#start[Program], contents, origin, allowRecovery=true);
```
Now syntax highlighting will indicate which part of the file has been recognized and
which part of the file has not. The parse errors will still appear in the Diagnostics view.


```rascal-prepare
import ParseTree;
import demo::lang::Pico::Syntax;
start[Program] parsePico(str contents, loc origin)
= parse(#start[Program], contents, origin);
```

It is always a good idea to test your parser in the terminal:
```rascal-shell,continue
parsePico("begin declare a: natural; a := 42 end", |demo:///|)
```
And to find out what a parse error looks like:
```rascal-shell,continue,errors
parsePico("begin declare a: natural; a = 42 end", |demo:///|)
```

Or you could write a test function for it, for future reference:
```rascal-commands,continue
test bool testPicoParser() {
return start[Program] _ := parsePico("begin declare a: natural; a := 42 end", |demo:///|);
}
test bool testErrorPicoParser() {
try {
parsePico("begin declare a: natural; a = 4$2 end", |demo:///|);
return false;
}
catch ParseError(_): {
return true;
}
}
```

In general a ((ParsingService)) is simply a function that satisfies the ((util::LanguageServer-Parser)) signature.

Now let's move on to [registering your language with the IDE]((RegisterYourLanguage)) and
run your own language server.

#### Benefits

* you can always test any service function in the terminal. This is highly recommended because simple errors and output can sometimes be hard to find in the IDE.
* you can always write Rascal test functions to add to the stability of your LSP services.
* the ((ParseTree-Tree))s produced by your ((ParsingService)) will be the input of all other services later.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: References Service
---

(((TODO)))
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
---
title: Register your language
sidebar_position: 2
---

#### Synopsis

Start a new VScode extension for your language

#### Description

We assume you have already written a ((ParsingService)) function. Now think of:
* a name for your language;
* a file extension for its files.

```rascal-module
With the following shell commands you register your language with the IDE:
```rascal-module
module MyLanguageServer

import util::LanguageServer; // <1>
import demo::lang::Pico::Syntax; // <2>

start[Program] myParsingService(str s, loc l) { // <3>
return parse(#start[Program], s, l);
}

// highlight-start
set[LanguageService] myLanguageServices() { // <4>
parser(myParsingService);
}
// highlight-end

void main() { // <5>
// highlight-next-line
registerLanguage( // <6>
// highlight-next-line
language( // <7>
pathConfig(), // <8>
"Pico", // <9>
{"pico"}, // <10>
"MyLanguageServer", // <11>
"myLanguageServices" // <12>
)
) ;
}
```

1. Load the LSP API we need later
2. Load the ((Rascal:SyntaxDefinition)) needed for our ((ParsingService))
3. Define a ((ParsingService)) (could also be already define in the imported module)
4. Collect all relevant language services. The function _must not have positional parameters_ and return a `set[LanguageService]`. You can keep adding new services here to the set, and keep the other code the same.
5. Having a `main` function is not obligatory, but when you deploy the extension later as an independent extension you will need it anyway.
6. The call to ((registerLanguage)) does the final job of extending the current IDE with your own extension. A whole new Rascal runtime environment will be started just for your extension. Modules will be loaded, and the services will be connected to LSP callbacks, etc.
7. The ((util::LanguageServer-language)) constructor provides five pieces of meta data:
8. ((util::PathConfig::pathConfig)) is used to configure the Rascal runtime environment for loading your services. This comes in handy when you have third-party dependencies.
9. This is the UI facing name of your language.
10. These are the file extensions that activate the current IDE extension.
11. This is the top module to import into the Rascal runtime environment for this extension.
12. This is the name of the function in the top module which provides a `set[LanguageService]` when called with no arguments.

Then you simply call `main()` and you can open an editor with the file extension `pico`.
The first time the parser will be generated and cached, and when it is finished ((SyntaxHighlighting)) will show you the success of the parse.

That's it!

Now you can continue, for example, with ((SyntaxHighlighting)) or ((HoverService)) as two of
the ((LanguageServerProtocol)) features to try out.

#### Benefits

* repeated calls to ((registerLanguage)) re-initialize your language extension from scratch

#### Pitfalls

* language extensions are not refreshed automatically if you change their implementation. You have to keep calling ((registerLanguage)) for this
* (Accidental) console output, logging and unexpected error messages appear in VScode `OUTPUT` tabs, but these tabs are not automatically floating to the top. It's better to debug your services in the terminal.

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: Rename Service
---

(((TODO)))
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: Selection Range Service
---

(((TODO)))
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: Syntax Highlighting
---

(((TODO)))
5 changes: 5 additions & 0 deletions courses/Recipes/UserInterfaces/SalixRecipes/SalixRecipes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: Constructing web-based user-interfaces with Salix
---

(((TODO)))
47 changes: 47 additions & 0 deletions courses/Recipes/UserInterfaces/UserInterfaces.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
title: User Interfaces
keywords:
- userinterfaces
- visualization
- UX
- UI
- HTML
- HTML5
- Servlets
- WebServer
---

#### Synopsis

Recipes for writing Rascal programs that create interactive _visual_ user interfaces.

#### Syntax

#### Types

#### Function

#### Description

These recipes are useful if have one of the following intentions for your programming language or DSL:
* creating a software visualization tool (interactive or not)
* creating an interactive or even live programming environment
* creating an full-featured editor
* creating a coupled visualization (code <-> visual)
* creating visual statistical overviews and reports of your empirical data (extracted from code)
* creating visual modeling tools
* etc.

These recipes fall into three major technological categories:
* ((VanillaHTML5)) recipes explain how to generate and serve HTML5 content directly from Rascal into your IDE web view or into your Desktop browser.
* ((SalixRecipes)) explain how to use the [Salix]((org.rascalmpl.salix-core)) framework to build user interfaces for the browser (or the IDE web view) in Rascal.
* ((LanguageServerProtocol)) recipes explain how to construct a full featured IDE with Visual Studio Code and the Language Server Protocol.

#### Examples

#### Benefits



#### Pitfalls

Loading
Loading