๐ Latest Patch Notes can be found here
๐ Q&D Issue Tracker can be found here
๐ Documentation is a high priority... waiting for an incoming API rejig to land first ๐
Popular solutions for x-platform programming offer convenience to the developer at the expense of the user experience.
These solutions abstract away the semantics of the target environment (APIs, memory model, rules etc), giving the impression that these platforms can be considered equivalent.
Not only does this require layers of complex, hard to debug framework glue, it also prevents developers from being able to truly write native, semantic, idiomatic or optimal code.
However, Sempiler puts semantics at the forefront of development, allowing you to use your favourite programming language to produce code that is all of those things!
Sempiler empowers teams to emit native code for any platform, without switching language.
- Decouple syntax from semantics
- No more developer time wasted learning new syntax
- No more frustrating typos caused by switching between similar languages
- High level primitives for common concepts like types, networking, and concurrency
- Native emissions to any language, without virtual machines or frameworks
Sempiler empowers teams to introspect, instrument, and optimise their code, from their code.
- Write build logic in the same language as your business logic
- Dictate how the compiler transforms your code, to match your use case
- No need to configure a flaky and fragmented toolchain
- Mix and match plugins across a fully programmable pipeline
- Compile time arbitrary code execution
- X-platform zero overhead native code emission (native emissions, no frameworks or VMs)
- Multiple artifact generation from single codebase
The compiler can be leveraged in several ways.
The Proto
project can be built and then passed a single input file path.
You can see an example of this in the .vscode/launch.json
for the Compile
config. It compiles the รnputs/src/app.ts
file.
There is a basic VSC plugin implementation in Distribution/VisualStudioCode
that can be used for a more interactive development experience, with diagnostics underlined in editor.
Under the hood the VSC plugin uses the CLI interface, which really just creates a duplex socket server over which it accepts and interprets commands.
This is similar to how the compiler performs compile time code execution.
The compiler can parse and emit native code in a variety of programming languages.
There are no hard constraints on suitable source languages, though typed source languages are preferable when emitting to typed target languages (for correctness and/or more accurate declarations).
Hopefully the supported source languages list will grow over time, making it trivial to link source files/libraries expressed in different languages.
Currently supported source languages:
- TypeScript (.ts files)
- TSX ie. TypeScript + JSX Tags (.tsx files)
NOTE There is no need to use TypeScript declaration files (.d.ts
) because the code you write is transpiled to the target context where it is actually type checked/executed. Visual Studio Code users should disable validation to prevent it complaining about missing symbol definitions.
There are no hard constraints on target languages, and hopefully the supported target languages list will grow over time.
Currently supported target languages:
- Swift 5 ๐
- Java 9 โ
- TypeScript 3
- JavaScript
- WASM
The compiler can produce artifacts for different environments.
Emitted artifacts contain the transpiled/transformed source files, as well as any autogenerated shard files required by the platform (eg. plists, podfile, manifests etc.).
Currently supported target platforms:
- iOS 13 ๐
- Android ๐ค
- Zeit Now โผ๏ธ
- Firebase Functions ๐ฅ
- Express
Look at the sample in Inputs/firebase-functions
.
If you open this repository in Visual Studio Code there should be a launch configuration called FirebaseFunctions
.
Running that should create and populate the Inputs/firebase-functions/out
directory with a bundle set up to be deployed via the Firebase CLI.
Assuming you have the Firebase CLI installed and configured (eg. you are logged in to Firebase), you should be able to run the following script:
cd Inputs/firebase-functions/out/server/functions && \
npm i && \
npm run build && \
cd ../ && \
firebase emulators:start --only functions,hosting --project <your-firebase-project-id>
# Use the local server address, and append the name of one of the functions. eg. http://localhost:5000/hello
# (TIP : make sure you are using the correct HTTP verb!)"
- Web
Control over your code does not stop once you hand it to the compiler. You can control the compilation process from your source code.
This is useful for:
- Optional code inclusion (dependent on variables like the build config, or target language/platform)
- Code generation (generating repetitive or boilerplate code, rather than having to write and maintain it)
- AST mutations (using APIs to perform bespoke transformations during the build, like how Sempiler transforms view declarations)
- Business logic assertions (check a switch is exhaustive, or that business logic is upheld, rather than use unit tests)
- Syntactic polyfills (like how Sempiler transforms the following TypeScript snippet
foo(...{ x : "hello", y : "world"})
, into named arguments in Swiftfoo(x:"hello", y:"world")
) - Target platform meta (conditionally adding dependencies, orientation, capabilities, entitlements and permissions for the artifact being generated)
And because compile time logic is expressed directly in source files, it can be shared between projects just like a regular runtime dependency.
Use the #compiler
directive to write code that will be executed at compilation time.
Note, all compile time code execution is performed in JavaScript with Node, regardless of the target language/platform being generated.
The syntax is:
#compiler <expression>
Compiler directives are evaluated in the depth first traversal order.
Whilst the operand can be of any form, the result of evaluating the actual directive is void. This means that #compiler
directives are removed entirely from the program AST after compilation time, and are not present in the final emitted artifact.
In the following case the operands are expressions, and executed in the first, second and third order as per their names:
#compiler first();
function foo()
{
#compiler second();
}
#compiler third();
Compile time execution can interact and mutate the entire program AST, but can also reference static or constant symbols - allowing some code in your program to be available at compile time and run time.
#compiler const myValue = square(3)
function square(n : number)
{
return n * n;
}
The square
function has no dependency on run time symbols and so is able to be invoked at compile time - but will also be present in the emitted artifact for invocation at run time.
The compiler API is barebones at present, but allows for the following:
addArtifact(name : string, targetLanguage : string, targetPlatform : string, entrypointSourcePath : string)
declares an artifactaddCapability(name : string, value : string | string[])
declares a capability that the artifact provides (eg.addCapability("UIBackgroundModes", ["audio"])
)addDependency(name : string, version? : string)
declares a dependency that the artifact requires (eg. Stripe)addEntitlement(name : string, value : string | string[])
declares a target platform entitlement that the artifact requires (eg. Apple Pay)addManifestEntry(path : string, value : string | string[])
declares a target platform manifest entry (eg. an entry in the iOS .plist file) (hint : use '/' separator to qualify the path)addPermission(name : string, description : string)
declares a target platform permission that the artifact requires (eg. camera access)addRawSources(...relativePaths : string[])
adds verbatim files to the artifact (ie. they will not be parsed/transformed)addSources(...relativePaths : string[])
add files that will be parsed/transformed and added to the artifactaddRes(path : string, targetFileName? : string)
adds verbatim resource files to the artifact (ie. they will not be parsed/transformed). Optionally set the target file name when thepath
resolves to a single fileaddShard(role : ShardRole, entrypointSourcePath : string)
add shard (eg share extension) to the ArtifactaddAsset(role : AssetRole, sourcePath : string)
add asset (eg image) to the ShardsetDisplayName(name : string) : void
to set the display name of the generated artifactsetTeamName(name : string) : void
to set the team name of the generated artifactsetVersion(version : string) : void
to set the version of the generated artifactisTargetPlatform(name : string) : bool
to check whether the artifact being generated is for the given target platformisTargetLanguage(name : string) : bool
to check whether the artifact being generated is for the given target language
- AST operations (currently only available in the C# engine, not the compile time execution context)
- Post compile time code execution (for running post build tasks, like invoking the target language compiler - with diagnostics mapped back to source code)
#compiler <declaration>
allow for symbols to be defined for compile time and referenced in the same scope
Use the #emit
directive to write code that will be executed at compilation time to generate code that will be incuded in the emitted artifact
The syntax is:
#emit <interpolated string>
The emit feature can be useful for including some platform specific code verbatim, especially if it contains syntactic constructs not available in your source language (like #available(iOS 11.0, *)
below):
#emit`if #available(iOS 11.0, *)
{
let window = UIApplication.shared.windows[0]
let safeFrame = window.safeAreaLayoutGuide.layoutFrame
topSafeAreaHeight = safeFrame.minY
bottomSafeAreaHeight = window.frame.maxY - safeFrame.maxY
}`
The interpolated string can contain symbols, as long as they can be resolved during compile time.
A typical need for x-platform native emissions involves UI driven experiences, like mobile apps.
To facilitate reusable views x-platform Sempiler riffs heavily off the React-inspired functional components.
You can define a view as a function, and a compile time translation will transform the declaration to it's equivalent in the context of the target platform/language.
function MyView(
name : string,
@ObservedObject foo : Foo
) : View
{
return (
<Column>
<Row>
<Text text={`Hello ${name}!`} />
</Row>
</Column>
)
}
- Function declaration with a
View
return type annotation - Uses compiler primitives like
Column
/Row
(transformed toVStack
/HStack
in SwiftUI) - Inspired by SwiftUI's reactive binding annotations (
@ObservedObject
,@EnvironmentObject
,@State
,@Binding
)
- Parity for reactive binding support in Litho
Currently supported target view frameworks:
- Litho (Android) ๐ค
- SwiftUI (iOS) ๐
Project dependencies are often listed in shard files, such as manifests.
A goal of Sempiler is to allow the developer to dictate all aspects of a project directly from source code:
#compiler addDependency(name : string, version? : string)`
- The directive to tell the compiler to execute the operand at compile time
addDependency(...)
will add the dependency to the relevant shard file for the target platform (eg. podfile)
Currently supported target package managers:
- CocoaPods
- NPM
Sempiler automatically parses the location (ie. file, line, column) of diagnostic messages (infos, warnings, errors) from the transformed target code back to the problematic line in your source code.
- GCC family
- JavaC โ
- Step through debugging in process
Dependencies fall into two categories, whether they are dependencies for an emitted artifact, or whether they are dependencies to build the code in this repository.
- Node (for compile time code execution by JavaScript)
- iOS only Ruby (iOS only - Sempiler creates an
init.rb
that uses xcodeproj to generate an.xcworkspace
file) - iOS only macOS Catalina (for SwiftUI, iOS 13.0, SwiftC, xcrun, xcbuild, simctl)
- iOS only CocoaPods (for dependency management)
The Sempiler source code requires the following dependencies to build the actual compiler:
- .NET Core 3.0
Here are various related links for the project.