Skip to content

Commit

Permalink
Compile: Support calling functions by value, iterating and accessing …
Browse files Browse the repository at this point in the history
…and slicing strings

- Fix vars declared in some loop macros being considered invalid parameters because they occur after a #raw
  • Loading branch information
ChiriVulpes committed Dec 12, 2024
1 parent 02642d4 commit f95ae8e
Show file tree
Hide file tree
Showing 6 changed files with 35 additions and 24 deletions.
8 changes: 4 additions & 4 deletions src/chc/read/consume/consumeCompilerVariableOptional.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export interface ChiriCompilerVariable {
assignment?: "=" | "??="
}

export default async (reader: ChiriReader, prefix = true): Promise<ChiriCompilerVariable | undefined> => {
export default async (reader: ChiriReader, prefix = true, skipInvalidParamCheck?: true): Promise<ChiriCompilerVariable | undefined> => {
const save = reader.savePosition()
const position = reader.getPosition()
if (prefix)
Expand Down Expand Up @@ -55,7 +55,7 @@ export default async (reader: ChiriReader, prefix = true): Promise<ChiriCompiler
consumeWhiteSpaceOptional(reader)

let assignment = reader.consumeOptional("??=", "=") as "??=" | "=" | undefined
if (assignment === "??=" && reader.context.type === "mixin")
if (!skipInvalidParamCheck && assignment === "??=" && reader.context.type === "mixin")
throw reader.error(save.i, "Mixins cannot accept parameters")

let expression: ChiriExpressionResult | undefined
Expand All @@ -71,11 +71,11 @@ export default async (reader: ChiriReader, prefix = true): Promise<ChiriCompiler
if (!assignment && reader.consumeOptional("?"))
assignment = "??="

else if (reader.context.type === "mixin")
else if (!skipInvalidParamCheck && reader.context.type === "mixin")
throw reader.error(save.i, "Mixins cannot accept parameters")
}

if (assignment !== "=" && reader.getStatements(true).some(statement => statement.type === "variable" && statement.valueType.name.value === "raw"))
if (!skipInvalidParamCheck && assignment !== "=" && reader.getStatements(true).some(statement => statement.type === "variable" && statement.valueType.name.value === "raw"))
throw reader.error(save.i, "No further parameters can appear after a parameter of type \"raw\"")

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import consumeExpression from "./consumeExpression"
export interface ChiriFunctionCall {
type: "function-call"
name: ChiriWord
indexedAssignments: boolean
assignments: Record<string, ChiriExpressionResult>
valueType: ChiriType
position: ChiriPosition
Expand Down Expand Up @@ -99,6 +100,7 @@ export function consumePartialFuntionCall (reader: ChiriReader, position: ChiriP
return {
type: "function-call",
name,
indexedAssignments: fn.type !== "function",
assignments,
valueType: returnType,
position,
Expand Down
4 changes: 2 additions & 2 deletions src/chc/read/consume/macro/macroEach.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,15 @@ export default MacroConstruct("each")
reader.consume("as")
consumeWhiteSpace(reader)

const variable1 = await consumeCompilerVariableOptional(reader, false)
const variable1 = await consumeCompilerVariableOptional(reader, false, true)
if (!variable1)
throw reader.error("Expected variable declaration")

let variable2: ChiriCompilerVariable | undefined
if (reader.consumeOptional(",")) {
consumeWhiteSpaceOptional(reader)

variable2 = await consumeCompilerVariableOptional(reader, false)
variable2 = await consumeCompilerVariableOptional(reader, false, true)
if (!variable2)
throw reader.error("Expected variable declaration")
}
Expand Down
18 changes: 12 additions & 6 deletions src/chc/util/resolveExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ function resolveExpression (compiler: ChiriCompiler, expression?: ChiriExpressio
case "get-by-key": {
const obj = resolveExpression(compiler, expression.value)
let key = resolveExpression.stringifyExpression(compiler, expression.key)
if (!Record.is(obj) && !Array.isArray(obj))
if (typeof obj !== "string" && !Record.is(obj) && !Array.isArray(obj))
throw compiler.error(`Cannot access value in "${key}" of "${resolveExpression.stringifyExpression(compiler, expression.value)}"`)

if (Array.isArray(obj)) {
if (typeof obj === "string" || Array.isArray(obj)) {
let index = +key
index = index < 0 ? obj.length + index : index
key = `${index}`
Expand All @@ -53,7 +53,7 @@ function resolveExpression (compiler: ChiriCompiler, expression?: ChiriExpressio

case "list-slice": {
const list = resolveExpression(compiler, expression.list)
if (!Array.isArray(list))
if (typeof list !== "string" && !Array.isArray(list))
throw compiler.error("Cannot create list slice, invalid list")

expression.range.end ??= makeLiteralInt(list.length)
Expand All @@ -62,16 +62,22 @@ function resolveExpression (compiler: ChiriCompiler, expression?: ChiriExpressio
if (!Array.isArray(range))
throw compiler.error("Cannot create list slice, invalid range")

const result: Value[] = []
let result: Value[] | string = typeof list === "string" ? "" : []
for (let index of range) {
if (typeof index !== "number")
throw compiler.error("Cannot create list slice, provided index is not an integer")

index = index < 0 ? range.length + index : index
result.push(list[Math.max(0, Math.min(index, list.length - 1))])
index = Math.max(0, Math.min(index, list.length - 1))
const value = list[index]
if (typeof result === "string")
result += value as string
else
result.push(value)
}

return result

}

case "match": {
Expand Down Expand Up @@ -175,7 +181,7 @@ function resolveExpression (compiler: ChiriCompiler, expression?: ChiriExpressio
case ">>>":
return operandA >>> operandB
case "is":
return compiler.types.types[operandB as string].is?.(operandA as Value) ?? false
return compiler.types.types[operandB as string]?.is?.(operandA as Value) ?? false
default:
throw compiler.error(undefined, `Unable to resolve binary operator "${expression.operator}"`)
}
Expand Down
8 changes: 2 additions & 6 deletions src/chc/util/resolveLiteralValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ function resolveLiteralValue (compiler: ChiriCompiler, expression: ChiriLiteralV
}
}

export function resolveLiteralRange (compiler: ChiriCompiler, range: ChiriLiteralRange, list?: any[]) {
export function resolveLiteralRange (compiler: ChiriCompiler, range: ChiriLiteralRange, list?: string | any[]) {
const startRaw = resolveLiteralValue.resolveExpression(compiler, range.start) ?? 0
const endRaw = resolveLiteralValue.resolveExpression(compiler, range.end) ?? list?.length
if (!Number.isInteger(startRaw))
Expand All @@ -79,9 +79,6 @@ export function resolveLiteralRange (compiler: ChiriCompiler, range: ChiriLitera
end = !list ? end : Math.max(0, Math.min(end, listLength - 1))

const result: number[] = []
if (Math.abs(start - end) <= 1)
return result

if (range.inclusive)
if (start < end)
for (let i = start; i <= end; i++)
Expand All @@ -90,14 +87,13 @@ export function resolveLiteralRange (compiler: ChiriCompiler, range: ChiriLitera
for (let i = start; i >= end; i--)
result.push(i)

else {
else
if (start < end)
for (let i = start; i < end; i++)
result.push(i)
else
for (let i = start; i > end; i--)
result.push(i)
}

return result
}
Expand Down
19 changes: 13 additions & 6 deletions src/chc/write/ChiriCompiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import ChiriTypeManager from "../type/ChiriTypeManager"
import type { BodyVariableContext, BodyVariableContexts } from "../type/typeBody"
import typeString from "../type/typeString"
import type { ComponentStateSpecial } from "../util/componentStates"
import getFunctionParameters from "../util/getFunctionParameters"
import relToCwd from "../util/relToCwd"
import type { Value } from "../util/resolveExpression"
import resolveExpression, { Record as ChiriRecord } from "../util/resolveExpression"
Expand Down Expand Up @@ -305,6 +306,10 @@ function ChiriCompiler (ast: ChiriAST, dest: string): ChiriCompiler {
throw error(position, `Function ${name} is not defined`)
}

function isFunction (fn: unknown): fn is ChiriFunction {
return (fn as ChiriFunction)?.type === "function"
}

function setFunction (fn: ChiriFunction) {
scope().functions ??= {}
if (scope().functions![fn.name.value])
Expand Down Expand Up @@ -1226,11 +1231,11 @@ function ChiriCompiler (ast: ChiriAST, dest: string): ChiriCompiler {
case "each": {
let list = resolveExpression(compiler, statement.iterable)

if (!Array.isArray(list) && (!ChiriRecord.is(list) || !statement.keyVariable))
if (typeof list !== "string" && !Array.isArray(list) && (!ChiriRecord.is(list) || !statement.keyVariable))
throw error(statement.iterable.position, "Variable is not iterable")

list = !statement.keyVariable ? list as Value[]
: !Array.isArray(list) ? Object.entries(list)
: typeof list !== "string" && !Array.isArray(list) ? Object.entries(list)
: Object.values(list).map((v, i) => [i, v] as const)

const result: T[] = []
Expand Down Expand Up @@ -1488,8 +1493,10 @@ function ChiriCompiler (ast: ChiriAST, dest: string): ChiriCompiler {
}

function callFunction (call: ChiriFunctionCall) {
const fn = getFunction(call.name.value, call.position)
const result = compileStatements(fn.content, resolveAssignments(call.assignments), compileFunction)
const fnVar = getVariable(call.name.value, call.position, true)
const fn = isFunction(fnVar) ? fnVar : getFunction(call.name.value, call.position)
const assignments = resolveAssignments(call.assignments, call.indexedAssignments ? getFunctionParameters(fn).map(p => p.name.value) : undefined)
const result = compileStatements(fn.content, assignments, compileFunction)
if (result.length > 1)
throw internalError(call.position, "Function call returned multiple values")

Expand All @@ -1508,10 +1515,10 @@ function ChiriCompiler (ast: ChiriAST, dest: string): ChiriCompiler {
return statement.type + name
}

function resolveAssignments (assignments: Record<string, ChiriExpressionResult>): Partial<Scope> {
function resolveAssignments (assignments: Record<string, ChiriExpressionResult>, indicesIntoParams?: string[]): Partial<Scope> {
return Scope.variables(Object.fromEntries(Object.entries(assignments)
.map(([name, expr]) =>
[name, { type: expr.valueType, value: resolveExpression(compiler, expr) }])))
[indicesIntoParams?.[+name] ?? name, { type: expr.valueType, value: resolveExpression(compiler, expr) }])))
}

function resolveWordLowercase (word: ChiriWordInterpolated | ChiriWord | string): ChiriWord {
Expand Down

0 comments on commit f95ae8e

Please sign in to comment.