Fixing pretty print ast. adding a few more builtins :( getting further with inspector

master
Dustin Swan 2 weeks ago
parent 30234875fe
commit 8bc05efa1e
Signed by: dustinswan
GPG Key ID: 30D46587E2100467

@ -109,7 +109,11 @@ export type List = {
export type Record = {
kind: 'record'
fields: { [key: string]: AST }
entries: Array<
| { kind: 'field', key: string, value: AST }
| { kind: 'spread', expr: AST }
>
// fields: { [key: string]: AST }
line?: number
column?: number
start?: number
@ -195,56 +199,72 @@ export function prettyPrint(ast: AST, indent = 0): string {
switch (ast.kind) {
case 'literal': {
const val = ast.value;
switch (val.kind) {
case 'int':
case 'float':
return `${i}${val.value}`;
case 'string':
if (val.kind === 'string') {
return `${i}"${val.value}"`;
}
return `${i}${val.value}`;
}
case 'variable':
return `${i}${ast.name}`;
case 'constructor':
return `${i}${ast.name}`;
return ast.name;
case 'apply':
const func = prettyPrint(ast.func, 0);
const args = ast.args.map(a => prettyPrint(a, 0)).join(' ');
return `${i}(${func} ${args})`
return `(${func} ${args})`
case 'let':
return `${i}let ${ast.name} = \n${prettyPrint(ast.value, indent + 1)}\n${i}in\n${prettyPrint(ast.body, indent + 1)}`
return `${ast.name} = ${prettyPrint(ast.value, indent + 1)};\n${i}${prettyPrint(ast.body, indent)}`
case 'list':
const elems = ast.elements.map(e => prettyPrint(e, 0)).join(', ');
return `${i}[${elems}]`;
return `[${elems}]`;
case 'record':
const fields = Object.entries(ast.fields)
.map(([k, v]) => `${k} = ${prettyPrint(v, 0)}`)
.join(', ');
return `${i}{${fields}}`;
const parts = ast.entries.map(entry =>
entry.kind === 'spread'
? `...${prettyPrint(entry.expr, )}`
: `${entry.key} = ${prettyPrint(entry.value, 0)}`
);
return `{ ${parts.join(', ') }`;
case 'lambda': {
const params = ast.params.join(' ');
const body = prettyPrint(ast.body, indent + 1);
const isComplex = ast.body.kind === 'match' || ast.body.kind === 'let';
if (isComplex) {
return `${params} \\\n${body}`
case 'lambda':
const params = ast.params.join(', ');
return `${i}(${params}) => ${prettyPrint(ast.body)}`
}
return `${params} \\ ${body}`
}
case 'record-access':
return `${i}${prettyPrint(ast.record)}.${ast.field}`;
return `${prettyPrint(ast.record, 0)}.${ast.field}`;
case 'record-update':
const updates = Object.entries(ast.updates).map(([k, v]) => `${k} = ${prettyPrint(v, 0)}`).join(', ');
return `${i}${prettyPrint(ast.record)} { ${updates} }`
case 'record-update': {
const updates = Object.entries(ast.updates)
.map(([k, v]) => `${k} = ${prettyPrint(v, 0)}`)
.join(', ');
return `${prettyPrint(ast.record, 0)}.{ ${updates} }`
}
case 'match':
const expr = prettyPrint(ast.expr, 0);
const cases = ast.cases
.map(c => ` | ${prettyPrintPattern(c.pattern)} -> ${prettyPrint(c.result, 0)}`)
.map(c => `${i}| ${prettyPrintPattern(c.pattern)} \\ ${prettyPrint(c.result, indent + 1)}`)
.join('\n');
return `${i}match ${expr}\n${cases}`;
return `${expr}\n${cases}`;
case 'rebind':
return `${prettyPrint(ast.target, 0)} := ${prettyPrint(ast.value, 0)}`;
case 'list-spread':
return `...${prettyPrint(ast.spread, 0)}`;
case 'definition':
return `${ast.name} = ${prettyPrint(ast.body, indent)}`;
default:
return `Unknown AST kind: ${i}${(ast as any).kind}`
@ -273,6 +293,12 @@ function prettyPrintPattern(pattern: Pattern): string {
const elems = pattern.elements.map(prettyPrintPattern).join(', ');
return `[${elems}]`;
case 'list-spread':
const head = pattern.head.map(prettyPrintPattern).join(', ');
return head.length > 0
? `[${head}, ...${pattern.spread}]`
: `[...${pattern.spread}]`;
case 'record':
const fields = Object.entries(pattern.fields)
.map(([k, p]) => `${k} = ${prettyPrintPattern(p)}`)

@ -1,8 +1,9 @@
# nth : Int \ List a \ Maybe a
nth = i list \ [i, list]
| [_, []] \ None
| [0, [x, ...xs]] \ (Some x)
| [n, [x, ...xs]] \ nth (n - 1) xs;
# in host at the moment, until we get typeclasses or something
# nth = i list \ [i, list]
# | [_, []] \ None
# | [0, [x, ...xs]] \ (Some x)
# | [n, [x, ...xs]] \ nth (n - 1) xs;
# map : (a \ b) \ List a \ List b
map = f list \ list
@ -118,3 +119,5 @@ any = f list \ fold (acc x \ or acc (f x)) False list;
# all : (a \ Bool) \ List a \ Bool
all = f list \ fold (acc x \ and acc (f x)) True list;
# split : String \ String \ String

@ -1,3 +1,5 @@
empty = ui.rect { w = 0, h = 0 };
centerH = parentW child \
childW = (ui.measure child).width;
ui.positioned { x = (parentW - childW) / 2, y = 0, child = child };
@ -10,6 +12,17 @@ center = parentW parentH child \
childSize = ui.measure child;
ui.positioned { x = (parentW - childSize.width) / 2, y = (parentH - childSize.height) / 2, child = child };
scrollable = config \
ui.clip {
w = config.w,
h = config.h,
child = ui.positioned {
x = 0 - config.scrollX,
y = 0 - config.scrollY,
child = config.child
}
};
# button : Record -> ui
button = config \
ui.clickable {
@ -22,25 +35,42 @@ button = config \
}
};
box = config \ ui.stack {
box = config \
defaults = {
w = 100,
h = 100,
color = "white",
borderTop = 0,
borderBottom = 0,
borderLeft = 0,
borderRight = 0,
borderColor = "transparent",
paddingTop = 0,
paddingLeft = 0
};
c = { ...defaults, ...config };
ui.stack {
children = [
# background
ui.rect { w = config.w, h = config.h, color = config.color },
ui.rect { w = c.w, h = c.h, color = c.color },
# top border
ui.rect { w = config.w, h = config.borderTop, color = config.borderColor },
ui.rect { w = c.w, h = c.borderTop, color = c.borderColor },
# bottom border
ui.positioned { x = 0, y = config.h - config.borderBottom, child =
ui.rect { w = config.w, h = config.borderBottom, color = config.borderColor }
ui.positioned { x = 0, y = c.h - c.borderBottom, child =
ui.rect { w = c.w, h = c.borderBottom, color = c.borderColor }
},
# left border
ui.rect { w = config.borderLeft, h = config.h, color = config.borderColor },
ui.rect { w = c.borderLeft, h = c.h, color = c.borderColor },
# right border
ui.positioned { x = config.w - config.borderRight, y = 0, child =
ui.rect { w = config.borderRight, h = config.h, color = config.borderColor }
ui.positioned { x = c.w - c.borderRight, y = 0, child =
ui.rect { w = c.borderRight, h = c.h, color = c.borderColor }
},
# content
ui.positioned { x = config.paddingLeft, y = config.paddingTop, child = config.child }
]};
ui.positioned { x = c.paddingLeft, y = c.paddingTop, child = c.child }
]
};
insertChar = text pos char \
before = slice text 0 pos;

@ -1,9 +1,43 @@
# TODO: Section labels - flat list with Section/Item types,
# skip sections in keyboard nav, when scrolling with keyboard
# itemHeight
# items = [
# Section { label = "Suggestions" },
# Item { name = "Calendar", type = "Application", icon = "..." },
# Item { name = "Weather", type = "Application", icon = "..." },
# Section { label = "Commands" },
# Item { name = "DynamoDB", subtitle = "Amazon AWS", type = "Command" },
# ...
# ];
#
# itemHeight = item \ item
# | Section _ \ 30
# | Item _ \ 44;
#
# itemYOffset = items index \
# sum (map itemHeight (take index items));
# Then render based on type:
# renderItem = item \ item
# | Section { label = l } \ ui.text { content = l, color = "#888" }
# | Item i \ paletteRow { ... };
#
# For keyboard nav, you'd skip sections when moving focus:
# nextSelectableIndex = items currentIndex direction \
# next = currentIndex + direction;
# (nth next items
# | Some (Section _) \ nextSelectableIndex items next direction
# | Some (Item _) \ next
# | None \ currentIndex);
palette = config \
focusedIndex = 0;
windowHeight = 400;
windowWidth = 600;
results = take 10 (config.search config.state.query);
results = take 8 (config.search config.state.query);
# results = config.search config.state.query; # once you get scrolling..
dialogPadding = 0;
@ -20,13 +54,12 @@ palette = config \
child = ui.stack {
children = [
ui.rect { w = config.w, h = config.h, color = color },
centerV config.h (
ui.positioned {
x = 10,
y = 10,
x = 6,
y = 12,
child = ui.text { content = config.child, color = "white" }
}
)
]
}
};
@ -54,22 +87,34 @@ palette = config \
onChange = text \ batch [config.state.query := text, config.state.focusedIndex := 0],
onKeyDown = key \ key
| "ArrowUp" \ config.state.focusedIndex := max 0 (config.state.focusedIndex - 1)
| "ArrowDown" \ config.state.focusedIndex := (config.state.focusedIndex + 1)
| "ArrowDown" \ config.state.focusedIndex := min (len results - 1) (config.state.focusedIndex + 1)
| "Enter" \ (\ config.onSelect (unwrapOr "" (nth config.state.focusedIndex results)))
| _ \ noOp
},
ui.clip {
w = contentWidth,
h = listHeight,
child = ui.column {
gap = 1,
children = mapWithIndex (t i \ paletteRow {
children = [
box {
w = contentWidth,
h = 30,
color = "transparent",
paddingLeft = 6,
paddingTop = 8,
child = ui.text { content = "Store values", color = "#bbb" },
},
...(mapWithIndex (t i \ paletteRow {
child = t,
w = contentWidth,
h = textInputHeight,
selected = (config.state.focusedIndex == i),
onClick = \ config.onSelect t
}) results
}) results)
]
}
}
]

@ -0,0 +1,44 @@
inspector = config \
windowHeight = 400;
windowWidth = 600;
source = getSource config.name;
sourceLines = split "\n" source;
_ = debug "source" source;
_ = debug "sourceLines" sourceLines;
dialogPadding = 0;
textInputHeight = 40;
contentWidth = windowWidth - (dialogPadding * 2);
contentHeight = windowHeight - (dialogPadding * 2);
ui.positioned {
x = (config.viewport.width - windowWidth) / 2,
y = (config.viewport.height - windowHeight) / 2,
child = ui.stack {
children = [
# background
ui.rect { w = windowWidth, h = windowHeight, color = "#063351", radius = 0, strokeWidth = 1, strokeColor = "#1A5F80" },
ui.column {
gap = 0,
children = mapWithIndex (line i \
textInput {
key = "palette-query" & (str i),
initialValue = line,
initialFocus = False,
color = "white",
backgroundColor = "rgba(0,0,0,0.0)",
w = contentWidth,
h = textInputHeight,
# onChange = text \ batch [config.state.query := text, config.state.focusedIndex := 0],
onChange = text \ batch [],
onKeyDown = key \ key
| _ \ noOp
}
) sourceLines
}
]
}
};

@ -4,8 +4,20 @@ osState = {
query = "",
focusedIndex = 0,
},
inspector = {
visible = False,
name = ""
}
};
inspect = item \
batch [
osState.palette.visible := False,
osState.palette.query := "",
osState.inspector.visible := True,
osState.inspector.name := item
];
init = {};
update = state event \ event
@ -16,14 +28,24 @@ view = state viewport \
ui.stack {
children = [
ui.rect { w = viewport.width, h = viewport.height, color = "#012" },
osState.inspector.visible
| True \ inspector {
name = osState.inspector.name,
viewport = viewport,
}
| False \ empty,
# keep palette at the end so it's on top
osState.palette.visible
| True \ palette {
state = osState.palette,
search = storeSearch,
onSelect = item \ (debug "selected" item),
onSelect = item \ inspect item,
viewport = viewport,
}
| False \ ui.text { content = "" }
| False \ empty,
]
};

@ -12,7 +12,7 @@ export function compile(ast: AST): string {
return JSON.stringify(ast.value.value);
if (ast.value.kind === 'int' || ast.value.kind === 'float')
return JSON.stringify(ast.value.value);
throw new Error(`Cannot compile literal of kind ${ast.value.kind}`);
throw new Error(`Cannot compile literal`); // of kind ${ast.value.kind}`);
case 'variable':
return sanitize(ast.name);
@ -35,9 +35,12 @@ export function compile(ast: AST): string {
return `${compile(ast.func)}(${args})`;
case 'record': {
const fields = Object.entries(ast.fields)
.map(([k, v]) => `${sanitizeName(k)}: ${compile(v)}`);
return `({${fields.join(', ')}})`;
const parts = ast.entries.map(entry =>
entry.kind === 'spread'
? `...${compile(entry.expr)}`
: `${sanitizeName(entry.key)}: ${compile(entry.value)}`
)
return `({${parts.join(', ')}})`;
}
case 'list': {
@ -98,7 +101,7 @@ function sanitize(name: string): string {
if (ops[name]) return ops[name];
const natives = ['measure', 'measureText', 'storeSearch', 'getSource', 'debug', 'len', 'slice', 'str', 'redefine', 'stateful', 'batch', 'noOp', 'rerender', 'focus', 'ui'];
const natives = ['measure', 'measureText', 'storeSearch', 'getSource', 'debug', 'nth', 'len', 'slice', 'join', 'chars', 'split', 'str', 'redefine', 'stateful', 'batch', 'noOp', 'rerender', 'focus', 'ui'];
if (natives.includes(name)) return `_rt.${name}`;
return sanitizeName(name);
@ -284,7 +287,11 @@ function freeVars(ast: AST, bound: Set<string> = new Set()): Set<string> {
}
case 'record': {
const allVars = Object.values(ast.fields).flatMap(v => [...freeVars(v, bound)]);
const allVars = ast.entries.flatMap(entry =>
entry.kind === 'spread'
? [...freeVars(entry.expr, bound)]
: [...freeVars(entry.value, bound)]
);
return new Set(allVars);
}

@ -475,7 +475,7 @@ export class Parser {
if (token.kind === 'open-brace') {
this.advance();
const fields: { [key: string]: AST } = {};
const entries: Array<{ kind: 'field', key: string, value: AST } | { kind: 'spread', expr: AST }> = [];
let first = true;
while (this.current().kind !== 'close-brace') {
@ -485,14 +485,20 @@ export class Parser {
}
first = false;
if (this.current().kind === 'dot-dot-dot') {
this.advance();
const expr = this.parseExpression();
entries.push({ kind: 'spread', expr });
} else {
const keyToken = this.expect('ident');
const key = (keyToken as { value: string }).value;
this.expect('equals');
fields[key] = this.parseExpression();
entries.push({ kind: 'field', key, value: this.parseExpression() });
}
}
this.expect('close-brace');
return { kind: 'record', fields, ...this.getPos(token) };
return { kind: 'record', entries, ...this.getPos(token) };
}
if (token.kind === 'int') {

@ -51,8 +51,14 @@ export const _rt = {
rerender: { _tag: 'Rerender' },
focus: (key: string) => ({ _tag: 'Focus', _0: key }),
len: (list: any[]) => list.length,
nth: (i: number) => (xs: any[] | string) => i >= 0 && i < xs.length
? { _tag: 'Some', _0: xs[i] }
: { _tag: 'None' },
len: (xs: any[] | string) => xs.length,
str: (x: any) => String(x),
chars: (s: string) => s.split(''),
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),
debug: (label: string) => (value: any) => { console.log(label, value); return value; },
fuzzyMatch: (query: string) => (target: string) => {
@ -70,7 +76,8 @@ export const _rt = {
getSource: (name: string) => {
const ast = definitions.get(name);
if (!ast) return "";
return prettyPrint(ast);
const printed = prettyPrint(ast);
return printed;
},
rebind: (name: string, pathOrValue: any, maybeValue?: any) => {
if (maybeValue === undefined) {

Loading…
Cancel
Save