change the way text suggestions are rendered + option to disable syntactic sugar
This commit is contained in:
parent
ea8c015eff
commit
2d81e42447
12 changed files with 357 additions and 291 deletions
52
pnpm-lock.yaml
generated
52
pnpm-lock.yaml
generated
|
|
@ -10,7 +10,7 @@ importers:
|
|||
dependencies:
|
||||
dope2:
|
||||
specifier: git+https://deemz.org/git/joeri/dope2.git
|
||||
version: git+https://deemz.org/git/joeri/dope2.git#443a13998dc3eccab26c27bee4fa056cdbc8f994
|
||||
version: git+https://deemz.org/git/joeri/dope2.git#d75bf9f0f200769a5248ace8e4e2ace04fd60381
|
||||
react:
|
||||
specifier: ^19.1.0
|
||||
version: 19.1.0
|
||||
|
|
@ -262,8 +262,8 @@ packages:
|
|||
resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
|
||||
engines: {node: '>=18.18'}
|
||||
|
||||
'@modelcontextprotocol/sdk@1.11.2':
|
||||
resolution: {integrity: sha512-H9vwztj5OAqHg9GockCQC06k1natgcxWQSRpQcPJf6i5+MWBzfKkRtxGbjQf0X2ihii0ffLZCRGbYV2f2bjNCQ==}
|
||||
'@modelcontextprotocol/sdk@1.11.3':
|
||||
resolution: {integrity: sha512-rmOWVRUbUJD7iSvJugjUbFZshTAuJ48MXoZ80Osx1GM0K/H1w7rSEvmw8m6vdWxNASgtaHIhAgre4H/E9GJiYQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
|
|
@ -617,8 +617,8 @@ packages:
|
|||
csstype@3.1.3:
|
||||
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
|
||||
|
||||
debug@4.4.0:
|
||||
resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
|
||||
debug@4.4.1:
|
||||
resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
|
||||
engines: {node: '>=6.0'}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
|
|
@ -633,8 +633,8 @@ packages:
|
|||
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
dope2@git+https://deemz.org/git/joeri/dope2.git#443a13998dc3eccab26c27bee4fa056cdbc8f994:
|
||||
resolution: {commit: 443a13998dc3eccab26c27bee4fa056cdbc8f994, repo: https://deemz.org/git/joeri/dope2.git, type: git}
|
||||
dope2@git+https://deemz.org/git/joeri/dope2.git#d75bf9f0f200769a5248ace8e4e2ace04fd60381:
|
||||
resolution: {commit: d75bf9f0f200769a5248ace8e4e2ace04fd60381, repo: https://deemz.org/git/joeri/dope2.git, type: git}
|
||||
version: 0.0.1
|
||||
|
||||
dunder-proto@1.0.1:
|
||||
|
|
@ -729,8 +729,8 @@ packages:
|
|||
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
eventsource-parser@3.0.1:
|
||||
resolution: {integrity: sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==}
|
||||
eventsource-parser@3.0.2:
|
||||
resolution: {integrity: sha512-6RxOBZ/cYgd8usLwsEl+EC09Au/9BcmCKYF2/xbml6DNczf7nv0MQb+7BA2F+li6//I+28VNlQR37XfQtcAJuA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
eventsource@3.0.7:
|
||||
|
|
@ -1369,7 +1369,7 @@ snapshots:
|
|||
'@eslint/config-array@0.20.0':
|
||||
dependencies:
|
||||
'@eslint/object-schema': 2.1.6
|
||||
debug: 4.4.0
|
||||
debug: 4.4.1
|
||||
minimatch: 3.1.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
|
@ -1383,7 +1383,7 @@ snapshots:
|
|||
'@eslint/eslintrc@3.3.1':
|
||||
dependencies:
|
||||
ajv: 6.12.6
|
||||
debug: 4.4.0
|
||||
debug: 4.4.1
|
||||
espree: 10.3.0
|
||||
globals: 14.0.0
|
||||
ignore: 5.3.2
|
||||
|
|
@ -1416,7 +1416,7 @@ snapshots:
|
|||
|
||||
'@humanwhocodes/retry@0.4.3': {}
|
||||
|
||||
'@modelcontextprotocol/sdk@1.11.2':
|
||||
'@modelcontextprotocol/sdk@1.11.3':
|
||||
dependencies:
|
||||
content-type: 1.0.5
|
||||
cors: 2.8.5
|
||||
|
|
@ -1590,7 +1590,7 @@ snapshots:
|
|||
'@typescript-eslint/types': 8.32.1
|
||||
'@typescript-eslint/typescript-estree': 8.32.1(typescript@5.8.3)
|
||||
'@typescript-eslint/visitor-keys': 8.32.1
|
||||
debug: 4.4.0
|
||||
debug: 4.4.1
|
||||
eslint: 9.26.0
|
||||
typescript: 5.8.3
|
||||
transitivePeerDependencies:
|
||||
|
|
@ -1605,7 +1605,7 @@ snapshots:
|
|||
dependencies:
|
||||
'@typescript-eslint/typescript-estree': 8.32.1(typescript@5.8.3)
|
||||
'@typescript-eslint/utils': 8.32.1(eslint@9.26.0)(typescript@5.8.3)
|
||||
debug: 4.4.0
|
||||
debug: 4.4.1
|
||||
eslint: 9.26.0
|
||||
ts-api-utils: 2.1.0(typescript@5.8.3)
|
||||
typescript: 5.8.3
|
||||
|
|
@ -1618,7 +1618,7 @@ snapshots:
|
|||
dependencies:
|
||||
'@typescript-eslint/types': 8.32.1
|
||||
'@typescript-eslint/visitor-keys': 8.32.1
|
||||
debug: 4.4.0
|
||||
debug: 4.4.1
|
||||
fast-glob: 3.3.3
|
||||
is-glob: 4.0.3
|
||||
minimatch: 9.0.5
|
||||
|
|
@ -1681,7 +1681,7 @@ snapshots:
|
|||
dependencies:
|
||||
bytes: 3.1.2
|
||||
content-type: 1.0.5
|
||||
debug: 4.4.0
|
||||
debug: 4.4.1
|
||||
http-errors: 2.0.0
|
||||
iconv-lite: 0.6.3
|
||||
on-finished: 2.4.1
|
||||
|
|
@ -1754,7 +1754,7 @@ snapshots:
|
|||
|
||||
csstype@3.1.3: {}
|
||||
|
||||
debug@4.4.0:
|
||||
debug@4.4.1:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
|
|
@ -1762,7 +1762,7 @@ snapshots:
|
|||
|
||||
depd@2.0.0: {}
|
||||
|
||||
dope2@git+https://deemz.org/git/joeri/dope2.git#443a13998dc3eccab26c27bee4fa056cdbc8f994:
|
||||
dope2@git+https://deemz.org/git/joeri/dope2.git#d75bf9f0f200769a5248ace8e4e2ace04fd60381:
|
||||
dependencies:
|
||||
functional-red-black-tree: 1.0.1
|
||||
|
||||
|
|
@ -1846,13 +1846,13 @@ snapshots:
|
|||
'@humanfs/node': 0.16.6
|
||||
'@humanwhocodes/module-importer': 1.0.1
|
||||
'@humanwhocodes/retry': 0.4.3
|
||||
'@modelcontextprotocol/sdk': 1.11.2
|
||||
'@modelcontextprotocol/sdk': 1.11.3
|
||||
'@types/estree': 1.0.7
|
||||
'@types/json-schema': 7.0.15
|
||||
ajv: 6.12.6
|
||||
chalk: 4.1.2
|
||||
cross-spawn: 7.0.6
|
||||
debug: 4.4.0
|
||||
debug: 4.4.1
|
||||
escape-string-regexp: 4.0.0
|
||||
eslint-scope: 8.3.0
|
||||
eslint-visitor-keys: 4.2.0
|
||||
|
|
@ -1895,11 +1895,11 @@ snapshots:
|
|||
|
||||
etag@1.8.1: {}
|
||||
|
||||
eventsource-parser@3.0.1: {}
|
||||
eventsource-parser@3.0.2: {}
|
||||
|
||||
eventsource@3.0.7:
|
||||
dependencies:
|
||||
eventsource-parser: 3.0.1
|
||||
eventsource-parser: 3.0.2
|
||||
|
||||
express-rate-limit@7.5.0(express@5.1.0):
|
||||
dependencies:
|
||||
|
|
@ -1913,7 +1913,7 @@ snapshots:
|
|||
content-type: 1.0.5
|
||||
cookie: 0.7.2
|
||||
cookie-signature: 1.2.2
|
||||
debug: 4.4.0
|
||||
debug: 4.4.1
|
||||
encodeurl: 2.0.0
|
||||
escape-html: 1.0.3
|
||||
etag: 1.8.1
|
||||
|
|
@ -1969,7 +1969,7 @@ snapshots:
|
|||
|
||||
finalhandler@2.1.0:
|
||||
dependencies:
|
||||
debug: 4.4.0
|
||||
debug: 4.4.1
|
||||
encodeurl: 2.0.0
|
||||
escape-html: 1.0.3
|
||||
on-finished: 2.4.1
|
||||
|
|
@ -2260,7 +2260,7 @@ snapshots:
|
|||
|
||||
router@2.2.0:
|
||||
dependencies:
|
||||
debug: 4.4.0
|
||||
debug: 4.4.1
|
||||
depd: 2.0.0
|
||||
is-promise: 4.0.0
|
||||
parseurl: 1.3.3
|
||||
|
|
@ -2282,7 +2282,7 @@ snapshots:
|
|||
|
||||
send@1.2.0:
|
||||
dependencies:
|
||||
debug: 4.4.0
|
||||
debug: 4.4.1
|
||||
encodeurl: 2.0.0
|
||||
escape-html: 1.0.3
|
||||
etag: 1.8.1
|
||||
|
|
|
|||
12
src/App.tsx
12
src/App.tsx
|
|
@ -48,6 +48,8 @@ export function App() {
|
|||
// load from localStorage
|
||||
const [appState, setAppState] = useState(loadFromLocalStorage());
|
||||
|
||||
const [syntacticSugar, setSyntacticSugar] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
// persist accross reloads
|
||||
localStorage["appState"] = JSON.stringify(appState);
|
||||
|
|
@ -146,15 +148,21 @@ export function App() {
|
|||
<button className="factoryReset" onClick={factoryReset}>
|
||||
FACTORY RESET
|
||||
</button>
|
||||
<label>
|
||||
<input type="checkbox"
|
||||
checked={syntacticSugar}
|
||||
onChange={e => setSyntacticSugar(e.target.checked)}/>
|
||||
syntactic sugar
|
||||
</label>
|
||||
</header>
|
||||
|
||||
<main onKeyDown={onKeyDown}>
|
||||
<CommandContext value={{undo: onUndo, redo: onRedo, doHighlight}}>
|
||||
<CommandContext value={{undo: onUndo, redo: onRedo, doHighlight, syntacticSugar}}>
|
||||
<Editor
|
||||
state={appState.history.at(-1)!}
|
||||
setState={pushHistory}
|
||||
onCancel={() => {}}
|
||||
suggestionPriority={() => 0}
|
||||
suggestionPriority={() => 1}
|
||||
/>
|
||||
</CommandContext>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import { useContext } from "react";
|
||||
import { useContext, useInsertionEffect } from "react";
|
||||
|
||||
import { Editor, type EditorState } from "./Editor";
|
||||
import { Value } from "./Value";
|
||||
import { type SetStateFn, type State2Props } from "./Editor";
|
||||
import { DeepError, evalCallBlock, evalEditorBlock, haveValue } from "./eval";
|
||||
import { evalCallBlock, evalEditorBlock } from "./eval";
|
||||
import { type ResolvedType } from "./eval";
|
||||
import "./CallBlock.css";
|
||||
import { EnvContext } from "./EnvContext";
|
||||
import type { SuggestionType } from "./InputBlock";
|
||||
import { getType, NotAFunctionError, unify, UnifyError } from "dope2";
|
||||
import { UnifyError } from "dope2";
|
||||
|
||||
export interface CallBlockState<
|
||||
FnState=EditorState,
|
||||
|
|
@ -47,7 +47,7 @@ export function CallBlock({ state, setState, suggestionPriority }: CallBlockProp
|
|||
= headlessCallBlock(setState);
|
||||
const env = useContext(EnvContext);
|
||||
const resolved = evalEditorBlock(state, env);
|
||||
return <span className={"functionBlock" + ((resolved instanceof DeepError) ? " unifyError" : "")}>
|
||||
return <span className={"functionBlock" + ((resolved.kind === "error") ? " unifyError" : "")}>
|
||||
<FunctionHeader
|
||||
fn={state.fn}
|
||||
setFn={setFn}
|
||||
|
|
@ -56,32 +56,79 @@ export function CallBlock({ state, setState, suggestionPriority }: CallBlockProp
|
|||
suggestionPriority={suggestionPriority}
|
||||
/>
|
||||
<div className="functionParams">
|
||||
<div className="outputParam">
|
||||
<Output resolved={resolved}>
|
||||
{/* Sequence of input parameters */}
|
||||
<InputParams
|
||||
fn={state.fn} setFn={setFn}
|
||||
input={state.input} setInput={setInput}
|
||||
onInputCancel={onInputCancel}
|
||||
depth={0}
|
||||
errorDepth={resolved instanceof DeepError ? (resolved.depth) : -1}
|
||||
errorDepth={(resolved.kind === "error") ? (resolved.depth) : -1}
|
||||
suggestionPriority={suggestionPriority}
|
||||
/>
|
||||
{/* Output (or Error) */}
|
||||
{ resolved instanceof DeepError && resolved.e.toString()
|
||||
|| resolved && <><Value dynamic={resolved} />
|
||||
</>}
|
||||
</div>
|
||||
</Output>
|
||||
</div>
|
||||
</span>;
|
||||
}
|
||||
|
||||
export function Output({resolved, children}) {
|
||||
return <div className="outputParam">
|
||||
{children}
|
||||
{ (resolved.kind === "error") && resolved.e.toString()
|
||||
|| (resolved.kind === "value") && <Value dynamic={resolved} />
|
||||
|| "unknown" }
|
||||
</div>;
|
||||
}
|
||||
|
||||
export function CallBlockNoSugar({ state, setState, suggestionPriority }: CallBlockProps) {
|
||||
const {setFn, setInput, onFnCancel, onInputCancel}
|
||||
= headlessCallBlock(setState);
|
||||
const env = useContext(EnvContext);
|
||||
const resolved = evalEditorBlock(state, env);
|
||||
const isOffending = (resolved.kind === "error") ? (resolved.depth===0) : false;
|
||||
return <span className={"functionBlock" + ((resolved.kind === "error") ? " unifyError" : "")}>
|
||||
<FunctionName fn={state.fn} setFn={setFn} onFnCancel={onFnCancel} suggestionPriority={suggestionPriority} input={state.input} />
|
||||
<div className="functionParams">
|
||||
<Output resolved={resolved}>
|
||||
<div className={"inputParam" + (isOffending ? " offending" : "")}>
|
||||
<Editor
|
||||
state={state.input}
|
||||
setState={setInput}
|
||||
onCancel={onInputCancel}
|
||||
suggestionPriority={
|
||||
(inputSuggestion: SuggestionType) => computePriority(
|
||||
evalEditorBlock(state.fn, env), // fn *may* be set
|
||||
inputSuggestion[2], // suggestions will be for input
|
||||
suggestionPriority, // priority function we get from parent block
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</Output>
|
||||
</div>
|
||||
</span>;
|
||||
}
|
||||
|
||||
function computePriority(fn: ResolvedType, input: ResolvedType, outPriority: (s: SuggestionType) => number) {
|
||||
const resolved = evalCallBlock(fn, input);
|
||||
if ((resolved && !(resolved instanceof DeepError))) {
|
||||
|
||||
const r = outPriority(['literal', '<computed>',
|
||||
// @ts-ignore: // TODO fix this
|
||||
{t: resolved.t}]);
|
||||
|
||||
if (resolved.kind === "value") {
|
||||
// The computed output becomes an input for the surrounding block, which may also have a priority function defined, for instance our output is the input to another function
|
||||
return 1 + outPriority(['literal', '<computed>', resolved]);
|
||||
console.log('r:', r);
|
||||
return 1 + r;
|
||||
}
|
||||
else if (resolved.kind === "unknown") {
|
||||
return 0 + r;
|
||||
}
|
||||
if (resolved.e instanceof UnifyError) {
|
||||
return -1 + r; // parameter doesn't match
|
||||
}
|
||||
else {
|
||||
return -2 + r; // even worse: fn is not a function!
|
||||
}
|
||||
return 0; // no fit
|
||||
}
|
||||
|
||||
function FunctionHeader({ fn, setFn, input, onFnCancel, suggestionPriority }) {
|
||||
|
|
@ -110,23 +157,28 @@ function FunctionHeader({ fn, setFn, input, onFnCancel, suggestionPriority }) {
|
|||
}
|
||||
else {
|
||||
// end of recursion - draw function name
|
||||
return <span className="functionName">
|
||||
𝑓𝑛
|
||||
<Editor
|
||||
state={fn}
|
||||
setState={setFn}
|
||||
onCancel={onFnCancel}
|
||||
suggestionPriority={
|
||||
(fnSuggestion: SuggestionType) => computePriority(
|
||||
fnSuggestion[2], // suggestions will be for function
|
||||
evalEditorBlock(input, env), // input *may* be set
|
||||
suggestionPriority, // priority function we get from parent block
|
||||
)}
|
||||
/>
|
||||
</span>;
|
||||
return <FunctionName fn={fn} setFn={setFn} onFnCancel={onFnCancel} suggestionPriority={suggestionPriority} input={input}/>;
|
||||
}
|
||||
}
|
||||
|
||||
function FunctionName({fn, setFn, onFnCancel, suggestionPriority, input}) {
|
||||
const env = useContext(EnvContext);
|
||||
return <span className="functionName">
|
||||
𝑓𝑛
|
||||
<Editor
|
||||
state={fn}
|
||||
setState={setFn}
|
||||
onCancel={onFnCancel}
|
||||
suggestionPriority={
|
||||
(fnSuggestion: SuggestionType) => computePriority(
|
||||
fnSuggestion[2], // suggestions will be for function
|
||||
evalEditorBlock(input, env), // input *may* be set
|
||||
suggestionPriority, // priority function we get from parent block
|
||||
)}
|
||||
/>
|
||||
</span>;
|
||||
}
|
||||
|
||||
function InputParams({ fn, setFn, input, setInput, onInputCancel, depth, errorDepth, suggestionPriority }) {
|
||||
const env = useContext(EnvContext);
|
||||
let nestedParams;
|
||||
|
|
@ -158,6 +210,7 @@ function InputParams({ fn, setFn, input, setInput, onInputCancel, depth, errorDe
|
|||
const isOffending = depth === errorDepth;
|
||||
return <div className={"inputParam" + (isOffending ? " offending" : "")}>
|
||||
{nestedParams}
|
||||
{/* Our own input param */}
|
||||
<Editor
|
||||
state={input}
|
||||
setState={setInput}
|
||||
|
|
@ -170,53 +223,4 @@ function InputParams({ fn, setFn, input, setInput, onInputCancel, depth, errorDe
|
|||
)}
|
||||
/>
|
||||
</div>;
|
||||
// {(fn.kind === "call") &&
|
||||
// // if the function we're calling is itself the result of a function call,
|
||||
// // then we render its input parameter nested in our own input parameter box, which is way more readable
|
||||
|
||||
// // recurse:
|
||||
// <NestedParams
|
||||
// fn={fn}
|
||||
// setFn={setFn}
|
||||
// depth={depth}
|
||||
// errorDepth={errorDepth}
|
||||
// suggestionPriority={suggestionPriority}
|
||||
// />
|
||||
// }
|
||||
// {/* Our own input */}
|
||||
// <Editor
|
||||
// state={input}
|
||||
// setState={setInput}
|
||||
// onCancel={onInputCancel}
|
||||
// suggestionPriority={
|
||||
// (inputSuggestion: SuggestionType) => computePriority(
|
||||
// evalEditorBlock(fn, env), // fn *may* be set
|
||||
// inputSuggestion[2], // suggestions will be for input
|
||||
// suggestionPriority, // priority function we get from parent block
|
||||
// )}
|
||||
// />
|
||||
// </div>;
|
||||
}
|
||||
|
||||
// function NestedParams({fn, setFn, depth, errorDepth, suggestionPriority}) {
|
||||
// const env = useContext(EnvContext);
|
||||
// const {
|
||||
// setFn : setFnFn,
|
||||
// setInput : setFnInput,
|
||||
// } = headlessCallBlock(setFn);
|
||||
// return <InputParams
|
||||
// fn={fn.fn}
|
||||
// setFn={setFnFn}
|
||||
// input={fn.input}
|
||||
// setInput={setFnInput}
|
||||
// onInputCancel={() => {/*todo*/}}
|
||||
// depth={depth+1}
|
||||
// errorDepth={errorDepth}
|
||||
// suggestionPriority={
|
||||
// (inputSuggestion: SuggestionType) => computePriority(
|
||||
// evalEditorBlock(fn.fn, env), // fn *may* be set
|
||||
// inputSuggestion[2], // suggestions will be for input
|
||||
// suggestionPriority, // priority function we get from parent block
|
||||
// )}
|
||||
// />;
|
||||
// }
|
||||
|
|
@ -4,6 +4,7 @@ interface GlobalActions {
|
|||
undo: () => void;
|
||||
redo: () => void;
|
||||
doHighlight: {[key:string]: () => void};
|
||||
syntacticSugar: boolean;
|
||||
}
|
||||
|
||||
export const CommandContext = createContext<GlobalActions|null>(null);
|
||||
|
|
|
|||
|
|
@ -4,4 +4,5 @@
|
|||
|
||||
.commandInput {
|
||||
width: 90px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
|
@ -2,10 +2,10 @@ import { useContext, useEffect, useRef, useState } from "react";
|
|||
|
||||
import { getSymbol, getType, symbolFunction } from "dope2";
|
||||
|
||||
import { CallBlock, type CallBlockState } from "./CallBlock";
|
||||
import { CallBlock, CallBlockNoSugar, type CallBlockState } from "./CallBlock";
|
||||
import { InputBlock, type InputBlockState, type SuggestionType } from "./InputBlock";
|
||||
import { Type } from "./Type";
|
||||
import { DeepError, evalEditorBlock } from "./eval";
|
||||
import { evalEditorBlock } from "./eval";
|
||||
import { CommandContext } from "./CommandContext";
|
||||
import "./Editor.css";
|
||||
import { EnvContext } from "./EnvContext";
|
||||
|
|
@ -148,11 +148,20 @@ export function Editor({state, setState, onCancel, suggestionPriority}: EditorPr
|
|||
onCancel={onCancel}
|
||||
/>;
|
||||
case "call":
|
||||
return <CallBlock
|
||||
state={state}
|
||||
setState={setState as (callback:(p:CallBlockState)=>EditorState)=>void}
|
||||
suggestionPriority={suggestionPriority}
|
||||
/>;
|
||||
if (globalContext?.syntacticSugar) {
|
||||
return <CallBlock
|
||||
state={state}
|
||||
setState={setState as (callback:(p:CallBlockState)=>EditorState)=>void}
|
||||
suggestionPriority={suggestionPriority}
|
||||
/>;
|
||||
}
|
||||
else {
|
||||
return <CallBlockNoSugar
|
||||
state={state}
|
||||
setState={setState as (callback:(p:CallBlockState)=>EditorState)=>void}
|
||||
suggestionPriority={suggestionPriority}
|
||||
/>;
|
||||
}
|
||||
case "let":
|
||||
return <LetInBlock
|
||||
state={state}
|
||||
|
|
@ -165,20 +174,16 @@ export function Editor({state, setState, onCancel, suggestionPriority}: EditorPr
|
|||
const resolved = evalEditorBlock(state, env);
|
||||
return <>
|
||||
{renderBlock()}
|
||||
{
|
||||
(resolved && !(resolved instanceof DeepError))
|
||||
? <div className="typeSignature">
|
||||
:: <Type type={getType(resolved)} />
|
||||
</div>
|
||||
: <></>
|
||||
}
|
||||
<div className="typeSignature">
|
||||
:: <Type type={getType(resolved)} />
|
||||
</div>
|
||||
<input
|
||||
ref={commandInputRef}
|
||||
spellCheck={false}
|
||||
className="editable commandInput"
|
||||
placeholder={`<command>`}
|
||||
onKeyDown={onCommand}
|
||||
value={""}
|
||||
onChange={() => {}} />
|
||||
ref={commandInputRef}
|
||||
spellCheck={false}
|
||||
className="editable commandInput"
|
||||
placeholder={`<command>`}
|
||||
onKeyDown={onCommand}
|
||||
value={""}
|
||||
onChange={() => {}} />
|
||||
</>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,62 +1,52 @@
|
|||
@import url('https://fonts.googleapis.com/css2?family=Inconsolata:wght@500&display=swap');
|
||||
|
||||
.suggest {
|
||||
margin-left: -3.5px;
|
||||
margin-right: 5px;
|
||||
color: #aaa;
|
||||
min-width: 30px;
|
||||
|
||||
.inputBlock {
|
||||
position: relative;
|
||||
}
|
||||
.editable {
|
||||
position: relative;
|
||||
outline: 0px solid transparent;
|
||||
display: inline-block;
|
||||
border: 0;
|
||||
font-size: 13pt;
|
||||
font-family: "Inconsolata", monospace;
|
||||
font-optical-sizing: auto;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-variation-settings: "wdth" 100;
|
||||
|
||||
background-color: transparent;
|
||||
color: inherit;
|
||||
padding: 0;
|
||||
}
|
||||
.suggestions {
|
||||
display: none;
|
||||
color: black;
|
||||
.suggest {
|
||||
left: 0;
|
||||
top: 0;
|
||||
position: absolute;
|
||||
color: #aaa;
|
||||
}
|
||||
.suggestionsPlaceholder {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
.suggestions {
|
||||
display: block;
|
||||
color: black;
|
||||
text-align: left;
|
||||
position: absolute;
|
||||
margin-top: 7px;
|
||||
margin-left: 4px;
|
||||
border: solid 1px dodgerblue;
|
||||
cursor: pointer;
|
||||
max-height: calc(100vh - 64px);
|
||||
overflow: auto;
|
||||
z-index: 10;
|
||||
background-color: white;
|
||||
width: max-content;
|
||||
max-width: 500px;
|
||||
}
|
||||
.selected {
|
||||
background-color: dodgerblue;
|
||||
color: white;
|
||||
}
|
||||
.editable {
|
||||
outline: 0px solid transparent;
|
||||
display: inline-block;
|
||||
border: 0;
|
||||
/* box-sizing: border-box; */
|
||||
/* width: ; */
|
||||
/* border: 1px black solid; */
|
||||
/* border-style: dashed none dashed; */
|
||||
|
||||
margin-left: 4px;
|
||||
margin-right: 0;
|
||||
|
||||
font-size: 13pt;
|
||||
font-family: "Inconsolata", monospace;
|
||||
font-optical-sizing: auto;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-variation-settings: "wdth" 100;
|
||||
|
||||
background-color: transparent;
|
||||
color: inherit;
|
||||
}
|
||||
.border-around-input {
|
||||
border: 1px solid black;
|
||||
padding: 1px;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useContext, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { memo, useContext, useEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
import { Double, getType, Int, newDynamic, prettyT, trie } from "dope2";
|
||||
import { getType, prettyT, trie } from "dope2";
|
||||
|
||||
import { EnvContext } from "./EnvContext";
|
||||
import type { Dynamic } from "./eval";
|
||||
|
|
@ -8,7 +8,7 @@ import "./InputBlock.css";
|
|||
import { Type } from "./Type";
|
||||
import type { State2Props } from "./Editor";
|
||||
import { autoInputWidth, focusNextElement, focusPrevElement, setRightMostCaretPosition } from "./util/dom_trickery";
|
||||
import { parseDouble, parseInt } from "./util/parse";
|
||||
import { attemptParseLiteral } from "./eval";
|
||||
|
||||
interface Literal {
|
||||
kind: "literal";
|
||||
|
|
@ -38,13 +38,15 @@ interface InputBlockProps extends State2Props<InputBlockState> {
|
|||
}
|
||||
|
||||
const computeSuggestions = (text, env, suggestionPriority: (s: SuggestionType) => number): PrioritizedSuggestionType[] => {
|
||||
const asDouble = parseDouble(text);
|
||||
const asInt = parseInt(text);
|
||||
const literals = attemptParseLiteral(text);
|
||||
|
||||
const ls = [
|
||||
... (asDouble ? [["literal", asDouble.toString(), newDynamic(asDouble)(Double)]] : []),
|
||||
... (asInt ? [["literal", asInt.toString(), newDynamic(BigInt(asInt))(Int)]] : []),
|
||||
... trie.suggest(env.name2dyn)(text)(Infinity).map(([name,type]) => ["name", name, type]),
|
||||
// literals
|
||||
... literals.map((lit) => ["literal", text, lit]),
|
||||
|
||||
// names
|
||||
... trie.suggest(env.name2dyn)(text)(Infinity)
|
||||
.map(([name,type]) => ["name", name, type]),
|
||||
]
|
||||
// return ls;
|
||||
return ls
|
||||
|
|
@ -60,9 +62,9 @@ export function InputBlock({ state, setState, suggestionPriority, onCancel }: In
|
|||
const [haveFocus, setHaveFocus] = useState(false); // whether to render suggestions or not
|
||||
|
||||
const singleSuggestion = trie.growPrefix(env.name2dyn)(text);
|
||||
const suggestions = useMemo(() => computeSuggestions(text, env, suggestionPriority), [text]);
|
||||
const suggestions = useMemo(() => computeSuggestions(text, env, suggestionPriority), [text, suggestionPriority, env]);
|
||||
|
||||
useEffect(() => autoInputWidth(inputRef, text), [inputRef, text]);
|
||||
useEffect(() => autoInputWidth(inputRef, text+singleSuggestion), [inputRef, text, singleSuggestion]);
|
||||
|
||||
useEffect(() => {
|
||||
if (focus) {
|
||||
|
|
@ -81,7 +83,6 @@ export function InputBlock({ state, setState, suggestionPriority, onCancel }: In
|
|||
}
|
||||
|
||||
const onTextChange = newText => {
|
||||
const found = trie.get(env.name2dyn)(newText);
|
||||
setState(state => ({...state, text: newText}));
|
||||
}
|
||||
|
||||
|
|
@ -162,51 +163,67 @@ export function InputBlock({ state, setState, suggestionPriority, onCancel }: In
|
|||
}
|
||||
};
|
||||
|
||||
return <span>
|
||||
<span className="">
|
||||
{/* Dropdown suggestions */}
|
||||
{haveFocus &&
|
||||
<span style={{display:'inline-block'}}>
|
||||
return <span className="inputBlock">
|
||||
{/* Dropdown suggestions */}
|
||||
{haveFocus &&
|
||||
<span className="suggestionsPlaceholder">
|
||||
<Suggestions
|
||||
suggestions={suggestions}
|
||||
onSelect={onSelectSuggestion}
|
||||
i={i} setI={setI} />
|
||||
</span>
|
||||
}
|
||||
{/* Input box */}
|
||||
<input ref={inputRef}
|
||||
placeholder="<name or literal>"
|
||||
className="editable"
|
||||
value={text}
|
||||
onInput={onInput}
|
||||
onKeyDown={onKeyDown}
|
||||
onFocus={() => setHaveFocus(true)}
|
||||
onBlur={() => setHaveFocus(false)}
|
||||
spellCheck={false}/>
|
||||
{/* Single 'grey' suggestion */}
|
||||
<span className="text-block suggest">{singleSuggestion}</span>
|
||||
</span>
|
||||
</span>
|
||||
}
|
||||
{/* Single 'grey' suggestion */}
|
||||
<span className="editable suggest">{text}{singleSuggestion}</span>
|
||||
{/* Input box */}
|
||||
<input ref={inputRef}
|
||||
placeholder="<name or literal>"
|
||||
className="editable"
|
||||
value={text}
|
||||
onInput={onInput}
|
||||
onKeyDown={onKeyDown}
|
||||
onFocus={() => setHaveFocus(true)}
|
||||
onBlur={() => setHaveFocus(false)}
|
||||
spellCheck={false}/>
|
||||
</span>;
|
||||
}
|
||||
|
||||
function Suggestions({ suggestions, onSelect, i, setI }) {
|
||||
return <>{(suggestions.length > 0) &&
|
||||
<div className={"suggestions"}>
|
||||
{suggestions.map((suggestion, j) =>
|
||||
<SuggestionMemo key={j}
|
||||
{...{setI, j,
|
||||
onSelect,
|
||||
highlighted: i===j,
|
||||
suggestion}}/>)}
|
||||
</div>
|
||||
}</>;
|
||||
}
|
||||
|
||||
interface SuggestionProps {
|
||||
setI: any;
|
||||
j: number;
|
||||
onSelect: any;
|
||||
highlighted: boolean;
|
||||
suggestion: PrioritizedSuggestionType;
|
||||
}
|
||||
|
||||
function Suggestion({ setI, j, onSelect, highlighted, suggestion: [priority, kind, name, dynamic] }: SuggestionProps) {
|
||||
const onMouseEnter = j => () => {
|
||||
setI(j);
|
||||
};
|
||||
const onMouseDown = j => () => {
|
||||
setI(j);
|
||||
onSelect(suggestions[i]);
|
||||
onSelect([priority, kind, name, dynamic]);
|
||||
};
|
||||
return <>{(suggestions.length > 0) &&
|
||||
<div className={"suggestions"}>
|
||||
{suggestions.map(([priority, kind, name, dynamic], j) =>
|
||||
<div
|
||||
key={`${j}_${name}`}
|
||||
className={(i === j ? " selected" : "")}
|
||||
onMouseEnter={onMouseEnter(j)}
|
||||
onMouseDown={onMouseDown(j)}>
|
||||
({priority}) ({kind}) {name} :: <Type type={getType(dynamic)} />
|
||||
</div>)}
|
||||
</div>
|
||||
}</>;
|
||||
}
|
||||
return <div
|
||||
key={`${j}_${name}`}
|
||||
className={(highlighted ? " selected" : "")}
|
||||
onMouseEnter={onMouseEnter(j)}
|
||||
onMouseDown={onMouseDown(j)}>
|
||||
({priority}) ({kind}) {name} :: <Type type={getType(dynamic)} />
|
||||
</div>
|
||||
}
|
||||
|
||||
const SuggestionMemo = memo<SuggestionProps>(Suggestion);
|
||||
|
|
@ -4,7 +4,7 @@ import { growEnv } from "dope2";
|
|||
|
||||
import { Editor, type EditorState } from "./Editor";
|
||||
import { EnvContext } from "./EnvContext";
|
||||
import { DeepError, evalEditorBlock, type ResolvedType } from "./eval";
|
||||
import { evalEditorBlock, type ResolvedType } from "./eval";
|
||||
import { type State2Props } from "./Editor";
|
||||
import { autoInputWidth } from "./util/dom_trickery";
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ interface LetInBlockProps extends State2Props<LetInBlockState> {
|
|||
}
|
||||
|
||||
export function makeInnerEnv(env, name: string, value: ResolvedType) {
|
||||
if (value && !(value instanceof DeepError)) {
|
||||
if (value.kind === "value") {
|
||||
return growEnv(env)(name)(value)
|
||||
}
|
||||
return env;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import {getType, getInst, getSymbol, Double, Int, symbolFunction, symbolProduct, symbolSum, symbolDict, symbolSet, symbolList, eqType, match, getLeft, getRight, dict, Bool, set} from "dope2";
|
||||
import {getType, getInst, getSymbol, Double, Int, symbolFunction, symbolProduct, symbolSum, symbolDict, symbolSet, symbolList, eqType, match, getLeft, getRight, dict, Bool, set, Unit} from "dope2";
|
||||
|
||||
import "./Value.css";
|
||||
|
||||
|
|
@ -14,6 +14,9 @@ export function Value({dynamic}) {
|
|||
if (eqType(type)(Bool)) {
|
||||
return <ValueBool val={inst}/>;
|
||||
}
|
||||
if (eqType(type)(Unit)) {
|
||||
return <ValueUnit/>;
|
||||
}
|
||||
|
||||
const symbol = getSymbol(type);
|
||||
switch (symbol) {
|
||||
|
|
@ -47,9 +50,6 @@ function ValueFunction() {
|
|||
function ValueBool({val}) {
|
||||
return <span className="valuePrimitive">{val.toString()}</span>;
|
||||
}
|
||||
// function Sum({val, elemType}) {
|
||||
// return
|
||||
// }
|
||||
function ValueList({val, elemType}) {
|
||||
return <span className="listType">[{val.map((v, i) => <Value key={i} dynamic={{i:v, t:elemType}}/>)}]</span>;
|
||||
}
|
||||
|
|
@ -57,9 +57,10 @@ function ValueSet({val, elemType}) {
|
|||
return <span className="setType">{'{'}{set.fold(acc => elem => acc.concat([elem]))([])(val).map((v, i) => <Value key={i} dynamic={{i:v, t:elemType}}/>)}{'}'}</span>;
|
||||
}
|
||||
function ValueDict({val, keyType, valueType}) {
|
||||
return <span className="dictType">{'{'}{set.fold(acc => key => value => acc.concat([[key,value]]))([])(val).map(([key, value], i) => <span key={i}>
|
||||
<Value key={i} dynamic={{i:key, t:keyType}}/>
|
||||
<Value key={i} dynamic={{i:value, t:valueType}}/>
|
||||
return <span className="dictType">{'{'}{dict.fold(acc => key => value => acc.concat([[key,value]]))([])(val).map(([key, value], i) => <span key={i}>
|
||||
<Value dynamic={{i:key, t:keyType}}/>
|
||||
⇒
|
||||
<Value dynamic={{i:value, t:valueType}}/>
|
||||
</span>)}{'}'}</span>;
|
||||
}
|
||||
function ValueSum({val, leftType, rightType}) {
|
||||
|
|
@ -70,23 +71,6 @@ function ValueSum({val, leftType, rightType}) {
|
|||
function ValueProduct({val, leftType, rightType}) {
|
||||
return <span className="productType">(<Value dynamic={{i:getLeft(val), t:leftType}}/>, <Value dynamic={{i:getRight(val), t:rightType}} />)</span>;
|
||||
}
|
||||
// function ValueDict({val, keyType, valueType}) {
|
||||
// let i=0;
|
||||
// return <span className="dictType">{'{'}<>{
|
||||
// dict.fold
|
||||
// (acc => key => value => {
|
||||
// console.log({acc, key, value});
|
||||
// return acc.concat([<>
|
||||
// <Value key={i++} dynamic={{i: key, t: keyType}}/>
|
||||
// ⇒
|
||||
// <Value key={i++} dynamic={{i: value, t: valueType}}/>
|
||||
// </>]);
|
||||
// })
|
||||
// ([])
|
||||
// (val)
|
||||
// .map(result => {
|
||||
// console.log(result);
|
||||
// return result;
|
||||
// })
|
||||
// }</>{'}'}</span>;
|
||||
// }
|
||||
function ValueUnit() {
|
||||
return <>{'()'}</>;
|
||||
}
|
||||
|
|
|
|||
150
src/eval.ts
150
src/eval.ts
|
|
@ -1,27 +1,32 @@
|
|||
import { apply, Double, Int, NotAFunctionError, trie, UnifyError } from "dope2";
|
||||
import { apply, assignFn, Double, getSymbol, Int, makeGeneric, NotAFunctionError, prettyT, symbolFunction, trie, UnifyError } from "dope2";
|
||||
|
||||
import type { EditorState } from "./Editor";
|
||||
import type { InputValueType } from "./InputBlock";
|
||||
import { makeInnerEnv } from "./LetInBlock";
|
||||
import { parseDouble, parseInt } from "./util/parse";
|
||||
|
||||
export class DeepError {
|
||||
export interface DeepError {
|
||||
kind: "error";
|
||||
e: Error;
|
||||
depth: number;
|
||||
constructor(e, depth) {
|
||||
this.e = e;
|
||||
this.depth = depth;
|
||||
}
|
||||
};
|
||||
t: any;
|
||||
}
|
||||
|
||||
// a dynamically typed value = tuple (instance, type)
|
||||
export interface Dynamic {
|
||||
kind: "value",
|
||||
i: any;
|
||||
t: any;
|
||||
};
|
||||
|
||||
export interface Unknown {
|
||||
kind: "unknown";
|
||||
t: any;
|
||||
}
|
||||
|
||||
export const entirelyUnknown: Unknown = { kind: "unknown", t: makeGeneric(a => a) };
|
||||
|
||||
// the value of every block is either known (Dynamic), an error, or unknown
|
||||
export type ResolvedType = Dynamic | DeepError | undefined;
|
||||
export type ResolvedType = Dynamic | DeepError | Unknown;
|
||||
|
||||
export const evalEditorBlock = (s: EditorState, env): ResolvedType => {
|
||||
if (s.kind === "input") {
|
||||
|
|
@ -39,8 +44,9 @@ export const evalEditorBlock = (s: EditorState, env): ResolvedType => {
|
|||
}
|
||||
if (s.kind === "lambda") {
|
||||
const expr = evalEditorBlock(s.expr, env);
|
||||
return undefined; // todo
|
||||
// todo
|
||||
}
|
||||
return entirelyUnknown; // todo
|
||||
};
|
||||
|
||||
export function evalInputBlock(text: string, value: InputValueType, env): ResolvedType {
|
||||
|
|
@ -48,45 +54,115 @@ export function evalInputBlock(text: string, value: InputValueType, env): Resolv
|
|||
return parseLiteral(text, value.type);
|
||||
}
|
||||
else if (value.kind === "name") {
|
||||
return trie.get(env.name2dyn)(text);
|
||||
const found = trie.get(env.name2dyn)(text);
|
||||
if (found) {
|
||||
return { kind: "value", ...found };
|
||||
} else {
|
||||
return entirelyUnknown;
|
||||
}
|
||||
}
|
||||
else { // kind === "text" -> unresolved
|
||||
return;
|
||||
return entirelyUnknown;
|
||||
}
|
||||
}
|
||||
|
||||
export function evalCallBlock(fn: ResolvedType, input: ResolvedType) {
|
||||
if (haveValue(input) && haveValue(fn)) {
|
||||
try {
|
||||
const outputResolved = apply(input)(fn); // may throw
|
||||
return outputResolved; // success
|
||||
}
|
||||
catch (e) {
|
||||
if (!(e instanceof UnifyError) && !(e instanceof NotAFunctionError)) {
|
||||
throw e;
|
||||
}
|
||||
return new DeepError(e, 0); // eval error
|
||||
export function evalCallBlock(fn: ResolvedType, input: ResolvedType): ResolvedType {
|
||||
if (getSymbol(fn.t) !== symbolFunction) {
|
||||
if (fn.kind === "unknown") {
|
||||
return entirelyUnknown; // don't flash everything red, giving the user a heart attack
|
||||
}
|
||||
// worst outcome: we know nothing about the result!
|
||||
return {
|
||||
kind: "error",
|
||||
e: new NotAFunctionError(`${prettyT(fn.t)} is not a function type!`),
|
||||
t: entirelyUnknown.t,
|
||||
depth: 0,
|
||||
};
|
||||
}
|
||||
else if (input instanceof DeepError) {
|
||||
return input; // bubble up the error
|
||||
}
|
||||
else if (fn instanceof DeepError) {
|
||||
return new DeepError(fn.e, fn.depth+1);
|
||||
}
|
||||
}
|
||||
try {
|
||||
// fn is a function...
|
||||
const outType = assignFn(fn.t, input.t); // may throw
|
||||
|
||||
function parseLiteral(text: string, type: string) {
|
||||
// dirty
|
||||
if (type === "Int") {
|
||||
return { i: parseInt(text), t: Int };
|
||||
if (input.kind === "error") {
|
||||
return {
|
||||
kind: "error",
|
||||
e: input.e, // bubble up the error
|
||||
depth: 0,
|
||||
t: outType,
|
||||
};
|
||||
}
|
||||
if (fn.kind === "error") {
|
||||
// also bubble up
|
||||
return {
|
||||
kind: "error",
|
||||
e: fn.e,
|
||||
depth: fn.depth+1,
|
||||
t: outType,
|
||||
};
|
||||
}
|
||||
// if the above statement did not throw => types are compatible...
|
||||
if (input.kind === "value" && fn.kind === "value") {
|
||||
const outValue = fn.i(input.i);
|
||||
return { kind: "value", i: outValue, t: outType };
|
||||
}
|
||||
else {
|
||||
// we don't know the value, but we do know the type:
|
||||
return { kind: "unknown", t: outType };
|
||||
}
|
||||
}
|
||||
if (type === "Double") {
|
||||
return { i: parseDouble(text), t: Double };
|
||||
catch (e) {
|
||||
if ((e instanceof UnifyError)) {
|
||||
// even though fn was incompatible with the given parameter, we can still suppose that our output-type will be that of fn...?
|
||||
const outType = fn.t.params[1](fn.t);
|
||||
return {
|
||||
kind: "error",
|
||||
e,
|
||||
depth: 0,
|
||||
t: outType,
|
||||
};
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
export function haveValue(resolved: ResolvedType) {
|
||||
return resolved && !(resolved instanceof DeepError);
|
||||
// return resolved && !(resolved instanceof DeepError);
|
||||
return resolved.kind === "value";
|
||||
}
|
||||
|
||||
function parseLiteral(text: string, type: string): ResolvedType {
|
||||
// dirty
|
||||
if (type === "Int") {
|
||||
return parseAsInt(text);
|
||||
}
|
||||
if (type === "Double") {
|
||||
return parseAsDouble(text);
|
||||
}
|
||||
return entirelyUnknown;
|
||||
}
|
||||
|
||||
function parseAsDouble(text: string): ResolvedType {
|
||||
if (text !== '') {
|
||||
const num = Number(text);
|
||||
if (!Number.isNaN(num)) {
|
||||
return { kind: "value", i: num, t: Double };
|
||||
}
|
||||
}
|
||||
return entirelyUnknown;
|
||||
}
|
||||
function parseAsInt(text: string): ResolvedType {
|
||||
if (text !== '') {
|
||||
try {
|
||||
return { kind: "value", i: BigInt(text), t: Int }; // may throw
|
||||
}
|
||||
catch {}
|
||||
}
|
||||
return entirelyUnknown;
|
||||
}
|
||||
|
||||
const literalParsers = [parseAsDouble, parseAsInt];
|
||||
|
||||
export function attemptParseLiteral(text: string): Dynamic[] {
|
||||
return literalParsers.map(parseFn => parseFn(text))
|
||||
.filter(resolved => (resolved.kind !== "unknown" && resolved.kind !== "error")) as unknown as Dynamic[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1 @@
|
|||
|
||||
// Helpers...
|
||||
export function parseDouble(text: string): number | undefined {
|
||||
if (text === '') {
|
||||
return;
|
||||
}
|
||||
const num = Number(text);
|
||||
if (Number.isNaN(num)) {
|
||||
return;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
export function parseInt(text: string): bigint | undefined {
|
||||
if (text === '') {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
return BigInt(text);
|
||||
}
|
||||
catch (e) { };
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue