Compare commits
No commits in common. "78fa27f70a25f680659700992840e3caaf6ed5c0" and "b0726bc0fb22120b704f914f682eb7cc8d74994a" have entirely different histories.
78fa27f70a
...
b0726bc0fb
7 changed files with 121 additions and 277 deletions
44
src/ast.ts
44
src/ast.ts
|
|
@ -173,13 +173,6 @@ export type AST =
|
|||
| Definition
|
||||
| Rebind
|
||||
|
||||
const infixOps: { [key: string]: string } = {
|
||||
cat: '&', add: '+', sub: '-', mul: '*', div: '/', mod: '%',
|
||||
pow: '^', eq: '==', neq: '!=', gt: '>', lt: '<', gte: '>=', lte: '<='
|
||||
};
|
||||
|
||||
const isInfix = (a: AST) => a.kind === 'apply' && a.func.kind === 'variable' && a.args.length === 2 && infixOps[a.func.name];
|
||||
|
||||
export function prettyPrint(ast: AST, indent = 0): string {
|
||||
const i = ' '.repeat(indent);
|
||||
|
||||
|
|
@ -202,43 +195,23 @@ export function prettyPrint(ast: AST, indent = 0): string {
|
|||
return ast.name;
|
||||
|
||||
case 'apply':
|
||||
// infix ops
|
||||
if (isInfix(ast)) {
|
||||
const wrapIfNeeded = (a: AST) => {
|
||||
const printed = prettyPrint(a, indent);
|
||||
if (a.kind === 'apply' || a.kind === 'lambda' || a.kind === 'match' || a.kind === 'let') {
|
||||
return `(${printed})`;
|
||||
}
|
||||
return `${printed}`;
|
||||
|
||||
}
|
||||
const left = wrapIfNeeded(ast.args[0]);
|
||||
const right = wrapIfNeeded(ast.args[1]);
|
||||
return `${left} ${infixOps[(ast.func as Variable).name]} ${right}`;
|
||||
}
|
||||
|
||||
const func = prettyPrint(ast.func, indent);
|
||||
const args = ast.args.map(a => {
|
||||
const printed = prettyPrint(a, indent);
|
||||
if (a.kind === 'lambda' || a.kind === 'match' || a.kind === 'let' || a.kind === 'rebind' || a.kind === 'apply') {
|
||||
if (a.kind === 'lambda' || a.kind === 'match' || a.kind === 'let' || a.kind === 'rebind') {
|
||||
return `(${printed})`;
|
||||
}
|
||||
return printed;
|
||||
}).join(' ');
|
||||
|
||||
if (ast.func.kind === 'lambda' || ast.func.kind === 'match' || ast.func.kind === 'let') {
|
||||
return `(${func} ${args})`
|
||||
}
|
||||
return `${func} ${args}`
|
||||
|
||||
case 'let':
|
||||
const sep = indent === 0 ? '\n\n' : '\n';
|
||||
return `${ast.name} = ${prettyPrint(ast.value, indent + 1)};${sep}${i}${prettyPrint(ast.body, indent)}`
|
||||
return `${ast.name} = ${prettyPrint(ast.value, indent + 1)};\n${i}${prettyPrint(ast.body, indent)}`
|
||||
|
||||
case 'list': {
|
||||
const elems = ast.elements.map(e => prettyPrint(e, indent + 1))
|
||||
const oneLine = `[${elems.join(', ')}]`;
|
||||
if (oneLine.length <= 60 || elems.length <= 1) return oneLine;
|
||||
if (elems.length <= 1) return `[${elems.join(', ')}]`;
|
||||
const inner = elems.map(e => `${' '.repeat(indent + 1)}${e}`).join(',\n');
|
||||
return `[\n${inner}\n${i}]`;
|
||||
}
|
||||
|
|
@ -258,8 +231,8 @@ export function prettyPrint(ast: AST, indent = 0): string {
|
|||
const body = prettyPrint(ast.body, indent + 1);
|
||||
const isComplex = ast.body.kind === 'match' || ast.body.kind === 'let';
|
||||
if (isComplex) {
|
||||
const ii = ' '.repeat(indent + 1);
|
||||
return `${params} \\\n${ii}${body}`
|
||||
return `${params} \\\n${body}`
|
||||
|
||||
}
|
||||
return `${params} \\ ${body}`
|
||||
}
|
||||
|
|
@ -278,11 +251,8 @@ export function prettyPrint(ast: AST, indent = 0): string {
|
|||
case 'match':
|
||||
const expr = prettyPrint(ast.expr, indent);
|
||||
const cases = ast.cases
|
||||
.map(c => {
|
||||
const result = prettyPrint(c.result, indent + 1);
|
||||
const needsParens = c.result.kind === 'match' || c.result.kind === 'let';
|
||||
return `${i}| ${prettyPrintPattern(c.pattern)} \\ ${needsParens ? '(' : ''}${result}${needsParens ? ')' : ''}`;
|
||||
}).join('\n');
|
||||
.map(c => `${i}| ${prettyPrintPattern(c.pattern)} \\ ${prettyPrint(c.result, indent + 1)}`)
|
||||
.join('\n');
|
||||
return `${expr}\n${cases}`;
|
||||
|
||||
case 'rebind':
|
||||
|
|
|
|||
|
|
@ -77,16 +77,6 @@ join = s list \ list
|
|||
| [x] \ x
|
||||
| [x, ...xs] \ (x & s & (join s xs));
|
||||
|
||||
# repeatStr : Int \ String \ String
|
||||
repeatStr = n str \ n
|
||||
| 0 \ ""
|
||||
| m \ str & (repeatStr (m - 1) str);
|
||||
|
||||
# repeat : Int \ a \ List a
|
||||
repeat = n a \ n
|
||||
| 0 \ []
|
||||
| m \ [a, ...repeat (m - 1) a];
|
||||
|
||||
# zipWith : (a \ b \ c) \ List a \ List b \ List c
|
||||
zipWith = f l1 l2 \ l1
|
||||
| [] \ []
|
||||
|
|
|
|||
|
|
@ -134,8 +134,8 @@ scrollable = config \
|
|||
| False \ []),
|
||||
...(showHBar
|
||||
| True \ [ui.positioned {
|
||||
x = hBarX,
|
||||
y = c.h - 4,
|
||||
x = c.h - 4,
|
||||
y = hBarX,
|
||||
child = ui.rect { h = 4, w = hBarWidth, color = "rgba(255,255,255,0.3)", radius = 2 }
|
||||
}]
|
||||
| False \ [])
|
||||
|
|
|
|||
|
|
@ -1,52 +1,10 @@
|
|||
textEditorBuffers = [];
|
||||
textEditor = name \
|
||||
# defaults = {};
|
||||
# c = { ...defaults, ...config };
|
||||
scale = 2;
|
||||
charH = 12;
|
||||
charW = 5;
|
||||
lineGap = 1;
|
||||
charGap = 2;
|
||||
lineH = charH * scale + lineGap;
|
||||
|
||||
buffersKey = "textEditorBuffers";
|
||||
|
||||
# load from staging buffers if it exists there. if not, load from source
|
||||
source = getAt [buffersKey, name]
|
||||
| None \ getSource name
|
||||
| Some v \ v;
|
||||
|
||||
source = getSource name;
|
||||
lines = split "\n" source;
|
||||
|
||||
clampCursor = state \
|
||||
line = nth state.cursorRow state.lines ~ unwrapOr "";
|
||||
|
||||
newRow = max 0 state.cursorRow;
|
||||
newRow2 = min (len state.lines - 1) newRow;
|
||||
|
||||
maxCol = state.mode
|
||||
| Insert \ len line
|
||||
| Normal \ max 0 (len line - 1);
|
||||
newCol2 = min maxCol (max 0 state.cursorCol);
|
||||
|
||||
state.{ cursorRow = newRow2, cursorCol = newCol2 };
|
||||
|
||||
write = state \
|
||||
content = join "\n" state.lines;
|
||||
{ state = state, emit = [rebindAt [buffersKey, name] content] };
|
||||
|
||||
apply = state \
|
||||
content = name & " = " & (join "\n" state.lines) & ";";
|
||||
result = eval! content;
|
||||
_ = debug! "apply" [content, result];
|
||||
result
|
||||
| Defined _ \ { state = state, emit = [] }
|
||||
| Err msg \ (
|
||||
_ = debug! "error applying" [];
|
||||
{ state = state, emit = [] }
|
||||
)
|
||||
| _ \ { state = state, emit = [] };
|
||||
|
||||
insertChar = char state \
|
||||
newLines = updateAt state.cursorRow (line \
|
||||
insertCharAt line state.cursorCol char
|
||||
|
|
@ -65,36 +23,6 @@ textEditor = name \
|
|||
emit = []
|
||||
};
|
||||
|
||||
# 'o' key
|
||||
openLine = state \
|
||||
stateSnapshot = { lines = state.lines, cursorRow = state.cursorRow, cursorCol = state.cursorCol };
|
||||
newUndoStack = [stateSnapshot, ...state.undoStack];
|
||||
cursorRow = state.cursorRow + 1;
|
||||
newLines = insertAt cursorRow "" state.lines;
|
||||
{
|
||||
state = state.{
|
||||
lines = newLines,
|
||||
cursorRow = cursorRow,
|
||||
cursorCol = 0,
|
||||
undoStack = newUndoStack,
|
||||
mode = Insert
|
||||
},
|
||||
emit = []
|
||||
};
|
||||
|
||||
deleteLine = state \
|
||||
stateSnapshot = { lines = state.lines, cursorRow = state.cursorRow, cursorCol = state.cursorCol };
|
||||
newUndoStack = [stateSnapshot, ...state.undoStack];
|
||||
newLines = [...(take (state.cursorRow) state.lines), ...(drop (state.cursorRow + 1) state.lines)];
|
||||
{
|
||||
state = clampCursor state.{
|
||||
lines = newLines,
|
||||
undoStack = newUndoStack,
|
||||
pending = None
|
||||
},
|
||||
emit = []
|
||||
};
|
||||
|
||||
escape = state \ { state = state.{ mode = Normal }, emit = [] };
|
||||
|
||||
undo = state \ state.undoStack
|
||||
|
|
@ -136,57 +64,42 @@ textEditor = name \
|
|||
newLines = updateAt state.cursorRow (_ \ before) state.lines;
|
||||
newLines2 = insertAt (state.cursorRow + 1) after newLines;
|
||||
|
||||
_ = debug! "enter" { before = before, after = after, newLines2 = newLines2, newRow =
|
||||
state.cursorRow + 1 };
|
||||
|
||||
{ state = state.{ lines = newLines2, cursorCol = 0, cursorRow = state.cursorRow + 1 }, emit = [] };
|
||||
|
||||
upArrow = state \
|
||||
clampCursor = state \
|
||||
line = nth state.cursorRow state.lines ~ unwrapOr "";
|
||||
|
||||
newRow = max 0 state.cursorRow;
|
||||
newRow2 = min (len state.lines - 1) newRow;
|
||||
|
||||
maxCol = state.mode
|
||||
| Insert \ len line
|
||||
| Normal \ max 0 (len line - 1);
|
||||
newCol2 = min maxCol (max 0 state.cursorCol);
|
||||
|
||||
state.{ cursorRow = newRow2, cursorCol = newCol2 };
|
||||
|
||||
upArrow = state \ (
|
||||
newState = clampCursor state.{ cursorRow = state.cursorRow - 1 };
|
||||
{ state = newState, emit = [] };
|
||||
{ state = newState, emit = [] });
|
||||
|
||||
downArrow = state \
|
||||
downArrow = state \ (
|
||||
newState = clampCursor state.{ cursorRow = state.cursorRow + 1 };
|
||||
{ state = newState, emit = [] };
|
||||
{ state = newState, emit = [] });
|
||||
|
||||
leftArrow = state \
|
||||
leftArrow = state \ (
|
||||
newState = clampCursor state.{ cursorCol = state.cursorCol - 1 };
|
||||
{ state = newState, emit = [] };
|
||||
{ state = newState, emit = [] });
|
||||
|
||||
rightArrow = state \
|
||||
rightArrow = state \ (
|
||||
newState = clampCursor state.{ cursorCol = state.cursorCol + 1 };
|
||||
{ state = newState, emit = [] };
|
||||
{ state = newState, emit = [] });
|
||||
|
||||
{
|
||||
view = ctx \
|
||||
|
||||
autoScroll = state \
|
||||
cursorY = state.cursorRow * lineH;
|
||||
newScrollY = (cursorY < state.scrollY
|
||||
| True \ cursorY
|
||||
| False \ (cursorY + lineH > state.scrollY + ctx.h
|
||||
| True \ cursorY + lineH - ctx.h
|
||||
| False \ state.scrollY));
|
||||
|
||||
cursorX = state.cursorCol * charW * scale + charGap * state.cursorCol;
|
||||
newScrollX = (cursorX < state.scrollX
|
||||
| True \ cursorX
|
||||
| False \ (cursorX + charW * scale > state.scrollX + ctx.w
|
||||
| True \ cursorX + charW * scale - ctx.w
|
||||
| False \ state.scrollX));
|
||||
state.{ scrollY = newScrollY, scrollX = newScrollX };
|
||||
|
||||
withScroll = result \
|
||||
{ state = autoScroll result.state, emit = result.emit };
|
||||
|
||||
scrollHalfUp = state ctx \ (
|
||||
diff = floor ((ctx.h / 2) / lineH);
|
||||
newRow = state.cursorRow - diff;
|
||||
withScroll { state = clampCursor state.{ cursorRow = newRow }, emit = [] });
|
||||
|
||||
scrollHalfDown = state ctx \ (
|
||||
diff = floor ((ctx.h / 2) / lineH);
|
||||
newRow = state.cursorRow + diff;
|
||||
withScroll { state = clampCursor state.{ cursorRow = newRow }, emit = [] });
|
||||
|
||||
ui.stateful {
|
||||
view = ctx \ ui.stateful {
|
||||
focusable = True,
|
||||
autoFocus = True,
|
||||
|
||||
|
|
@ -200,88 +113,87 @@ textEditor = name \
|
|||
scrollY = 0,
|
||||
undoStack = [],
|
||||
redoStack = [],
|
||||
mode = Normal, # Normal | Insert | Visual
|
||||
pending = None # Some "d" | Some "g" | etc.
|
||||
mode = Normal # Normal | Insert | Visual
|
||||
},
|
||||
|
||||
update = state event \ state.mode
|
||||
| Insert \ (event
|
||||
| Key { key = "Escape" } \ escape state
|
||||
| Key { key = "Control" } \ escape state
|
||||
| Key { key = "Backspace" } \ backspace state
|
||||
| Key { key = "Enter" } \ enter state
|
||||
| Key { key = k } \ insertChar k state)
|
||||
| Normal \ (event
|
||||
| Scrolled delta \ (
|
||||
maxLineLen = fold (acc line \ max acc (len line)) 0 state.lines;
|
||||
totalW = maxLineLen * charW * scale + maxLineLen * charGap;
|
||||
totalH = len state.lines * lineH;
|
||||
newX = max 0 (min (totalW - ctx.w) (state.scrollX + delta.deltaX));
|
||||
newY = max 0 (min (totalH - ctx.h) (state.scrollY + delta.deltaY));
|
||||
{ state = state.{ scrollY = newY, scrollX = newX }, emit = [] })
|
||||
update = state event \ event
|
||||
| Key { key = "ArrowDown" } \ downArrow state
|
||||
| Key { key = "j" } \ (state.mode
|
||||
| Insert \ insertChar "j" state
|
||||
| Normal \ downArrow state)
|
||||
|
||||
| Key { key = "ArrowDown" } \ withScroll (downArrow state)
|
||||
| Key { key = "j" } \ withScroll (downArrow state)
|
||||
| Key { key = "ArrowUp" } \ withScroll (upArrow state)
|
||||
| Key { key = "k" } \ withScroll(upArrow state)
|
||||
| Key { key = "ArrowLeft" } \ withScroll (leftArrow state)
|
||||
| Key { key = "h" } \ withScroll(leftArrow state)
|
||||
| Key { key = "ArrowRight" } \ withScroll (rightArrow state)
|
||||
| Key { key = "l" } \ withScroll(rightArrow state)
|
||||
| Key { key = "i" } \ insertMode state
|
||||
| Key { key = "o" } \ openLine state
|
||||
| Key { key = "d", ctrl = True } \ scrollHalfDown state ctx
|
||||
| Key { key = "u", ctrl = True } \ scrollHalfUp state ctx
|
||||
| Key { key = "u" } \ undo state
|
||||
| Key { key = "d" } \ (state.pending
|
||||
| Some "d" \ deleteLine state
|
||||
| _ \ { state = state.{ pending = Some "d" }, emit = [] })
|
||||
| Key { key = "r", ctrl = True } \ redo state
|
||||
| Key { key = "W", ctrl = True, shift = True } \ write state
|
||||
| Key { key = "A", ctrl = True, shift = True } \ apply state
|
||||
# any other key or event
|
||||
| _ \ { state = state, emit = [] }
|
||||
),
|
||||
| Key { key = "ArrowUp" } \ upArrow state
|
||||
| Key { key = "k" } \ (state.mode
|
||||
| Insert \ insertChar "k" state
|
||||
| Normal \ upArrow state)
|
||||
|
||||
| Key { key = "ArrowLeft" } \ leftArrow state
|
||||
| Key { key = "h" } \ (state.mode
|
||||
| Insert \ insertChar "h" state
|
||||
| Normal \ leftArrow state)
|
||||
|
||||
| Key { key = "ArrowRight" } \ rightArrow state
|
||||
| Key { key = "l" } \ (state.mode
|
||||
| Insert \ insertChar "l" state
|
||||
| Normal \ rightArrow state)
|
||||
|
||||
| Key { key = "i" } \ (state.mode
|
||||
| Insert \ insertChar "i" state
|
||||
| Normal \ insertMode state)
|
||||
|
||||
| Key { key = "u" } \ (state.mode
|
||||
| Insert \ insertChar "u" state
|
||||
| Normal \ undo state)
|
||||
|
||||
| Key { key = "r", ctrl = True } \ (state.mode
|
||||
| Insert \ insertChar "R" state
|
||||
| Normal \ redo state)
|
||||
|
||||
| Key { key = "Escape" } \ escape state
|
||||
|
||||
| Key { key = "Backspace" } \ (state.mode
|
||||
| Insert \ backspace state
|
||||
| _ \ { state = state, emit = [] })
|
||||
|
||||
| Key { key = "Enter" } \ (state.mode
|
||||
| Insert \ enter state
|
||||
| _ \ { state = state, emit = [] })
|
||||
|
||||
# any other key
|
||||
| Key { key = key, printable = True } \ (state.mode
|
||||
| Insert \ insertChar key state
|
||||
| _ \ { state = state, emit = [] })
|
||||
|
||||
| _ \ { state = state, emit = [] },
|
||||
|
||||
view = state emit \
|
||||
cursorX = state.cursorCol * charW * scale + charGap * state.cursorCol;
|
||||
cursorY = state.cursorRow * lineH;
|
||||
buffer = map (l \ text l) state.lines;
|
||||
|
||||
scale = 2;
|
||||
charH = 12;
|
||||
charW = 5;
|
||||
lineGap = 1;
|
||||
charGap = 2;
|
||||
|
||||
cursor = ui.positioned {
|
||||
x = cursorX,
|
||||
y = cursorY,
|
||||
x = state.cursorCol * charW * scale + charGap * state.cursorCol,
|
||||
y = state.cursorRow * charH * scale + lineGap * state.cursorRow,
|
||||
child = ui.rect { w = charW * scale, h = charH * scale, color = "rgba(255,255,255,0.5)" }
|
||||
};
|
||||
|
||||
maxLineLen = fold (acc line \ max acc (len line)) 0 state.lines;
|
||||
totalWidth = maxLineLen * charW * scale + maxLineLen * charGap;
|
||||
totalHeight = len state.lines * lineH;
|
||||
|
||||
# buffer = map (l \ text l) state.lines;
|
||||
# only render visible lines
|
||||
startLine = max 0 (floor (state.scrollY / lineH));
|
||||
visibleCount = floor (ctx.h / lineH) + 2;
|
||||
endLine = min (len state.lines) (startLine + visibleCount);
|
||||
visibleLines = slice state.lines startLine endLine;
|
||||
buffer = mapWithIndex (l i \
|
||||
ui.positioned {
|
||||
x = 0,
|
||||
y = (startLine + i) * lineH,
|
||||
child = text l
|
||||
}
|
||||
) visibleLines;
|
||||
|
||||
scrollable {
|
||||
w = ctx.w,
|
||||
h = ctx.h,
|
||||
totalWidth = totalWidth,
|
||||
totalHeight = totalHeight,
|
||||
totalWidth = 1000,
|
||||
totalHeight = 1000,
|
||||
scrollX = state.scrollX,
|
||||
scrollY = state.scrollY,
|
||||
onScroll = delta \ emit (Scrolled delta),
|
||||
child = ui.stack {
|
||||
children = [
|
||||
...buffer,
|
||||
ui.column {
|
||||
gap = 1,
|
||||
children = buffer
|
||||
},
|
||||
cursor
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ treeNode = config \
|
|||
|
||||
valueLabel = value \ value
|
||||
| NumberValue n \ Some (show n)
|
||||
| StringValue n \ Some ("\"" & r & "\"")
|
||||
| StringValue n \ Some ("\"" & n & "\"")
|
||||
| ConstructorValue { tag = tag } \ Some tag
|
||||
| FunctionValue _ \ Some "<fn>"
|
||||
| _ \ None;
|
||||
|
|
@ -105,7 +105,7 @@ treeNode = config \
|
|||
h = 30,
|
||||
onSubmit = onSubmit
|
||||
}
|
||||
| False \ simple config.path (config.prefix & "\"" & (slice n 0 30) & "..\"") "#f6a" (Some (_ \ config.onEditLeaf config.path)))
|
||||
| False \ simple config.path (config.prefix & "\"" & n & "\"") "#f6a" (Some (_ \ config.onEditLeaf config.path)))
|
||||
|
||||
| ConstructorValue { tag = tag } \ (isEditing
|
||||
| True \ textInput {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
import { render, hitTest, scrollHitTest } from './ui';
|
||||
import { syncToAst, saveDefinitions } from './runtime-js';
|
||||
import { definitions } from './compiler';
|
||||
|
||||
type UIValue = any;
|
||||
|
||||
|
|
@ -219,28 +217,6 @@ export function runAppCompiled(canvas: HTMLCanvasElement, store: any) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (event._tag === 'DeleteAt') {
|
||||
const path = event._0;
|
||||
const name = path[0];
|
||||
if (path.length === 1) {
|
||||
delete store[name];
|
||||
definitions.delete(name);
|
||||
saveDefinitions();
|
||||
} else {
|
||||
let obj = store[name];
|
||||
for (let i = 1; i < path.length - 1; i++) {
|
||||
if (obj === undefined || obj === null) return;
|
||||
obj = obj[path[i]];
|
||||
}
|
||||
if (obj !== undefined && obj !== null) {
|
||||
delete obj[path[path.length - 1]];
|
||||
}
|
||||
}
|
||||
syncToAst(name);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (event._tag === 'Focus') {
|
||||
setFocus(event._0);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -80,7 +80,6 @@ export const _rt = {
|
|||
return String(value);
|
||||
},
|
||||
chars: (s: string) => s.split(''),
|
||||
isWordChar: (s: string) => ({ _tag: /^\w$/.test(s) ? 'True' : 'False' }),
|
||||
// join: (delim: string) => (xs: string[]) => xs.join(delim),
|
||||
split: (delim: string) => (xs: string) => xs.split(delim),
|
||||
slice: (s: string | any[]) => (start: number) => (end: number) => s.slice(start, end),
|
||||
|
|
@ -161,9 +160,6 @@ export const _rt = {
|
|||
|
||||
return { _tag: 'Rebind', _0: name, _1: rest, _2: value };
|
||||
},
|
||||
deleteAt: (path: any[]) => {
|
||||
return { _tag: 'DeleteAt', _0: path };
|
||||
},
|
||||
"undefine!": (name: string) => {
|
||||
delete store[name];
|
||||
definitions.delete(name);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue