Skip to content

Commit

Permalink
LENGTH() function on sets, ANY sets, update lint config
Browse files Browse the repository at this point in the history
  • Loading branch information
BalaM314 committed Nov 4, 2024
1 parent c1a3080 commit f3ce929
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 21 deletions.
45 changes: 34 additions & 11 deletions core/src/runtime/builtin_functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ This file contains all builtin functions. Some defined in the insert, others wer
/* eslint-disable @typescript-eslint/no-unsafe-unary-minus */


import { ArrayVariableType, BuiltinFunctionData, PrimitiveVariableType, PrimitiveVariableTypeMapping, PrimitiveVariableTypeName, VariableTypeMapping, VariableValue } from "../runtime/runtime-types.js";
import { ArrayVariableType, BuiltinFunctionData, PrimitiveVariableType, PrimitiveVariableTypeMapping, PrimitiveVariableTypeName, SetVariableType, VariableTypeMapping, VariableValue } from "../runtime/runtime-types.js";
import type { Runtime } from "../runtime/runtime.js";
import { f, fail, tryRun } from "../utils/funcs.js";
import { f, fail, tryRun, unreachable } from "../utils/funcs.js";
import type { BoxPrimitive, RangeAttached } from "../utils/types.js";


//Warning: this file contains extremely sane code
//Function implementations have the parameter arg types determined by the following generics
//The functions sometimes need to fail() and set the range to the range of an argument
Expand All @@ -31,15 +32,19 @@ import type { BoxPrimitive, RangeAttached } from "../utils/types.js";
* "STRING" -> string
* ["STRING"] -> string
* ["STRING", "NUMBER"] -> string
* ["STRING, ["NUMBER"]] -> string | number[]
* ["STRING", ["NUMBER"]] -> string | number[]
* ["STRING", { set: "NUMBER" }] -> string | Set<number>
* [["ANY"]] -> unknown[]
**/
type BuiltinFunctionArgType = PrimitiveVariableTypeName | (PrimitiveVariableTypeName | [PrimitiveVariableTypeName | "ANY"])[];
**/
type BuiltinFunctionArgType = PrimitiveVariableTypeName | ComplexBuiltinFunctionArgType[];
type ComplexBuiltinFunctionArgType = PrimitiveVariableTypeName | [PrimitiveVariableTypeName | "ANY"] | {
set: PrimitiveVariableTypeName | "ANY";
};
type BuiltinFunctionArg = [name:string, type:BuiltinFunctionArgType];

/** Maps BuiltinFunctionArgTypes to the JS type */
type FunctionArgVariableTypeMapping<T extends BuiltinFunctionArgType> =
T extends Array<infer U extends PrimitiveVariableTypeName | [PrimitiveVariableTypeName | "ANY"]> ?
T extends Array<infer U extends ComplexBuiltinFunctionArgType> ?
U extends PrimitiveVariableTypeName ?
RangeAttached<PrimitiveVariableTypeMapping<U>> : //this is actually a RangeAttached<BoxPrimitive<...>> but that causes Typescript to complain
U extends [infer S] ?
Expand All @@ -49,7 +54,9 @@ type FunctionArgVariableTypeMapping<T extends BuiltinFunctionArgType> =
S extends "REAL" ? Float64Array :
never
)> :
RangeAttached<unknown[]>
RangeAttached<unknown[]> :
U extends { set: infer S extends PrimitiveVariableTypeName | "ANY" } ?
RangeAttached<VariableTypeMapping<SetVariableType> & Array<PrimitiveVariableTypeMapping<S extends "ANY" ? PrimitiveVariableTypeName : S>>>
: never :
T extends PrimitiveVariableTypeName
? RangeAttached<PrimitiveVariableTypeMapping<T>> //this is actually a RangeAttached<BoxPrimitive<...>> but that causes Typescript to complain
Expand Down Expand Up @@ -98,9 +105,14 @@ export const getBuiltinFunctions = ():Record<keyof typeof preprocessedBuiltinFun
args: new Map((data.args as BuiltinFunctionArg[]).map(([name, type]) => [name, {
passMode: "reference",
type: (Array.isArray(type) ? type : [type]).map(t =>
Array.isArray(t)
? new ArrayVariableType(null, null, t[0] == "ANY" ? null : PrimitiveVariableType.get(t[0]), [-1, -1])
: PrimitiveVariableType.get(t)
Array.isArray(t) ?
//TODO fix type error: this is ArrayVariableType<false>
new ArrayVariableType(null, null, t[0] == "ANY" ? null : PrimitiveVariableType.get(t[0]), [-1, -1])
: typeof t == "string" ?
PrimitiveVariableType.get(t)
: "set" in t ?
new SetVariableType(true, name, t.set == "ANY" ? null : PrimitiveVariableType.get(t.set))
: unreachable(t)
),
}] as const)),
aliases: data.aliases ?? [],
Expand Down Expand Up @@ -168,7 +180,7 @@ export const preprocessedBuiltinFunctions = ({
//source: spec 5.5
LENGTH: fn({
args: [
["ThisString", [["ANY"], "STRING"]],
["ThisString", [["ANY"], "STRING", { set: "ANY" }]],
],
returnType: "INTEGER",
impl(x){
Expand Down Expand Up @@ -400,6 +412,7 @@ export const preprocessedBuiltinFunctions = ({
return Date.now() + timezone * 36_000;
}
}),
//Extensions
DOWNLOADIMAGE: fn({
args: [
["Bytes", [["INTEGER"]]],
Expand Down Expand Up @@ -432,4 +445,14 @@ export const preprocessedBuiltinFunctions = ({
}
}
}),
//Included for compatibility with pseudocode.pro
SIZE: fn({
args: [
["set", [{set: "ANY"}]]
],
returnType: "INTEGER",
impl(set){
return set.length;
},
}),
}) satisfies Record<string, PreprocesssedBuiltinFunctionData<any, any>>;
15 changes: 9 additions & 6 deletions core/src/runtime/runtime-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -653,30 +653,33 @@ export class SetVariableType<Init extends boolean = true> extends BaseVariableTy
constructor(
public initialized: Init,
public name: string,
public baseType: (Init extends true ? never : UnresolvedVariableType) | VariableType,
//TODO rename this to elementType
public baseType: (Init extends true ? never : UnresolvedVariableType) | VariableType | null,
){super();}
init(runtime:Runtime){
if(Array.isArray(this.baseType)) this.baseType = runtime.resolveVariableType(this.baseType);
(this as SetVariableType<true>).initialized = true;
}
fmtText():string {
return f.text`${this.name} (user-defined set type containing "${this.baseType}")`;
return f.text`${this.name} (user-defined set type containing "${this.baseType ?? "ANY"}")`;
}
fmtShort():string {
return this.name;
}
toQuotedString():string {
return f.text`"${this.name}" (user-defined set type containing "${this.baseType}")`;
return f.text`"${this.name}" (user-defined set type containing "${this.baseType ?? "ANY"}")`;
}
fmtDebug():string {
return f.debug`SetVariableType [${this.name}] (contains: ${this.baseType})`;
return f.debug`SetVariableType [${this.name}] (contains: ${this.baseType ?? "ANY"})`;
}
getInitValue(runtime:Runtime):VariableValue | null {
crash(`Cannot initialize a variable of type SET`);
}
mapValues<T>(value:VariableTypeMapping<SetVariableType>, callback:(tval:TypedValue) => T):T[] {
const baseType = (this as SetVariableType<true>).baseType
?? crash(`Attempted to display a set with no element type`);
return value.map(v =>
callback(typedValue((this as SetVariableType<true>).baseType, v))
callback(typedValue(baseType, v))
);
}
asHTML(value:VariableTypeMapping<SetVariableType>):string {
Expand Down Expand Up @@ -909,7 +912,7 @@ export function typesAssignable(base:VariableType | UnresolvedVariableType, ext:
return typesEqual(base.target, ext.target) || [f.quote`Types ${base.target} and ${ext.target} are not equal`];
}
if(base instanceof SetVariableType && ext instanceof SetVariableType){
return typesEqual(base.baseType, ext.baseType) || [f.quote`Types ${base.baseType} and ${ext.baseType} are not equal`];
return base.baseType == null || (ext.baseType != null && typesEqual(base.baseType, ext.baseType) || [f.quote`Types ${base.baseType} and ${ext.baseType ?? "ANY"} are not equal`]);
}
if(base instanceof ClassVariableType && ext instanceof ClassVariableType){
return ext.inherits(base) || false;
Expand Down
3 changes: 2 additions & 1 deletion core/src/runtime/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,12 @@ function checkValueEquality<T extends VariableType>(type:T, a:VariableTypeMappin
if(type instanceof EnumeratedVariableType)
return a == b;
if(type instanceof SetVariableType){
const baseType = type.baseType ?? crash(`Unreachable: checking equality between sets, it should not be possible that both sets have element type "ANY"`);
forceType<VariableTypeMapping<SetVariableType>>(a);
forceType<VariableTypeMapping<SetVariableType>>(b);
return a.length == b.length &&
[...zip(a.values(), b.values())].every(
([aElement, bElement], i) => checkValueEquality(type.baseType, aElement, bElement, `${aPath}[${i}]`, `${bPath}[${i}]`, range)
([aElement, bElement], i) => checkValueEquality(baseType, aElement, bElement, `${aPath}[${i}]`, `${bPath}[${i}]`, range)
);
}
if(type instanceof ArrayVariableType){
Expand Down
2 changes: 1 addition & 1 deletion core/src/statements/statements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export class TypeSetStatement extends TypeStatement {
createType(runtime:Runtime){
return [this.name.text, new SetVariableType(
false, this.name.text, this.setType
)] as const; //TODO allow sets of UDTs
)] as const;
}
}
@statement("type", "TYPE StudentData", "block", "auto", "keyword.type", "name")
Expand Down
4 changes: 2 additions & 2 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ export default tseslint.config(
ignoredNodes: [
"* > TemplateLiteral",
"TemplateLiteral ~ *",
"SwitchCase"
"SwitchCase",
"ConditionalExpression",
],
flatTernaryExpressions: true
}
],
"linebreak-style": "off",
Expand Down

0 comments on commit f3ce929

Please sign in to comment.