diff --git a/docs/docs/languages/zig-wasm.mdx b/docs/docs/languages/zig-wasm.mdx
new file mode 100644
index 0000000000..e19b213395
--- /dev/null
+++ b/docs/docs/languages/zig-wasm.mdx
@@ -0,0 +1,124 @@
+# Zig (Wasm)
+
+Zig is a general-purpose programming language and toolchain for maintaining robust, optimal, and reusable software.
+
+In LiveCodes, Zig runs in the browser using WebAssembly with a WASI-compatible runtime environment.
+
+## Usage
+
+Demo:
+
+import LiveCodes from '../../src/components/LiveCodes.tsx';
+export const zigConfig = {
+ activeEditor: 'script',
+ script: {
+ language: 'zig-wasm',
+ content: `const std = @import("std");
+
+pub fn main() !void {
+ const stdout = std.io.getStdOut().writer();
+
+ const sorted_array = [_]i32{ 1, 3, 5, 7, 9, 11, 13, 15 };
+ const item_to_search: i32 = 7;
+
+ const result = binarySearch(i32, &sorted_array, item_to_search);
+
+ if (result == -1) {
+ try stdout.print("Result: Item not found in the array.\\n", .{});
+ } else {
+ try stdout.print("Result: Item found at index -> {}\\n", .{result});
+ }
+}
+
+fn binarySearch(comptime T: type, arr: []const T, item: T) i32 {
+ var left: usize = 0;
+ var right: usize = arr.len;
+
+ while (left < right) {
+ const mid = left + (right - left) / 2;
+
+ if (arr[mid] == item) {
+ return @intCast(mid);
+ }
+
+ if (arr[mid] > item) {
+ right = mid;
+ } else {
+ left = mid + 1;
+ }
+ }
+
+ return -1;
+}`,
+ },
+ mode: 'simple',
+ editor: 'auto',
+ tools: {
+ status: 'full',
+ },
+};
+
+
+
+### Communication with JavaScript
+
+The Zig code runs in the context of the result page. A few helper properties and methods are available in the browser global `livecodes.zig` object:
+
+- `livecodes.zig.input`: The initial standard input passed to the Zig code.
+- `livecodes.zig.loaded`: A promise that resolves when the Zig environment (WebAssembly runtime) is fully loaded. Other helpers should be used after this promise resolves.
+- `livecodes.zig.output`: The standard output from the Zig code execution.
+- `livecodes.zig.run`: A function that runs the Zig code with new input. This function takes a string as input and returns a promise that resolves with an object containing the `output`, `error`, and `exitCode` properties.
+
+Example:
+
+
+
+## Language Info
+
+### Name
+
+`zig-wasm`
+
+### Aliases / Extensions
+
+`zig`, `zig-wasm`
+
+### Editor
+
+`script`
+
+## Compiler
+
+Zig compiler in WebAssembly.
+
+### Version
+
+Zig 0.14.0
+
+## Code Formatting
+
+
+## Live Reload
+
+By default, new code changes are sent to the result page for re-evaluation without a full page reload, avoiding the need to reinitialize the Zig WebAssembly environment. This behavior can be disabled by adding the code comment `// __livecodes_reload__` to the Zig code, which forces a full page reload.
+
+This comment can be added in the `hiddenContent` property of the editor for embedded playgrounds.
+
+## Example Usage
+
+```zig
+const std = @import("std");
+
+pub fn main() !void {
+ const stdout = std.io.getStdOut().writer();
+ try stdout.print("Hello, LiveCodes Zig!\\n", .{});
+}
+```
+
+## Starter Template
+
+https://livecodes.io/?template=zig-wasm
+
+## Links
+
+- [Zig](https://ziglang.org/)
\ No newline at end of file
diff --git a/docs/src/components/LanguageSliders.tsx b/docs/src/components/LanguageSliders.tsx
index e1daff5ac6..d8284dbd9c 100644
--- a/docs/src/components/LanguageSliders.tsx
+++ b/docs/src/components/LanguageSliders.tsx
@@ -106,6 +106,7 @@ export default function Sliders() {
{ name: 'postgresql', title: 'PostgreSQL' },
{ name: 'prolog', title: 'Prolog' },
{ name: 'blockly', title: 'Blockly' },
+ { name: 'zig-wasm', title: 'Zig (Wasm)' },
],
};
const slides = ['markup', 'style', 'script'];
diff --git a/docs/src/components/TemplateList.tsx b/docs/src/components/TemplateList.tsx
index 126f377f72..27abff3c06 100644
--- a/docs/src/components/TemplateList.tsx
+++ b/docs/src/components/TemplateList.tsx
@@ -67,6 +67,7 @@ const templates = [
{ name: 'prolog', title: 'Prolog Starter', thumbnail: 'tau-prolog.svg' },
{ name: 'blockly', title: 'Blockly Starter', thumbnail: 'blockly.svg' },
{ name: 'diagrams', title: 'Diagrams Starter', thumbnail: 'diagrams.svg' },
+ { name: 'zig-wasm', title: 'Zig (Wasm)', thumbnail: 'zig.svg' },
];
export default function TemplateList() {
diff --git a/functions/vendors/templates.js b/functions/vendors/templates.js
index 10dbce55f7..bc7e0c6a1b 100644
--- a/functions/vendors/templates.js
+++ b/functions/vendors/templates.js
@@ -447,10 +447,10 @@ body {
font-size: 3.5rem;
}
}
-`.trimStart()},script:{language:"javascript",content:""},stylesheets:["{{ __CDN_URL__ }}bootstrap@5.3.0/dist/css/bootstrap.min.css"],scripts:["{{ __CDN_URL__ }}bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"],cssPreset:"",imports:{},types:{}};var y={name:"coffeescript",title:getTemplateName("templates.starter.coffeescript","CoffeeScript Starter"),thumbnail:"assets/templates/coffeescript.svg",activeEditor:"script",markup:{language:"html",content:`
+`.trimStart()},script:{language:"javascript",content:""},stylesheets:["{{ __CDN_URL__ }}bootstrap@5.3.0/dist/css/bootstrap.min.css"],scripts:["{{ __CDN_URL__ }}bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"],cssPreset:"",imports:{},types:{}};var y={name:"civet",title:getTemplateName("templates.starter.civet","Civet Starter"),thumbnail:"assets/templates/civet.png",activeEditor:"script",markup:{language:"html",content:`
Hello, World!
-

+
You clicked 0 times.
@@ -463,25 +463,25 @@ body {
.logo {
width: 150px;
}
-`.trimStart()},script:{language:"coffeescript",content:`
-titleElement = document.getElementById 'title'
-counterElement = document.getElementById 'counter'
-button = document.getElementById 'counter-button'
+`.trimStart()},script:{language:"civet",content:`
+titleElement := document.getElementById 'title'
+counterElement := document.getElementById 'counter'
+button := document.getElementById 'counter-button'
-title = 'CoffeeScript'
+title := 'Civet'
titleElement.innerText = title
-counter = (count) -> -> count += 1
-increment = counter 0
+counter := (count: number) => => count += 1
+increment := counter 0
+function handleClick: void counterElement.innerText = increment()
-button.addEventListener('click',
- -> counterElement.innerText = increment())
-`.trimStart()},stylesheets:[],scripts:[],cssPreset:"",imports:{},types:{}};var x={name:"go",title:getTemplateName("templates.starter.go","Go Starter"),thumbnail:"assets/templates/go.svg",activeEditor:"script",markup:{language:"html",content:`
+button.addEventListener 'click', handleClick
+`.trimStart()},stylesheets:[],scripts:[],cssPreset:"",imports:{},types:{}};var x={name:"clio",title:getTemplateName("templates.starter.clio","Clio Starter"),thumbnail:"assets/templates/clio.png",activeEditor:"script",markup:{language:"html",content:`
-
Hello, World!
-

+
Hello, World!
+
You clicked 0 times.
-
+
`.trimStart()},style:{language:"css",content:`
.container,
@@ -490,62 +490,38 @@ button.addEventListener('click',
font: 1em sans-serif;
}
.logo {
- width: 250px;
+ width: 150px;
}
-`.trimStart()},script:{language:"go",content:`
-package main
-
-import (
- "fmt"
- "syscall/js"
- "time"
-)
-
-func main() {
- title := querySelector("#title")
- title.Set("innerHTML", "Golang")
+`.trimStart()},script:{language:"clio",content:`
+fn capitalize str:
+ (str.charAt 0 -> .toUpperCase) + (str.slice 1 -> .toLowerCase)
- registerCounter()
+fn greet name:
+ f"Hello, {name}!"
- // yes, you can use goroutines (check the console)
- go greet()
- fmt.Println("Hello!")
-}
+fn setTitle name:
+ title = document.querySelector "#title"
+ title.innerText = name -> capitalize -> greet
-func querySelector(id string) js.Value {
- return js.Global().Get("document").Call("querySelector", id)
-}
+fn increment value:
+ (Number value) + 1
-func registerCounter() {
- btn := querySelector("#counter-button")
- counter := querySelector("#counter")
- count := 0
+fn activateBtn btn:
+ btn.disabled = false
+ btn.innerText = "Click me"
+ btn
- var cb js.Func
- cb = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
- count += 1
- counter.Set("innerHTML", count)
- return nil
- })
- btn.Call("addEventListener", "click", cb)
-}
+fn onBtnClick:
+ counter = document.querySelector "#counter"
+ counter.innerText = increment counter.innerText
-func greet() {
- if hours, _, _ := time.Now().Clock(); hours < 12 {
- fmt.Println("Good morning")
- } else if hours < 18 {
- fmt.Println("Good afternoon")
- } else {
- fmt.Println("Good evening")
- }
-}
-`.trimStart()},stylesheets:[],scripts:[],cssPreset:"",imports:{},types:{}};var w={name:"jquery",title:getTemplateName("templates.starter.jquery","jQuery Starter"),thumbnail:"assets/templates/jquery.svg",activeEditor:"script",markup:{language:"html",content:`
-
-
Hello, World!
-

-
You clicked 0 times.
-
-
+export fn main argv:
+ setTitle "clio"
+ document.querySelector "#counter-button"
+ -> activateBtn
+ -> .addEventListener "click" onBtnClick
+`.trimStart()},stylesheets:[],scripts:[],cssPreset:"",imports:{},types:{}};var w={name:"clojurescript",title:getTemplateName("templates.starter.clojurescript","ClojureScript Starter"),thumbnail:"assets/templates/cljs.svg",activeEditor:"script",markup:{language:"html",content:`
+Loading...
`.trimStart()},style:{language:"css",content:`
.container,
.container button {
@@ -553,24 +529,43 @@ func greet() {
font: 1em sans-serif;
}
.logo {
- width: 300px;
+ width: 150px;
}
-`.trimStart()},script:{language:"javascript",content:`
-import $ from "jquery";
+`.trimStart()},script:{language:"clojurescript",content:`
+(ns react.component
+ (:require
+ ;; you may use npm packages
+ ["canvas-confetti$default" :as confetti]
+ ["react$default" :as React]
+ ["react" :refer [useState]]
+ ["react-dom/client" :refer [createRoot]]))
-$("#title").text('jQuery');
+(defn Counter [^:js {:keys [name]}]
+ (let [[counter setCount] (useState 0)]
+ #jsx [:div
+ {:className "container"}
+ [:h1 (str "Hello, " name "!")]
+ [:img
+ {:className "logo"
+ :alt "logo"
+ :src "{{ __livecodes_baseUrl__ }}assets/templates/cljs.svg"}]
+ [:p "You clicked " counter " times."]
+ [:button
+ {:onClick (fn []
+ (if (= (mod counter 3) 0) (confetti))
+ (setCount (inc counter)))}
+ "Click me"]]))
-let count = 0;
-$("#counter-button").click(() => {
- count += 1;
- $("#counter").text(count);
-});
-`.trimStart()},stylesheets:[],scripts:[],cssPreset:"",imports:{},types:{}};var S={name:"knockout",title:getTemplateName("templates.starter.knockout","Knockout Starter"),thumbnail:"assets/templates/knockout.svg",activeEditor:"script",markup:{language:"html",content:`
+(def title "ClojureScript")
+(print (str "Hello, " title "!"))
+(defonce root (createRoot (js/document.querySelector "#app")))
+(.render root #jsx [Counter #js {:name title}])
+`.trimStart()}};var S={name:"coffeescript",title:getTemplateName("templates.starter.coffeescript","CoffeeScript Starter"),thumbnail:"assets/templates/coffeescript.svg",activeEditor:"script",markup:{language:"html",content:`
-
Hello, World!
-

-
You clicked 0 times.
-
+
Hello, World!
+

+
You clicked 0 times.
+
`.trimStart()},style:{language:"css",content:`
.container,
@@ -579,27 +574,25 @@ $("#counter-button").click(() => {
font: 1em sans-serif;
}
.logo {
- width: 250px;
+ width: 150px;
}
-`.trimStart()},script:{language:"javascript",content:`
-import ko from "knockout";
+`.trimStart()},script:{language:"coffeescript",content:`
+titleElement = document.getElementById 'title'
+counterElement = document.getElementById 'counter'
+button = document.getElementById 'counter-button'
-class ClickCounterViewModel {
- constructor() {
- this.title = 'Knockout';
- this.numberOfClicks = ko.observable(0);
+title = 'CoffeeScript'
+titleElement.innerText = title
- this.registerClick = function () {
- this.numberOfClicks(this.numberOfClicks() + 1);
- };
- }
-}
+counter = (count) -> -> count += 1
+increment = counter 0
-ko.applyBindings(new ClickCounterViewModel());
-`.trimStart()},stylesheets:[],scripts:[],cssPreset:"",imports:{},types:{}};var k={name:"livescript",title:getTemplateName("templates.starter.livescript","LiveScript Starter"),thumbnail:"assets/templates/livescript.svg",activeEditor:"script",markup:{language:"html",content:`
+button.addEventListener('click',
+ -> counterElement.innerText = increment())
+`.trimStart()},stylesheets:[],scripts:[],cssPreset:"",imports:{},types:{}};var k={name:"commonlisp",title:getTemplateName("templates.starter.commonlisp","Common Lisp Starter"),thumbnail:"assets/templates/commonlisp.svg",activeEditor:"script",markup:{language:"html",content:`
-
Hello, World!
-

+
Hello, World!
+
You clicked 0 times.
@@ -612,31 +605,70 @@ ko.applyBindings(new ClickCounterViewModel());
.logo {
width: 150px;
}
-`.trimStart()},script:{language:"livescript",content:`
-{ capitalize, join, map, words } = require 'prelude-ls'
-
-title = 'live script'
-|> words
-|> map capitalize
-|> join ''
-
-(document.getElementById \\title).innerText = title
+`.trimStart()},script:{language:"commonlisp",content:`
+(defun set-attribute (&key selector attribute value)
+ (let ((node
+ (#j:document:querySelector selector)))
+ (setf (jscl::oget node attribute) value)
+ node))
-increment = (count) -> -> count += 1
-counter = increment 0
+(let ((title "Common Lisp"))
+ (set-attribute :selector "#title" :attribute "innerHTML"
+ :value (format nil "Hello, ~A!" title)))
-counter-element = document.getElementById \\counter
-button = document.getElementById \\counter-button
+(let ((counter 0))
+ (set-attribute :selector "#counter-button" :attribute "onclick"
+ :value #'(lambda (ev)
+ (setf counter (+ counter 1))
+ (set-attribute :selector "#counter" :attribute "innerHTML"
+ :value counter))))
-button.addEventListener \\click,
- -> counter-element.innerText = counter!
-`.trimStart()},stylesheets:[],scripts:[],cssPreset:"",imports:{},types:{}};var _={name:"lua",title:getTemplateName("templates.starter.lua","Lua Starter"),thumbnail:"assets/templates/lua.svg",activeEditor:"script",markup:{language:"html",content:`
+(#j:console:clear)
+(write "Hello, Common Lisp!")
+`.trimStart()},stylesheets:[],scripts:[],cssPreset:"",imports:{},types:{}};var _={name:"cpp",title:getTemplateName("templates.starter.cpp","C++ Starter"),thumbnail:"assets/templates/cpp.svg",activeEditor:"script",markup:{language:"html",content:`
-
Hello, World!
-

-
You clicked 0 times.
+
Hello, World!
+

+
You clicked 0 times.
+
+
+
+`.trimStart(),
+ },
+ style: {
+ language: 'css',
+ content: `
+.container,
+.container button {
+ text-align: center;
+ font: 1em sans-serif;
+}
+.logo {
+ width: 150px;
+}
+`.trimStart(),
+ },
+ script: {
+ language: 'zig-wasm',
+ content: `
+const std = @import("std");
+
+pub fn main() !void {
+ const stdout = std.io.getStdOut().writer();
+ const stdin = std.io.getStdIn().reader();
+
+ const title = "Zig";
+ try stdout.print("{s}\\n", .{title});
+
+ var buf: [100]u8 = undefined;
+ const input = try stdin.readUntilDelimiterOrEof(&buf, '\\n');
+ const count = try std.fmt.parseInt(i32, input.?, 10);
+ try stdout.print("{}\\n", .{count + 1});
+
+}
+`.trimStart(),
+ },
+};
diff --git a/src/livecodes/vendors.ts b/src/livecodes/vendors.ts
index 3ee6afdcfc..50fc033e3e 100644
--- a/src/livecodes/vendors.ts
+++ b/src/livecodes/vendors.ts
@@ -98,6 +98,7 @@ export const comlinkBaseUrl = /* @__PURE__ */ getUrl('comlink@4.4.1/dist/');
export const cppWasmBaseUrl = /* @__PURE__ */ getUrl('@chriskoch/cpp-wasm@1.0.2/');
export const csharpWasmBaseUrl = /* @__PURE__ */ getUrl('@seth0x41/csharp-wasm@1.0.3/');
+export const zigWasmBaseUrl = /* @__PURE__ */ getUrl('@seth0x41/zig-wasm@1.0.0/');
export const csstreeUrl = /* @__PURE__ */ getUrl('css-tree@2.3.1/dist/csstree.js');
diff --git a/src/sdk/models.ts b/src/sdk/models.ts
index e6bd21a015..a9ee5f29d5 100644
--- a/src/sdk/models.ts
+++ b/src/sdk/models.ts
@@ -1022,6 +1022,8 @@ export type Language =
| 'java'
| 'csharp'
| 'csharp-wasm'
+ | 'zig'
+ | 'zig-wasm'
| 'cs'
| 'cs-wasm'
| 'wasm.cs'
@@ -1249,7 +1251,8 @@ export type ParserName =
| 'less'
| 'php'
| 'pug'
- | 'java';
+ | 'java'
+ | 'zig';
export interface Parser {
name: ParserName;
@@ -1343,6 +1346,7 @@ export interface Compiler {
| 'text/cpp'
| 'text/java'
| 'text/csharp-wasm'
+ | 'text/zig-wasm'
| 'text/perl'
| 'text/julia'
| 'text/biwascheme'
@@ -1442,7 +1446,8 @@ export type TemplateName =
| 'postgresql'
| 'prolog'
| 'blockly'
- | 'diagrams';
+ | 'diagrams'
+ | 'zig-wasm';
export interface Tool {
name: 'console' | 'compiled' | 'tests';