/pico8code

An attempt at language support for PICO-8's Lua

Primary LanguageTypeScriptMIT LicenseMIT

This extension is no longer maintained; if you are looking proper language support anyway, remember that PICO-8's Lua is still Lua.

An Other PICO-8 VSCode Extension

A first attempt at language support for PICO-8's Lua.

Aims at being helpful.

Barely functional, obnoxiously broken.

To only have the coloration, see the setting pico8.parse.dontBother (disabling diagnostics, completions, typing and such).

Functionality

This extension aims at providing the following language features:

The Language Server activates for PICO-8 sources (.p8, text).

Somewhat Important Points

  • not extensively tested, no QA
  • no parsing recovery (stops on first syntax error)
  • no type literal (eg. like type A = 'da' | 'di' | 'do' in TS)
  • union types are not accounted for in type checking (diagnostics)
  • no typing for a function's return from within the function itself (because of function a() return a end)
  • same with scope within init of assignments (like a = function() return a end)
  • inaccurate coloration for types representations (eg. in hovers/completion)
  • text document synchronization is not incremental (so quite heavy for larger size files)
  • no semantic tokens (eg. table() will be wrongly colored as a function)

Coloration

Lua code have syntactic coloration applied from after a line starting with __lua__ until any other __xyz__.

This was originally stolen from the pico8-vscode VS Code extension, although heavily modified.

Functions from the PICO-8 API are identified as being support.class.lua but that's just so that they stand out.

Include

PICO-8 relies on a preprocessor that supports a form of include directive:

pico-8 cartridge

__lua__
-- this will include `file.p8` which defines `cool_function`
#include file.p8

cool_function()

The behavior of this directive depends on the content of the target file; if it starts with a valid PICO-8 header (pico-8 cartridge anywhere on the first line) then only the __lua__ sections are included, otherwise the whole file is included as code.

Because this allow for very broken syntaxes that would require parsing the included file, the extension's underlying parser does not accept this directive. As a result, global variables and functions declared in the included file will not be accounted for by this extension. These may be added using the pico8code.parse.preDefinedGlobals setting when applicable.

Note: the preprocessing does not resolve nested inclusions; any #include within the file file.p8 in the example above will not be preprocessed and will probably throw a syntax error at runtime.

Diagnostics

Reports

  • function signature conflicting with its documentation (if any)
  • parameter type not matching with the function's in call expression
  • where a number was expected (eg. in operations like a + b)
  • where a table was expected (eg. in c["key"] or c.key)
  • where a function was expected (eg. in d(), d{}...)
  • potentially unwanted shadowing/multiple local definitions (apparently not)

Completions

When enabled, the language server will try to provide completions and function signatures.

Functions from the API are added as part of the global scope, and their typing is wrong.

Doc Comments

The language server will rely on any longstring comments the line before something. When such is found, it may consist of a type on the first line (see typing for syntaxes).

Example:

--[[ (a: number, b: number) -> number
	adds two numbers
]]
function add2(a, b)
	return a+b
end

The content of a doc comment can be markdown (but no html).

API Lookups

Using doc comments, functions from the API are somewhat documented and link to the wiki.

Typing

Or the idea of anyway.

Simple Types

string, number, boolean and nil.

Unions can be made using this syntax: string | number "either a string or a number".

Function Type

The general syntax is (params) -> return.

Takes 2 numbers and returns a number:

(a: number, b: number) -> number

Takes a string and return either a number or nil (note the parenthesis around the return):

(c: string) -> (number | nil)

Takes either a number or a boolean and return 3 strings:

(d: number | boolean) -> [string, string, string]

No parameter, return either a string and a number or two nils:

() -> ([string, number] | [nil, nil])

Side note: when inferring the type of the following

function fun()
	return fun
end

.. the type of fun ends up being () -> () -> nil

Table Type

Tables support both index and dotted notations; the general syntax is { !something!: type }, where !something! may be:

  • an identifier: { size: number }
  • an indexing expression: { ["the name"]: string, [0]: number, [false]: boolean }
  • a generic index type (id is just a label): { [id: string]: string }

Of course the type can be a table or a function:

{ [: string]: { name: string, position: { x: number, y: number }, speed: number }, update: () -> nil }

Side note: when inferring the type of the following

tab = {}
tab.tab = tab

.. the type of tab ends up being { tab: *circular* }


The Repo

Structure

.
├── client
│   ├── src
│   │   └── extension.ts   // language client (and extension) entry point
│   └── package.json
│
├── server
│   ├── src
│   │   ├── document
│   │   │   ├── explore.ts // explores the AST and augments it (eg. with expression typing)
│   │   │   └── typing.ts  // deals with types (eg. to and from string representation)
│   │   ├── documents.ts
│   │   ├── server.ts      // language server entry point
│   │   ├── settings.ts
│   │   └── util.ts
│   └── package.json
│
└── package.json           // the extension manifest

Powered By

Relies on the pico8parse and the vscode-languageserver/node.


  • TODO count: 12
  • XXX count: 19
  • as any count: 46