Fixing pretty print ast. adding a few more builtins :( getting further with inspector
This commit is contained in:
parent
30234875fe
commit
8bc05efa1e
9 changed files with 272 additions and 82 deletions
80
src/ast.ts
80
src/ast.ts
|
|
@ -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
|
||||
|
|
@ -190,61 +194,77 @@ export type AST =
|
|||
| Rebind
|
||||
|
||||
export function prettyPrint(ast: AST, indent = 0): string {
|
||||
const i = ' '.repeat(indent);
|
||||
const i = ' '.repeat(indent);
|
||||
|
||||
switch (ast.kind) {
|
||||
case 'literal': {
|
||||
const val = ast.value;
|
||||
switch (val.kind) {
|
||||
case 'int':
|
||||
case 'float':
|
||||
return `${i}${val.value}`;
|
||||
case 'string':
|
||||
return `${i}"${val.value}"`;
|
||||
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(', ');
|
||||
return `${i}(${params}) => ${prettyPrint(ast.body)}`
|
||||
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}`
|
||||
|
||||
}
|
||||
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 {
|
||||
children = [
|
||||
# background
|
||||
ui.rect { w = config.w, h = config.h, color = config.color },
|
||||
# top border
|
||||
ui.rect { w = config.w, h = config.borderTop, color = config.borderColor },
|
||||
# bottom border
|
||||
ui.positioned { x = 0, y = config.h - config.borderBottom, child =
|
||||
ui.rect { w = config.w, h = config.borderBottom, color = config.borderColor }
|
||||
},
|
||||
# left border
|
||||
ui.rect { w = config.borderLeft, h = config.h, color = config.borderColor },
|
||||
# right border
|
||||
ui.positioned { x = config.w - config.borderRight, y = 0, child =
|
||||
ui.rect { w = config.borderRight, h = config.h, color = config.borderColor }
|
||||
},
|
||||
# content
|
||||
ui.positioned { x = config.paddingLeft, y = config.paddingTop, child = config.child }
|
||||
]};
|
||||
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 = c.w, h = c.h, color = c.color },
|
||||
# top border
|
||||
ui.rect { w = c.w, h = c.borderTop, color = c.borderColor },
|
||||
# bottom border
|
||||
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 = c.borderLeft, h = c.h, color = c.borderColor },
|
||||
# right border
|
||||
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 = 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,
|
||||
child = ui.text { content = config.child, color = "white" }
|
||||
}
|
||||
)
|
||||
|
||||
ui.positioned {
|
||||
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 {
|
||||
child = t,
|
||||
w = contentWidth,
|
||||
h = textInputHeight,
|
||||
selected = (config.state.focusedIndex == i),
|
||||
onClick = \ config.onSelect t
|
||||
}) results
|
||||
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)
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
44
src/cg/06-inspector.cg
Normal file
44
src/cg/06-inspector.cg
Normal file
|
|
@ -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;
|
||||
|
||||
const keyToken = this.expect('ident');
|
||||
const key = (keyToken as { value: string }).value;
|
||||
this.expect('equals');
|
||||
fields[key] = this.parseExpression();
|
||||
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');
|
||||
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…
Add table
Add a link
Reference in a new issue