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';