| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
Liyad (Lisp yet another DSL interpreter, or LIYAD is yum and delicious) is
very small Lisp interpreter written in JavaScript.
You can easily start making your new DSL using Lisp and S-expression.
from NPM:
$ npm install liyad --saveor download UMD from release page.
NOTICE:
Use with webpack >= 5If you get the error:
Module not found: Error: Can't resolve '(importing/path/to/filename)' in '(path/to/node_modules/path/to/dirname)' Did you mean '(filename).js'?`Add following setting to your webpack.config.js.
{ test: /\.m?js/, resolve: { fullySpecified: false, }, },On webpack >= 5, the extension in the request is mandatory for it to be fully specified if the origin is a '.mjs' file or a '.js' file where the package.json contains '"type": "module"'.
See liyad-cli .
$ npm install -g liyad-cli
$ liyadhttps://shellyln.github.io/liyad/playground.html
LSX is an alternative JSX notation using Lisp.
No transpiler needed
Secure execution for untrusted contents
Simple and powerful
The LSX runtime directly calls React.createElement
(or a JSX Factory function such as
RedAgate,
Vue.js, etc.) as a Lisp function,
Convert a Lisp list to a renderer component object tree.
In order to resolve the renderer component, you must register the object's constructor with the LSX runtime in advance.
All unresolved lisp function symbols are dispatched to React.createElement('some_unresolved_name', ...).
You can declare HTML/XML standard tags.
As with JSX, LSX must always return a single component.
Using Template Lisp function instead of JSX Fragment tag will produce the same result.
lsx`
(Template
(select (@ (style (display "inline-block")
(width "300px") )
(className "foo bar baz")
(onChange ${(e) => this.handleExampleSelected(e.target.value)}) )
($=for ${exampleCodes}
($=if (== (% $index 2) 1)
(option (@ (value $index)) ($concat "odd: " ($get $data "name"))) )
($=if (== (% $index 2) 0)
(option (@ (value $index)) ($concat "even: " ($get $data "name"))) ))))`;Playground's source code is written in LSX.
import { S } from 'liyad';
console.log(
JSON.stringify(S`
($list
1 2 3 "a" "b" "C"
($list 4 5 6) ${"X"} ${["Y", "Z"]} )`
// You can also parse by calling w/o template literal syntax as following:
// S(' ... ')
)
);Output:
[{"symbol":"$list"},1,2,3,"a","b","C",[{"symbol":"$list"},4,5,6],{"value":"X"},{"value":["Y","Z"]}]import { lisp } from 'liyad';
console.log(
JSON.stringify(lisp`
($defun fac (n)
($if (== n 0)
1
(* n ($self (- n 1))) ))
($list
1 2 (fac 3) "a" "b" "c"
($list 4 5 (fac 6) ${"X"} ${["Y", "Z"]}) )`
// You can also evaluate by calling w/o template literal syntax as following:
// lisp(' ... ')
)
);Output:
[1,2,6,"a","b","c",[4,5,720,"X",["Y","Z"]]]import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { LSX } from 'liyad';
var lsx = null;
const exampleCodes = [{
name: "Example1: factorial",
code: ` ... `
}, {
name: "Example2: Hello, World!",
code: ` ... `,
}];
class ExampleLoader extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {};
}
handleExampleSelected(i) {
this.props.loadExample(i);
}
render() {
return (lsx`
(Template
(select (@ (style (display "inline-block")
(width "300px") )
(onChange ${(e) => this.handleExampleSelected(e.target.value)}) )
($=for ${exampleCodes}
(option (@ (value $index)) ($get $data "name")) )))`);
}
}
class App extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {};
}
loadExample(i) {
console.log(exampleCodes[i].code);
}
render() {
return (lsx`
(Template
(div (@ (style (margin "4px")))
(ExampleLoader (@ (loadExample ${(i) => this.loadExample(i)}))) ))`);
}
}
var lsx = LSX({
jsx: React.createElement,
jsxFlagment: React.Fragment,
components: {
ExampleLoader,
App,
},
});
ReactDOM.render(lsx`(App)`, document.getElementById('app'));import { SxFuncInfo,
SxMacroInfo,
SxSymbolInfo,
SExpression,
SxParserConfig,
defaultConfig,
installCore,
installArithmetic,
installSequence } from 'liyad';
const myOperators: SxFuncInfo[] = [{
name: '$__defun',
fn: (state: SxParserState, name: string) => (...args: any[]) => {
// S expression: ($__defun 'name '(sym1 ... symN) 'expr ... 'expr)
// -> S expr : fn
const car: SxSymbol = $$first(...args);
if (args.length < 3) {
throw new Error(`[SX] $__defun: Invalid argument length: expected: ${3} / args: ${args.length}.`);
}
const fn = $__lambda(state, name)(...args.slice(1));
state.funcMap.set(car.symbol, {
name: car.symbol,
fn: (st, nm) => fn
});
return fn;
},
}];
const myMacros: SxMacroInfo[] = [{
name: '$defun',
fn: (state: SxParserState, name: string) => (list) => {
// S expression: ($defun name (sym1 ... symN) expr ... expr)
// -> S expr : ($__defun 'name '(sym1 ... symN) 'expr ... 'expr)
return [{symbol: '$__defun'},
...(list.slice(1).map(x => quote(state, x))),
];
},
}];
const mySymbols: SxSymbolInfo[] = [
{name: '#t', fn: (state: SxParserState, name: string) => true}
];
export const MyDSL = (() => {
let config: SxParserConfig = Object.assign({}, defaultConfig);
config = installCore(config);
config = installArithmetic(config);
config = installSequence(config);
config.stripComments = true;
config.funcs = (config.funcs || []).concat(myOperators);
config.macros = (config.macros || []).concat(myMacros);
config.symbols = (config.symbols || []).concat(mySymbols);
return SExpression(config);
})();
console.log(
JSON.stringify(MyDSL`( ... )`)
);# This is a line comment
(# ; <-- This is a object literal, not a line comment
)
; This is a line comment
#|
This is a block comment
|#"""
Hello, Liyad!
"""is equivalent to:
($concat
"
Hello, Liyad!
"
)"""
Hello, Liyad!
"""is equivalent to:
(Template
"
Hello, Liyad!
"
)Template on the LSX preset interpreter, it is mapped to the function passed by LsxConfig.JsxFragment.
See also: Fragments (React), Template (RedAgate).
"""
Hello, %%%($get name)!
"""is equivalent to:
(Template
"
Hello, " ($get name) "!
"
)"""div
Hello, %%%($get name)!
"""is equivalent to:
(div
"
Hello, " ($get name) "!
"
)"""div@{(id "123") (class "foo bar baz")}
Hello, %%%($get name)!
"""is equivalent to:
(div (@ (id "123") (class "foo bar baz"))
"
Hello, " ($get name) "!
"
)($list 1 2 ...($concat (3 4) (5 6)) 7 8)is equivalent to:
($list 1 2 ($spread ($concat (3 4) (5 6))) 7 8)and is to be:
[1,2,3,4,5,6,7,8]$spread is NOT a macro. The list passed as a parameter is spliced after evaluation.
($list 1 2 3 4 ($splice (5 6 7 8)) 9 10)is equivalent to:
($list 1 2 3 4 5 6 7 8 9 10)(($splice ($call x add)) 5 7)is equivalent to:
($call x add 5 7)(::foo:bar:baz= 7)is equivalent to:
($set ("foo" "bar" "baz") 7)($list ::foo:bar:baz)is equivalent to:
($list ($get "foo" "bar" "baz"))(::foo:bar@baz 3 5 7)is equivalent to:
($call ($get "foo" "bar") baz 3 5 7)($defun f (x ...y)
($list x y) )
($list
(f 1)
(f 1 2)
(f 1 2 3)
(f 1 2 3 4)
(f 1 2 3 4 5) )is to be:
[
[1,[]],
[1,[2]],
[1,[2,3]],
[1,[2,3,4]],
[1,[2,3,4,5]]
]Verbatim string literal
($last @"c:\documents\files\u0066.txt")is to be:
"c:\\documents\\files\\u0066.txt"Normal string literal
($last "c:\documents\files\u0066.txt")is to be:
"c:documents\filesf.txt"(# (foo "a")
(bar 10)
(baz) )is to be:
{
"foo": "a",
"bar": 10,
"baz": true
}($list nil null undefined)is to be:
[[], null, undefined]See this.
($defun fn(x) (+ x 1))
($let x (<- fn))
(x 3) ;; 4Liyad is Lisp-2 language.
Lambda
($let fn (-> (x y z) (+ x y z)))
(fn 1 2 3) ;; 6$lambda is synonym of ->.
Closure
($let fn ($local ((a 1)(b 2)(c 3))
(|-> (x y z) use (a b c)
($set a (+ a x))
($set b (+ b y))
($set c (+ c z))
(+ a b c) )))
(fn 1 2 3) ;; 12
(fn 1 2 3) ;; 18$closure is synonym of |->.
is equivalent to:
($let fn ($local ((a 1)(b 2)(c 3))
($capture (a b c) (-> (x y z)
($set a (+ a x))
($set b (+ b y))
($set c (+ c z))
(+ a b c) ))))
(fn 1 2 3) ;; 12
(fn 1 2 3) ;; 18$capture can also be used with $defun.
($defun tarai(x y z)
($if (<= x y)
y
($self ($self (- x 1) y z)
($self (- y 1) z x)
($self (- z 1) x y) )))$self refers to the function currently defined by $defun or ->.
($defmacro FOR (!i <[> <FROM> s <TO> e <]> ...body)
`($last
($local ((,i ,s))
($while (<= ,i ,e)
,@body
($set ,i (+ ,i 1)) ))))
($let c1 0)
($let c2 100)
(FOR p [ FROM (+ 1) TO (+ 6 -3) ]
($set c1 (+ c1 p))
($set c2 (+ c2 p)) )| formal parameter | constraint |
|---|---|
| !token | parameter should be symbol |
| <token> | parameter should be symbol named token |
| token:number | parameter should be number |
| token:string | parameter should be string |
| token:function | parameter should be function |
| token:list | parameter should be list |
| token:symbol | parameter should be symbol |
Don't put spaces between ! < > :type and token.
Type checking checks formal parameter types before evaluation.
Macro can be overloaded with the same macro name but different numbers of formal parameters.
($let fn (-> () $this))
($let xx (# (a 3)
(b 5)
(f fn) ))
($json-stringify (::xx@f)) ;; {"a":3,"b":5}| interpreting | compiling | |
|---|---|---|
| $defun | $$defun | define the function |
| $lambda | $$lambda | define the lambda |
| -> | => | define the lambda |
| $closure | $$closure | define the closure |
| |-> | |=> | define the closure |
Create a new DSL.
interface SxParserConfig {
raiseOnUnresolvedSymbol: boolean;
enableEvaluate: boolean;
enableHereDoc: boolean;
enableSpread: boolean;
enableSplice: boolean;
enableShorthands: boolean;
enableVerbatimStringLiteral: boolean;
enableTailCallOptimization: boolean;
enableRegExpMatchOperator: true, // IMPORTANT: Turn off to prevent ReDoS when executing untrusted code
enableCompilationOperators: boolean; // IMPORTANT: Turn off to prevent DoS when executing untrusted code
stripComments: boolean;
wrapExternalValue: boolean;
reservedNames: SxReservedNames;
returnMultipleRoot: boolean;
maxEvalCount: number; // IMPORTANT: Set positive value to prevent DoS when executing untrusted code
jsx?: (comp: any, props: any, ...children: any[]) => any;
JsxFragment?: any;
funcs: SxFuncInfo[];
macros: SxMacroInfo[];
symbols: SxSymbolInfo[];
funcSymbolResolverFallback?: SxFunc;
valueSymbolResolverFallback?: SxSymbolResolver;
}
function SExpression(config: SxParserConfig): (strings: TemplateStringsArray | string, ...values?: any[]) => SxTokenParse a S-expression.
function S(strings: TemplateStringsArray | string, ...values?: any[]): SxTokenEvaluate a Lisp code.
function lisp(strings: TemplateStringsArray | string, ...values?: any[]): SxTokenEvaluate a Lisp code.
(asynchronous features are enabled.)
function lisp_async(strings: TemplateStringsArray | string, ...values?: any[]): Promise<SxToken>Evaluate a Lisp code (returns multiple value).
function LM(strings: TemplateStringsArray | string, ...values?: any[]): SxTokenEvaluate a Lisp code (returns multiple value).
(asynchronous features are enabled.)
function LM_async(strings: TemplateStringsArray | string, ...values?: any[]): Promise<SxToken>Evaluate a Lisp code as LSX.
interface LsxConfig {
jsx: (comp: any, props: any, ...children: any[]) => any;
jsxFlagment: any;
components: object;
}
function LSX<R = SxToken>(lsxConf: LsxConfig): (strings: TemplateStringsArray, ...values: any[]) => REvaluate a Lisp code as LSX.
(asynchronous features are enabled.)
interface LsxConfig {
jsx: (comp: any, props: any, ...children: any[]) => any;
jsxFlagment: any;
components: object;
}
function LSX_async<R = SxToken>(lsxConf: LsxConfig): (strings: TemplateStringsArray, ...values: any[]) => Promise<R>evaluateAST(ast: SxToken[]): SxToken;lisp.evaluateAST([[{symbol: '+'}, 1, 2 ,3]]); // 6repl(): SExpressionTemplateFn;setGlobals(globals: object): SExpressionTemplateFn;appendGlobals(globals: object): SExpressionTemplateFn;setStartup(strings: TemplateStringsArray | string, ...values: any[]): SExpressionTemplateFn;setStartupAST(ast: SxToken[]): SExpressionTemplateFn;appendStartup(strings: TemplateStringsArray | string, ...values: any[]): SExpressionTemplateFn;appendStartupAST(ast: SxToken[]): SExpressionTemplateFn;install(installer: (config: SxParserConfig) => SxParserConfig): SExpressionTemplateFn;Run script tags.
function runScriptTags(
lisp: SExpressionTemplateFn | SExpressionAsyncTemplateFn,
globals?: object,
contentType = 'text/lisp')Usage:
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script type="text/lisp">
($local ((body (::document@querySelector "body")))
($set (body innerText) "Hello, Lisp! ") )
($local (c) ($capture (c)
($$defun tarai(x y z)
($set c (+ c 1))
($if (<= x y)
y
($self ($self (- x 1) y z)
($self (- y 1) z x)
($self (- z 1) x y))))
($list ($datetime-to-iso-string ($now)) (tarai 13 6 0) c) ))
</script>
<script src="liyad.min.js"></script>
<script>
// Since the above lisp code refers to the body element,
// you need to enclose the lisp evaluation with addEventListener.
document.addEventListener('DOMContentLoaded', function(event) {
const result = JSON.stringify(
liyad.runScriptTags(liyad.lisp, {window, document}));
const body = document.querySelector('body');
setTimeout(() => body.innerText = body.innerText + result, 30);
});
</script>
</head>
<body></body>You can benefit from tree shaking by importing ES module separated files.
| Import path | Description |
|---|---|
| liyad/modules | Entire library |
| liyad/modules/s-exp/types | Type definitions |
| liyad/modules/s-exp/interpreter | Interpreter DIY APIs SExpression, SExpressionAsync |
| liyad/modules/s-exp/interpreter/presets/s-exp | Preset s-expression parser S |
| liyad/modules/s-exp/interpreter/presets/lisp | Preset interpreters lisp, lisp_async, LM, LM_async |
| liyad/modules/s-exp/interpreter/presets/lsx | Preset interpreters LSX, LSX_async |
| liyad/modules/s-exp/operators/core | Core operators |
| liyad/modules/s-exp/operators/arithmetic | Arithmetic operators |
| liyad/modules/s-exp/operators/sequence | Sequence operators |
| liyad/modules/s-exp/operators/concurrent | Concurrent operators |
| liyad/modules/s-exp/operators/jsx | JSX (LSX) operators |
NOTICE: liyad/modules/* are not babelized. These are output as ES2015 by tsc.
See core, arithmetic, sequence, concurrent, JSX (LSX) operators.
ISC
Copyright (c) 2018, 2019 Shellyl_N and Authors.
| Back | FazBrowse Home | New Git URL |