From acf2a2b90300e65d7575f76b9dbe4504dd8de1ae Mon Sep 17 00:00:00 2001 From: chibash Date: Mon, 21 Oct 2024 17:36:58 +0900 Subject: [PATCH 01/11] fixes issue #15 --- .../code-generator/code-generator.ts | 11 ++++++++- server/src/transpiler/type-checker.ts | 5 ++-- .../code-generator/code-generator.test.ts | 2 +- .../code-generator/code-generator2.test.ts | 24 +++++++++++++++++-- .../code-generator/test-code-generator.ts | 9 +++++-- 5 files changed, 42 insertions(+), 9 deletions(-) diff --git a/server/src/transpiler/code-generator/code-generator.ts b/server/src/transpiler/code-generator/code-generator.ts index 7a03364..f3808ed 100644 --- a/server/src/transpiler/code-generator/code-generator.ts +++ b/server/src/transpiler/code-generator/code-generator.ts @@ -166,8 +166,17 @@ export class CodeGenerator extends visitor.NodeVisitor { const vname = info.transpile(node.name) this.result.write(this.makeFunctionObject(vname)) } - else + else { + if (info.isGlobal() && info.type instanceof InstanceType + && env.table.lookup(info.type.name()) === undefined) { + // force to declare the class name as an external type if it is not declared + // in this module. + throw this.errorLog.push(`fatal: unknown class name: ${info.type.name()}`, node) + } + this.result.write(info.transpile(node.name)) + } + return } diff --git a/server/src/transpiler/type-checker.ts b/server/src/transpiler/type-checker.ts index ea46342..7182239 100644 --- a/server/src/transpiler/type-checker.ts +++ b/server/src/transpiler/type-checker.ts @@ -172,9 +172,8 @@ export default class TypeChecker extends visitor.NodeVisi const nameInfo = names.lookup(node.name) if (nameInfo !== undefined) { - const info = nameInfo as NameInfo - if (this.assert(!info.isTypeName, `bad use of type name: ${node.name}`, node)) { - this.result = info.type + if (this.assert(!nameInfo.isTypeName, `bad use of type name: ${node.name}`, node)) { + this.result = nameInfo.type return } } diff --git a/server/tests/transpiler/code-generator/code-generator.test.ts b/server/tests/transpiler/code-generator/code-generator.test.ts index 5376590..5869096 100644 --- a/server/tests/transpiler/code-generator/code-generator.test.ts +++ b/server/tests/transpiler/code-generator/code-generator.test.ts @@ -1690,7 +1690,7 @@ test('class with a default super constructor', () => { print(obj) ` - expect(compileAndRun(src)).toBe('??\n') + expect(compileAndRun(src)).toBe('\n') }) diff --git a/server/tests/transpiler/code-generator/code-generator2.test.ts b/server/tests/transpiler/code-generator/code-generator2.test.ts index 479a80f..4b2abfb 100644 --- a/server/tests/transpiler/code-generator/code-generator2.test.ts +++ b/server/tests/transpiler/code-generator/code-generator2.test.ts @@ -2,7 +2,6 @@ import { execSync } from 'child_process' import { compileAndRun, multiCompileAndRun, importAndCompileAndRun, transpileAndWrite } from './test-code-generator' import { describe, expect, test, beforeAll } from '@jest/globals' import { GlobalVariableNameTable } from '../../../src/transpiler/code-generator/variables' -import exp from 'constants' beforeAll(() => { execSync('mkdir -p ./temp-files') @@ -336,4 +335,25 @@ test('% and %= operators', () => { ` expect(compileAndRun(src2)).toBe('5\n5\n5\n3\n3\nany\n5\n5\ninteger\n') -}) \ No newline at end of file +}) + +test('issue #15. Cannot access a property of class type when a class declaration or an object creation is not in the same file.', () => { + const src = ` + class Foo { + value: Foo + constructor(i: integer) { this.value = this } + } + let obj = new Foo(17) + ` + + const src2 = ` + print(obj.value) + ` + + const src3 = ` + print(obj.value instanceof Foo) + ` + + expect(multiCompileAndRun(src, src2)).toBe('\n') + // expect(multiCompileAndRun(src, src3)).toBe('1\n') +}) diff --git a/server/tests/transpiler/code-generator/test-code-generator.ts b/server/tests/transpiler/code-generator/test-code-generator.ts index 41836f1..3f7960c 100644 --- a/server/tests/transpiler/code-generator/test-code-generator.ts +++ b/server/tests/transpiler/code-generator/test-code-generator.ts @@ -27,8 +27,13 @@ static void fbody_print(value_t self, value_t m) { puts("undefined"); else if (gc_is_string_literal(m)) puts(gc_string_literal_cstr(m)); - else - puts("??"); + else { + class_object* cls = gc_get_class_of(m); + if (cls == NULL) + puts("??"); + else + printf("\\n", cls->name); + } } static void fbody_print_i32(value_t self, int32_t i) { From 3d5c05b8a4eb6939f6bcdc5770c98399de87e68e Mon Sep 17 00:00:00 2001 From: chibash Date: Tue, 22 Oct 2024 02:14:20 +0900 Subject: [PATCH 02/11] supports ** --- microcontroller/core/include/c-runtime.h | 2 ++ microcontroller/core/src/c-runtime.c | 30 +++++++++++++++++++ .../transpiler/code-generator/c-runtime.ts | 11 +++++++ .../code-generator/code-generator.ts | 16 +++++++--- server/src/transpiler/type-checker.ts | 14 +++++++-- .../code-generator/code-generator2.test.ts | 28 +++++++++++++++++ 6 files changed, 94 insertions(+), 7 deletions(-) diff --git a/microcontroller/core/include/c-runtime.h b/microcontroller/core/include/c-runtime.h index 285f0e7..ce0f1a5 100644 --- a/microcontroller/core/include/c-runtime.h +++ b/microcontroller/core/include/c-runtime.h @@ -132,6 +132,8 @@ extern value_t CR_SECTION any_subtract(value_t a, value_t b); extern value_t CR_SECTION any_multiply(value_t a, value_t b); extern value_t CR_SECTION any_divide(value_t a, value_t b); extern value_t CR_SECTION any_modulo(value_t a, value_t b); +extern value_t CR_SECTION any_power(value_t a, value_t b); +extern double CR_SECTION double_power(double a, double b); extern bool CR_SECTION any_less(value_t a, value_t b); extern bool CR_SECTION any_less_eq(value_t a, value_t b); diff --git a/microcontroller/core/src/c-runtime.c b/microcontroller/core/src/c-runtime.c index ed3d014..b9ee3a9 100644 --- a/microcontroller/core/src/c-runtime.c +++ b/microcontroller/core/src/c-runtime.c @@ -287,6 +287,36 @@ value_t any_modulo(value_t a, value_t b) { return runtime_type_error("bad operand for %%"); } +value_t any_power(value_t a, value_t b) { + double x, y; + int int_type = 1; + if (is_int_value(a)) + x = value_to_int(a); + else if (is_float_value(a)) { + x = value_to_float(a); + int_type = 0; + } + else + return runtime_type_error("bad operand for **"); + + if (is_int_value(b)) + y = value_to_int(b); + else if (is_float_value(b)) { + y = value_to_float(b); + int_type = 0; + } + else + return runtime_type_error("bad operand for **"); + + double z = pow(x, y); + if (int_type) + return int_to_value((int32_t)z); + else + return float_to_value((float)z); +} + +double double_power(double a, double b) { return pow(a, b); } + #define ANY_CMP_FUNC(name, op) \ bool any_##name(value_t a, value_t b) {\ if (is_int_value(a)) {\ diff --git a/server/src/transpiler/code-generator/c-runtime.ts b/server/src/transpiler/code-generator/c-runtime.ts index a793f80..93316a9 100644 --- a/server/src/transpiler/code-generator/c-runtime.ts +++ b/server/src/transpiler/code-generator/c-runtime.ts @@ -205,6 +205,8 @@ export function arithmeticOpForAny(op: string) { return 'any_divide' case '%': return 'any_modulo' + case '**': + return 'any_power' case '+=': return 'any_add_assign' case '-=': @@ -228,6 +230,15 @@ export function arithmeticOpForAny(op: string) { } } +export function power(type: StaticType | undefined) { + if (type === Float) + return '(float)double_power(' + else if (type === Integer) + return '(int32_t)double_power(' + else + throw new Error('bad operand types for **') +} + export function updateOpForAny(prefix: boolean, op: string) { if (prefix) if (op === '++') diff --git a/server/src/transpiler/code-generator/code-generator.ts b/server/src/transpiler/code-generator/code-generator.ts index f3808ed..4d3db62 100644 --- a/server/src/transpiler/code-generator/code-generator.ts +++ b/server/src/transpiler/code-generator/code-generator.ts @@ -871,8 +871,8 @@ export class CodeGenerator extends visitor.NodeVisitor { if (op === '==' || op === '!=' || op === '===' || op === '!==') this.equalityExpression(op, left, right, env) else if (op === '<' || op === '<=' || op === '>' || op === '>=' - || op === '+' || op === '-' || op === '*' || op === '/' || op === '%') - this.basicBinaryExpression(op, left, right, env) + || op === '+' || op === '-' || op === '*' || op === '/' || op === '%' || op === '**') + this.basicBinaryExpression(op, node, left, right, env) else if (op === '|' || op === '^' || op === '&' || op === '<<' || op === '>>') { // both left and right are integer or float. this.numericBinaryExprssion(op, left, right, env) @@ -913,7 +913,7 @@ export class CodeGenerator extends visitor.NodeVisitor { } // +, -, *, /, %, <, <=, ... for integer, float, or any-type values - private basicBinaryExpression(op: string, left: AST.Node, right: AST.Node, env: VariableEnv): void { + private basicBinaryExpression(op: string, node: AST.BinaryExpression, left: AST.Node, right: AST.Node, env: VariableEnv): void { const left_type = this.needsCoercion(left) const right_type = this.needsCoercion(right) if (left_type === Any || right_type === Any) { @@ -924,7 +924,15 @@ export class CodeGenerator extends visitor.NodeVisitor { this.result.write('))') } else - this.numericBinaryExprssion(op, left, right, env) + if (op === '**') { + this.result.write(cr.power(getStaticType(node))) + this.visit(left, env) + this.result.write(', ') + this.visit(right, env) + this.result.write(')') + } + else + this.numericBinaryExprssion(op, left, right, env) } // binary expression for numeric (integer or float) values diff --git a/server/src/transpiler/type-checker.ts b/server/src/transpiler/type-checker.ts index 7182239..a07fb96 100644 --- a/server/src/transpiler/type-checker.ts +++ b/server/src/transpiler/type-checker.ts @@ -661,7 +661,7 @@ export default class TypeChecker extends visitor.NodeVisi } this.result = BooleanT } - else if (op === '+' || op === '-' || op === '*' || op === '/') { + else if (op === '+' || op === '-' || op === '*' || op === '/' || op === '**') { this.assert((isNumeric(left_type) || left_type === Any) && (isNumeric(right_type) || right_type === Any), this.invalidOperandsMessage(op, left_type, right_type), node) if (left_type === Any || right_type === Any) { @@ -669,10 +669,18 @@ export default class TypeChecker extends visitor.NodeVisi this.addCoercion(node.right, right_type) this.result = Any } - else if (left_type === Float || right_type === Float) + else if (left_type === Float || right_type === Float) { + if (op === '**') + this.addStaticType(node, Float) + this.result = Float - else + } + else { + if (op === '**') + this.addStaticType(node, Integer) + this.result = Integer + } } else if (op === '%') { this.assert((left_type === Integer || left_type === Any) && (right_type === Integer || right_type === Any), diff --git a/server/tests/transpiler/code-generator/code-generator2.test.ts b/server/tests/transpiler/code-generator/code-generator2.test.ts index 4b2abfb..95b1c1e 100644 --- a/server/tests/transpiler/code-generator/code-generator2.test.ts +++ b/server/tests/transpiler/code-generator/code-generator2.test.ts @@ -337,6 +337,34 @@ test('% and %= operators', () => { expect(compileAndRun(src2)).toBe('5\n5\n5\n3\n3\nany\n5\n5\ninteger\n') }) +test('** operator', () => { + const src = ` + let a = 3 + let b = 2 + let c = 3.0 + let d = 2.0 + print(a ** b) + print(c ** d) + print(a ** d) + print(c ** b) +` + + expect(compileAndRun(src)).toBe('9\n9.000000\n9.000000\n9.000000\n') + + const src2 = ` + let a: any = 3 + let b: any = 2 + print(a ** b) + a = 3.0 + print(a ** b) + print(b ** a) + b = 2.0 + print(a ** b) +` + + expect(compileAndRun(src2)).toBe('9\n9.000000\n8.000000\n9.000000\n') +}) + test('issue #15. Cannot access a property of class type when a class declaration or an object creation is not in the same file.', () => { const src = ` class Foo { From 261a23dbfe84bc315e165b0759ff8277eee2e1ab Mon Sep 17 00:00:00 2001 From: chibash Date: Tue, 22 Oct 2024 03:08:08 +0900 Subject: [PATCH 03/11] makes test cases compilable --- .github/workflows/microcontroller_core.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/microcontroller_core.yml b/.github/workflows/microcontroller_core.yml index 10a1c43..b8ceb28 100644 --- a/.github/workflows/microcontroller_core.yml +++ b/.github/workflows/microcontroller_core.yml @@ -21,19 +21,19 @@ jobs: - uses: actions/checkout@v4 - name: Build c-runtime-test - run: gcc -DTEST64 c-runtime-test.c -o c-runtime-test + run: gcc -DTEST64 c-runtime-test.c -o c-runtime-test -lm - name: Run c-runtime-test run: ./c-runtime-test - name: Build c-runtime-test2 - run: gcc -DTEST64 c-runtime-test2.c -o c-runtime-test2 + run: gcc -DTEST64 c-runtime-test2.c -o c-runtime-test2 -lm - name: Run c-runtime-test2 run: ./c-runtime-test2 - name: Build float-test - run: gcc -DTEST64 float-test.c -o float-test + run: gcc -DTEST64 float-test.c -o float-test -lm - name: Run float-test run: ./float-test \ No newline at end of file From 803f63e2779bbcfc6452c05e8f98892d07555f9b Mon Sep 17 00:00:00 2001 From: chibash Date: Tue, 22 Oct 2024 04:30:11 +0900 Subject: [PATCH 04/11] update the git address in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 31f1004..54892ea 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ To start using BlueScript, you'll need the following: First, clone the BlueScript repository using Git: ```bash -git clone git@github.com:csg-tokyo/bluescript.git +git clone https://github.com/csg-tokyo/bluescript.git ``` ### Build and Start the BlueScript Server From c0e08c4d11b728fff580db590f42a2886cbf2372 Mon Sep 17 00:00:00 2001 From: chibash Date: Tue, 22 Oct 2024 07:54:35 +0900 Subject: [PATCH 05/11] makes shell runnable on Linux --- microcontroller/core/src/c-runtime.c | 2 +- microcontroller/core/test/c-runtime-test.c | 2 +- microcontroller/core/test/c-runtime-test2.c | 2 +- microcontroller/core/test/float-test.c | 2 +- server/src/transpiler/shell.ts | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/microcontroller/core/src/c-runtime.c b/microcontroller/core/src/c-runtime.c index b9ee3a9..d67ac99 100644 --- a/microcontroller/core/src/c-runtime.c +++ b/microcontroller/core/src/c-runtime.c @@ -4,7 +4,7 @@ To run on a 64bit machine (for testing/debugging purpose only), compile with -DTEST64. To include test code, compile with -DTEST. So, - cc -DTEST -DTEST64 gc.c + cc -DTEST -DTEST64 gc.c -lm will produce ./a.out that runs test code on a 64bit machine. Typical usecase: diff --git a/microcontroller/core/test/c-runtime-test.c b/microcontroller/core/test/c-runtime-test.c index bb46c05..415fea1 100644 --- a/microcontroller/core/test/c-runtime-test.c +++ b/microcontroller/core/test/c-runtime-test.c @@ -1,6 +1,6 @@ // Test code // To cmpile, -// cc -DTEST64 c-runtime-test.c +// cc -DTEST64 c-runtime-test.c -lm #include #include "../src/c-runtime.c" diff --git a/microcontroller/core/test/c-runtime-test2.c b/microcontroller/core/test/c-runtime-test2.c index 5c1056b..7146a4d 100644 --- a/microcontroller/core/test/c-runtime-test2.c +++ b/microcontroller/core/test/c-runtime-test2.c @@ -1,6 +1,6 @@ // Test code for c-runtime.c // To compile, -// cc -DTEST64 c-runtime-test2.c +// cc -DTEST64 c-runtime-test2.c -lm #include #include "../src/c-runtime.c" diff --git a/microcontroller/core/test/float-test.c b/microcontroller/core/test/float-test.c index fef7263..cb95e12 100644 --- a/microcontroller/core/test/float-test.c +++ b/microcontroller/core/test/float-test.c @@ -1,6 +1,6 @@ // Test code for value_to_float and float_to_value functions in c-runtime.c // To compile, -// cc -DTEST64 float-test.c +// cc -DTEST64 float-test.c -lm #include #include diff --git a/server/src/transpiler/shell.ts b/server/src/transpiler/shell.ts index 7c41fe2..f3305fc 100644 --- a/server/src/transpiler/shell.ts +++ b/server/src/transpiler/shell.ts @@ -22,7 +22,7 @@ export function buildShell() { const srcdir = 'src/transpiler' console.log(execSync("pwd").toString()) execSync(`cc -DTEST64 -O2 -shared -fPIC -o ${dir}/c-runtime.so ${cRuntimeC} ${srcdir}/shell-builtins.c`) - execSync(`cc -DTEST64 -O2 -o ${dir}/shell ${srcdir}/shell.c ${dir}/c-runtime.so -ldl`) + execSync(`cc -DTEST64 -O2 -o ${dir}/shell ${srcdir}/shell.c ${dir}/c-runtime.so -lm -ldl`) } function makeShell(closer: (code: number) => void) { @@ -113,7 +113,7 @@ class Transpiler { fs.writeFileSync(`${fileName}.c`, prologCcode + src) // throw an Error when compilation fails. - execSync(`cc -shared -DTEST64 -O2 -o ${fileName}.so ${fileName}.c ${this.libs}`) + execSync(`cc -DTEST64 -O2 -shared -fPIC -o ${fileName}.so ${fileName}.c ${this.libs}`) this.libs =`${this.libs} ${fileName}.so` this.sources = `${this.sources} ${fileName}.c` } From c8658d8b0058b791a121cdb94dc6b9566cbef4e6 Mon Sep 17 00:00:00 2001 From: chibash Date: Wed, 23 Oct 2024 17:22:00 +0900 Subject: [PATCH 06/11] supports instanceof --- microcontroller/core/include/c-runtime.h | 3 +- microcontroller/core/src/c-runtime.c | 20 ++++++- .../transpiler/code-generator/c-runtime.ts | 8 ++- .../code-generator/code-generator.ts | 20 ++++++- server/src/transpiler/type-checker.ts | 35 ++++++++++- .../code-generator/code-generator.test.ts | 2 +- .../code-generator/code-generator2.test.ts | 60 ++++++++++++++++++- .../code-generator/test-code-generator.ts | 2 +- 8 files changed, 136 insertions(+), 14 deletions(-) diff --git a/microcontroller/core/include/c-runtime.h b/microcontroller/core/include/c-runtime.h index ce0f1a5..14718bf 100644 --- a/microcontroller/core/include/c-runtime.h +++ b/microcontroller/core/include/c-runtime.h @@ -161,6 +161,7 @@ extern void CR_SECTION interrupt_handler_end(); extern void CR_SECTION gc_initialize(); extern class_object* CR_SECTION gc_get_class_of(value_t value); +extern bool gc_is_instance_of(const class_object* clazz, value_t obj); extern void* CR_SECTION method_lookup(value_t obj, uint32_t index); extern pointer_t CR_SECTION gc_allocate_object(const class_object* clazz); @@ -210,8 +211,8 @@ extern value_t CR_SECTION gc_new_int_box(int32_t value); extern value_t CR_SECTION gc_new_float_box(float value); extern value_t CR_SECTION gc_new_string(char* str); -extern bool CR_SECTION gc_is_string_literal(value_t obj); extern const char* CR_SECTION gc_string_literal_cstr(value_t obj); +extern bool CR_SECTION gc_is_string_object(value_t obj); extern value_t CR_SECTION safe_value_to_intarray(value_t v); extern value_t CR_SECTION gc_new_intarray(int32_t n, int32_t init_value); diff --git a/microcontroller/core/src/c-runtime.c b/microcontroller/core/src/c-runtime.c index d67ac99..28bccdf 100644 --- a/microcontroller/core/src/c-runtime.c +++ b/microcontroller/core/src/c-runtime.c @@ -232,7 +232,7 @@ value_t safe_value_to_func(const char* signature, value_t func) { } value_t safe_value_to_string(value_t v) { - if (!gc_is_string_literal(v)) + if (!gc_is_string_object(v)) runtime_type_error("value_to_string"); return v; @@ -240,7 +240,7 @@ value_t safe_value_to_string(value_t v) { value_t safe_value_to_object(value_t v) { // note: String is not a subtype of Object - if (!is_ptr_value(v) || gc_is_string_literal(v)) + if (!is_ptr_value(v) || gc_is_string_object(v)) runtime_type_error("value_to_object"); return v; @@ -462,6 +462,15 @@ class_object* gc_get_class_of(value_t value) { return NULL; } +bool gc_is_instance_of(const class_object* clazz, value_t obj) { + const class_object* obj_class = gc_get_class_of(obj); + do { + if (obj_class == clazz) + return true; + } while (obj_class != NULL && (obj_class = obj_class->superclass)); + return false; +} + void* method_lookup(value_t obj, uint32_t index) { return get_objects_class(value_to_ptr(obj))->vtbl[index]; } @@ -698,16 +707,21 @@ value_t gc_new_string(char* str) { } // true if this is a string literal object. -bool gc_is_string_literal(value_t obj) { +static bool gc_is_string_literal(value_t obj) { return gc_get_class_of(obj) == &string_literal.clazz; } // returns a pointer to a char array in the C language. +// this function is only used in test-code-generator.ts. const char* gc_string_literal_cstr(value_t obj) { pointer_t str = value_to_ptr(obj); return (const char*)raw_value_to_ptr(str->body[0]); } +bool gc_is_string_object(value_t obj) { + return gc_is_string_literal(obj); +} + // An int32_t array static CLASS_OBJECT(intarray_object, 1) = { diff --git a/server/src/transpiler/code-generator/c-runtime.ts b/server/src/transpiler/code-generator/c-runtime.ts index 93316a9..67a9ed8 100644 --- a/server/src/transpiler/code-generator/c-runtime.ts +++ b/server/src/transpiler/code-generator/c-runtime.ts @@ -391,7 +391,9 @@ export function getArrayLengthIndex(t: StaticType) { } export const runtimeTypeArray = 'array_object' -export const arrayMaker = 'gc_new_string' + +export const stringMaker = 'gc_new_string' +export const isStringType = 'gc_is_string_object(' export const functionMaker = 'gc_new_function' export const functionPtr = 'fptr' @@ -489,3 +491,7 @@ export function makeInstance(clazz: InstanceType) { export function methodLookup(method: [StaticType, number, InstanceType], func: string) { return `((${funcTypeToCType(method[0])})method_lookup(${func}, ${method[1]}))` } + +export function isInstanceOf(t: InstanceType) { + return `gc_is_instance_of(&${classObjectNameInC(t.name())}, ` +} diff --git a/server/src/transpiler/code-generator/code-generator.ts b/server/src/transpiler/code-generator/code-generator.ts index 4d3db62..3c6143b 100644 --- a/server/src/transpiler/code-generator/code-generator.ts +++ b/server/src/transpiler/code-generator/code-generator.ts @@ -3,7 +3,8 @@ import * as AST from '@babel/types' import { runBabelParser, ErrorLog, CodeWriter } from '../utils' import { Integer, BooleanT, Void, Any, ObjectType, FunctionType, - StaticType, isPrimitiveType, encodeType, sameType, typeToString, ArrayType, objectType } from '../types' + StaticType, isPrimitiveType, encodeType, sameType, typeToString, ArrayType, objectType, + StringT } from '../types' import * as visitor from '../visitor' import { getCoercionFlag, getStaticType } from '../names' import { typecheck } from '../type-checker' @@ -142,7 +143,7 @@ export class CodeGenerator extends visitor.NodeVisitor { } private makeStringLiteral(value: string) { - this.result.write(`${cr.arrayMaker}(${JSON.stringify(value)})`) + this.result.write(`${cr.stringMaker}(${JSON.stringify(value)})`) } booleanLiteral(node: AST.BooleanLiteral, env: VariableEnv): void { @@ -881,6 +882,8 @@ export class CodeGenerator extends visitor.NodeVisitor { // both left and right are integer or float. this.unsignedRightShift(left, right, env) } + else if (op === 'instanceof') + this.instanceOfExpression(left, right, env) else throw this.errorLog.push(`bad binary operator ${op}`, node) @@ -949,6 +952,19 @@ export class CodeGenerator extends visitor.NodeVisitor { this.visit(right, env) } + private instanceOfExpression(left: AST.Node, right: AST.Node, env: VariableEnv) { + const type = getStaticType(right) + if (type === StringT) + this.result.write(cr.isStringType) + else if (type instanceof InstanceType) + this.result.write(cr.isInstanceOf(type)) + else + throw this.errorLog.push('fatal: bad instanceof', right) + + this.visit(left, env) + this.result.write(')') + } + assignmentExpression(node: AST.AssignmentExpression, env: VariableEnv): void { if (node.extra?.parenthesized) this.result.write('(') diff --git a/server/src/transpiler/type-checker.ts b/server/src/transpiler/type-checker.ts index a07fb96..407f1a7 100644 --- a/server/src/transpiler/type-checker.ts +++ b/server/src/transpiler/type-checker.ts @@ -631,11 +631,16 @@ export default class TypeChecker extends visitor.NodeVisi } binaryExpression(node: AST.BinaryExpression, names: NameTable): void { + const op = node.operator + if (op === 'instanceof') { + this.instanceofExpression(node, names) + return + } + this.visit(node.left, names) const left_type = this.result this.visit(node.right, names) const right_type = this.result - const op = node.operator if (op === '==' || op === '!=' || op === '===' || op === '!==') { if (left_type === BooleanT || right_type === BooleanT) { this.assert(left_type === right_type, 'a boolean must be compared with a boolean', node) @@ -695,15 +700,39 @@ export default class TypeChecker extends visitor.NodeVisi } else if (op === '|' || op === '^' || op === '&' || op === '<<' || op === '>>' || op === '>>>') { this.assert(left_type === Integer && right_type === Integer, - this.invalidOperandsMessage(op, left_type, right_type), node) + this.invalidOperandsMessage(op, left_type, right_type), node) this.result = Integer } - else { // 'in', '**', 'instanceof', '|>' + else { // 'in', '|>' this.assert(false, `not supported operator '${op}'`, node) this.result = BooleanT } } + instanceofExpression(node: AST.BinaryExpression, names: NameTable): void { + this.visit(node.left, names) + const leftType = this.result + if (isPrimitiveType(leftType)) + this.assert(false, 'primitive types cannot be used in instanceof', node.left) + + if (AST.isIdentifier(node.right)) { + const typeName = node.right.name + let type: StaticType = Any + const info = names.lookup(typeName) + if (info?.isTypeName && info.type instanceof InstanceType) + type = info.type + else if (typeName === 'string' || typeName === 'String') + type = StringT + else + this.assert(false, `invalid type name: ${typeName}`, node.right) + + this.addStaticType(node.right, type) + this.result = BooleanT + } + else + this.assertSyntax(false, node.right) + } + invalidOperandsMessage(op: string, t1: StaticType, t2: StaticType) { const t1name = typeToString(t1) const t2name = typeToString(t2) diff --git a/server/tests/transpiler/code-generator/code-generator.test.ts b/server/tests/transpiler/code-generator/code-generator.test.ts index 5869096..a9c7ab6 100644 --- a/server/tests/transpiler/code-generator/code-generator.test.ts +++ b/server/tests/transpiler/code-generator/code-generator.test.ts @@ -583,7 +583,7 @@ print(foo(100)) expect(() => { multiCompileAndRun(src1, src2) }).toThrow(/assignment to constant variable.*line 1/) }) -test('calling a const function (it compares execution times. If it fails, try again.)', () => { +test.skip('calling a const function (it compares execution times. If it fails, try again.)', () => { const src = ` function fib(i: integer): integer { if (i < 2) diff --git a/server/tests/transpiler/code-generator/code-generator2.test.ts b/server/tests/transpiler/code-generator/code-generator2.test.ts index 95b1c1e..25a1ae0 100644 --- a/server/tests/transpiler/code-generator/code-generator2.test.ts +++ b/server/tests/transpiler/code-generator/code-generator2.test.ts @@ -1,6 +1,6 @@ import { execSync } from 'child_process' import { compileAndRun, multiCompileAndRun, importAndCompileAndRun, transpileAndWrite } from './test-code-generator' -import { describe, expect, test, beforeAll } from '@jest/globals' +import { expect, test, beforeAll } from '@jest/globals' import { GlobalVariableNameTable } from '../../../src/transpiler/code-generator/variables' beforeAll(() => { @@ -365,6 +365,62 @@ test('** operator', () => { expect(compileAndRun(src2)).toBe('9\n9.000000\n8.000000\n9.000000\n') }) +test('instanceof', () => { + const src = ` + class Foo { + value: integer + constructor(i: integer) { this.value = i } + } + class Bar extends Foo{ + value: string + constructor(s: string) { super(3); this.value = s } + } + + let obj = new Foo(17) + print(obj instanceof Foo) + print(!(obj instanceof Foo)) + + print('foo' instanceof string) + print(!('foo' instanceof String)) + + let obj2 = new Bar('foo') + print(obj2 instanceof Bar) + print(obj2 instanceof Foo) + print(null instanceof Foo) + print(undefined instanceof Foo) + ` + + expect(compileAndRun(src)).toBe('1\n0\n1\n0\n1\n1\n0\n0\n') + + const src2 = ` + class Foo { + value: integer + constructor(i: integer) { this.value = i } + } + + let obj3: any = 3 + print(obj3 instanceof Foo) + print(obj3 instanceof string) + obj3 = 'foo' + print(obj3 instanceof Foo) + print(obj3 instanceof string) + obj3 = null + print(obj3 instanceof Foo) + print(obj3 instanceof string) + obj3 = [1, 2, 3] + print(obj3 instanceof Foo) + print(obj3 instanceof string) + ` + + expect(compileAndRun(src2)).toBe('0\n0\n0\n1\n0\n0\n0\n0\n') + + const src3 = ` + print((1 + 2) instanceof string) + ` + + expect(() => compileAndRun(src3)).toThrow(/instanceof/) +}) + test('issue #15. Cannot access a property of class type when a class declaration or an object creation is not in the same file.', () => { const src = ` class Foo { @@ -383,5 +439,5 @@ test('issue #15. Cannot access a property of class type when a class declaratio ` expect(multiCompileAndRun(src, src2)).toBe('\n') - // expect(multiCompileAndRun(src, src3)).toBe('1\n') + expect(multiCompileAndRun(src, src3)).toBe('1\n') }) diff --git a/server/tests/transpiler/code-generator/test-code-generator.ts b/server/tests/transpiler/code-generator/test-code-generator.ts index 3f7960c..696c0ea 100644 --- a/server/tests/transpiler/code-generator/test-code-generator.ts +++ b/server/tests/transpiler/code-generator/test-code-generator.ts @@ -25,7 +25,7 @@ static void fbody_print(value_t self, value_t m) { printf("%f\\n", value_to_float(m)); else if (m == VALUE_NULL || m == VALUE_UNDEF) puts("undefined"); - else if (gc_is_string_literal(m)) + else if (gc_is_string_object(m)) puts(gc_string_literal_cstr(m)); else { class_object* cls = gc_get_class_of(m); From 8df0db5affe5158c4bee5fef697572a3c7253abb Mon Sep 17 00:00:00 2001 From: chibash Date: Wed, 23 Oct 2024 17:40:34 +0900 Subject: [PATCH 07/11] changes a called function name --- modules/esp32/c/utils.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/esp32/c/utils.c b/modules/esp32/c/utils.c index 22f6b39..c6762b1 100644 --- a/modules/esp32/c/utils.c +++ b/modules/esp32/c/utils.c @@ -12,7 +12,7 @@ void fbody_print(value_t self, value_t _value) { sprintf(message, "%f\n", value_to_float(_value)); else if (_value == VALUE_NULL) sprintf(message, "null\n"); - else if (gc_is_string_literal(_value)) + else if (gc_is_string_object(_value)) sprintf(message, "%s\n", gc_string_literal_cstr(_value)); else sprintf(message, "??\n"); From e7b5d3b0534eb517204ec9cd4f8b25f14cc4a430 Mon Sep 17 00:00:00 2001 From: chibash Date: Wed, 23 Oct 2024 17:44:40 +0900 Subject: [PATCH 08/11] fixes a warning message by the C compiler --- microcontroller/core/src/c-runtime.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microcontroller/core/src/c-runtime.c b/microcontroller/core/src/c-runtime.c index 28bccdf..28c5dd5 100644 --- a/microcontroller/core/src/c-runtime.c +++ b/microcontroller/core/src/c-runtime.c @@ -1345,7 +1345,7 @@ static void scan_and_mark_objects(uint32_t mark) { while (start < end) { pointer_t obj = (pointer_t)&heap_memory[start]; class_object* clazz = get_objects_class(obj); - int32_t j = class_has_pointers(clazz); + // int32_t j = class_has_pointers(clazz); uint32_t size = object_size(obj, clazz); if (IS_GRAY(obj)) { gc_stack[0] = obj; From a08070ce199bb35694d71bcdb270be3ba007b02a Mon Sep 17 00:00:00 2001 From: chibash Date: Thu, 24 Oct 2024 06:21:48 +0900 Subject: [PATCH 09/11] makes the shell runnable --- server/src/transpiler/shell-builtins.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/server/src/transpiler/shell-builtins.c b/server/src/transpiler/shell-builtins.c index 4ba982c..1b287f7 100644 --- a/server/src/transpiler/shell-builtins.c +++ b/server/src/transpiler/shell-builtins.c @@ -16,12 +16,17 @@ static void fbody_print(value_t self, value_t m) { printf("%d\n", value_to_int(m)); else if (is_float_value(m)) printf("%f\n", value_to_float(m)); - else if (m == VALUE_NULL) - puts("null"); - else if (gc_is_string_literal(m)) + else if (m == VALUE_NULL || m == VALUE_UNDEF) + puts("undefined"); + else if (gc_is_string_object(m)) puts(gc_string_literal_cstr(m)); - else - puts("??"); + else { + class_object* cls = gc_get_class_of(m); + if (cls == NULL) + puts("??"); + else + printf("\n", cls->name); + } } static void fbody_print_i32(value_t self, int32_t i) { From 4d7bced46481812c8313922df759bf0123be74ac Mon Sep 17 00:00:00 2001 From: chibash Date: Thu, 24 Oct 2024 12:55:19 +0900 Subject: [PATCH 10/11] updates code-generator2.test.ts so that it can run in parallel with code-generator.test.ts --- .../code-generator/code-generator2.test.ts | 42 ++++++++++++------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/server/tests/transpiler/code-generator/code-generator2.test.ts b/server/tests/transpiler/code-generator/code-generator2.test.ts index 25a1ae0..b63697e 100644 --- a/server/tests/transpiler/code-generator/code-generator2.test.ts +++ b/server/tests/transpiler/code-generator/code-generator2.test.ts @@ -7,6 +7,8 @@ beforeAll(() => { execSync('mkdir -p ./temp-files') }) +const destFile = './temp-files/bscript2_' + class Importer { moduleId: number modules: { name: string, source: string }[] @@ -18,7 +20,7 @@ class Importer { this.modules = mods this.moduleId = 0 this.fileNames = [] - this.path = './temp-files/bscript' + this.path = destFile } init() { @@ -246,7 +248,7 @@ test('/ and /= operators', () => { print(a) ` - expect(compileAndRun(src)).toBe('18\n59\n') + expect(compileAndRun(src, destFile)).toBe('18\n59\n') const src1 = ` let a = 239.3 @@ -256,7 +258,7 @@ test('/ and /= operators', () => { print(a) ` - expect(compileAndRun(src1)).toBe('18.128788\n55.651165\n') + expect(compileAndRun(src1, destFile)).toBe('18.128788\n55.651165\n') const src2 = ` let a: any = 239 @@ -278,7 +280,7 @@ test('/ and /= operators', () => { print(typeof j) ` - expect(compileAndRun(src2)).toBe('18\n18\n18\n59\n59\nany\n18\n18\ninteger\n') + expect(compileAndRun(src2, destFile)).toBe('18\n18\n18\n59\n59\nany\n18\n18\ninteger\n') const src3 = ` let a: any = 239.3 @@ -300,7 +302,7 @@ test('/ and /= operators', () => { print(typeof j) ` - expect(compileAndRun(src3)).toBe('18.128788\n18.128788\n18.128788\n50.914898\n50.914898\nany\n18.128788\n18.128788\nfloat\n') + expect(compileAndRun(src3, destFile)).toBe('18.128788\n18.128788\n18.128788\n50.914898\n50.914898\nany\n18.128788\n18.128788\nfloat\n') }) test('% and %= operators', () => { @@ -312,7 +314,7 @@ test('% and %= operators', () => { print(a) ` - expect(compileAndRun(src)).toBe('5\n3\n') + expect(compileAndRun(src, destFile)).toBe('5\n3\n') const src2 = ` let a: any = 239 @@ -334,7 +336,7 @@ test('% and %= operators', () => { print(typeof j) ` - expect(compileAndRun(src2)).toBe('5\n5\n5\n3\n3\nany\n5\n5\ninteger\n') + expect(compileAndRun(src2, destFile)).toBe('5\n5\n5\n3\n3\nany\n5\n5\ninteger\n') }) test('** operator', () => { @@ -349,7 +351,7 @@ test('** operator', () => { print(c ** b) ` - expect(compileAndRun(src)).toBe('9\n9.000000\n9.000000\n9.000000\n') + expect(compileAndRun(src, destFile)).toBe('9\n9.000000\n9.000000\n9.000000\n') const src2 = ` let a: any = 3 @@ -362,7 +364,7 @@ test('** operator', () => { print(a ** b) ` - expect(compileAndRun(src2)).toBe('9\n9.000000\n8.000000\n9.000000\n') + expect(compileAndRun(src2, destFile)).toBe('9\n9.000000\n8.000000\n9.000000\n') }) test('instanceof', () => { @@ -376,6 +378,11 @@ test('instanceof', () => { constructor(s: string) { super(3); this.value = s } } + class Baz extends Bar { + fvalue: float + constructor() { super('baz'); this.fvalue = 0.3 } + } + let obj = new Foo(17) print(obj instanceof Foo) print(!(obj instanceof Foo)) @@ -388,9 +395,16 @@ test('instanceof', () => { print(obj2 instanceof Foo) print(null instanceof Foo) print(undefined instanceof Foo) + + print(' ') + let obj3 = new Baz() + print(obj3 instanceof Foo) + print(obj3 instanceof Bar) + print(obj3 instanceof Baz) + print(obj2 instanceof Baz) ` - expect(compileAndRun(src)).toBe('1\n0\n1\n0\n1\n1\n0\n0\n') + expect(compileAndRun(src, destFile)).toBe('1\n0\n1\n0\n1\n1\n0\n0\n \n1\n1\n1\n0\n') const src2 = ` class Foo { @@ -412,13 +426,13 @@ test('instanceof', () => { print(obj3 instanceof string) ` - expect(compileAndRun(src2)).toBe('0\n0\n0\n1\n0\n0\n0\n0\n') + expect(compileAndRun(src2, destFile)).toBe('0\n0\n0\n1\n0\n0\n0\n0\n') const src3 = ` print((1 + 2) instanceof string) ` - expect(() => compileAndRun(src3)).toThrow(/instanceof/) + expect(() => compileAndRun(src3, destFile)).toThrow(/instanceof/) }) test('issue #15. Cannot access a property of class type when a class declaration or an object creation is not in the same file.', () => { @@ -438,6 +452,6 @@ test('issue #15. Cannot access a property of class type when a class declaratio print(obj.value instanceof Foo) ` - expect(multiCompileAndRun(src, src2)).toBe('\n') - expect(multiCompileAndRun(src, src3)).toBe('1\n') + expect(multiCompileAndRun(src, src2, destFile)).toBe('\n') + expect(multiCompileAndRun(src, src3, destFile)).toBe('1\n') }) From 092e86508be07b4a17bd2c33bf19f27c9c92bc6c Mon Sep 17 00:00:00 2001 From: chibash Date: Thu, 24 Oct 2024 12:59:23 +0900 Subject: [PATCH 11/11] adds InstanceType.subclasses() and ClassTable.roots() --- server/src/transpiler/classes.ts | 17 ++++++++++-- server/src/transpiler/type-checker.ts | 4 +-- server/tests/transpiler/type-checker.test.ts | 27 ++++++++++++++++++-- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/server/src/transpiler/classes.ts b/server/src/transpiler/classes.ts index 64c67a9..f28bf42 100644 --- a/server/src/transpiler/classes.ts +++ b/server/src/transpiler/classes.ts @@ -13,6 +13,7 @@ export class InstanceType extends ObjectType { private numOfUnboxed: number | undefined = undefined private numOfMethods: number private superClass: ObjectType + private subClasses: InstanceType[] = [] private className: string private blueScriptName: string @@ -28,15 +29,17 @@ export class InstanceType extends ObjectType { // returns a globally-unique class name. It may be different from the // original name given in the source code. - name() { return this.className } + override name() { return this.className } // the name given in the source code. override sourceName() { return this.blueScriptName } - superType(): ObjectType | null { return this.superClass } + override superType(): ObjectType | null { return this.superClass } superclass(): ObjectType { return this.superClass } + subclasses(): InstanceType[] { return this.subClasses } + // false if this class extends another class that is not Object class. extendsObject() { return !(this.superClass instanceof InstanceType) } @@ -210,14 +213,19 @@ export class InstanceType extends ObjectType { export class ClassTable { private names private numOfNames + private rootClasses: InstanceType[] constructor() { this.names = new Map() this.numOfNames = 0 + this.rootClasses = [] const builtinMethods: string[] = [ArrayType.lengthMethod] builtinMethods.forEach(name => { this.names.set(name, this.numOfNames++) }) } + // returns all the classes that do not inherit from another class. + roots() { return this.rootClasses } + // clazz must be added after all properties and methods are // added to it. addClass(name: string, clazz: InstanceType) { @@ -228,6 +236,11 @@ export class ClassTable { clazz.forEachName(f) clazz.forEachMethodName(f) + const sup = clazz.superclass() + if (sup instanceof InstanceType) + sup.subclasses().push(clazz) + else + this.rootClasses.push(clazz) } encodeName(name: string) { diff --git a/server/src/transpiler/type-checker.ts b/server/src/transpiler/type-checker.ts index 407f1a7..0134c86 100644 --- a/server/src/transpiler/type-checker.ts +++ b/server/src/transpiler/type-checker.ts @@ -295,10 +295,10 @@ export default class TypeChecker extends visitor.NodeVisi if (superClassName) if (AST.isIdentifier(superClassName)) { const info = names.lookup(superClassName.name) - if (info && info.isTypeName && info.type instanceof ObjectType) + if (info && info.isTypeName && info.type instanceof InstanceType) superClass = info.type else - this.assert(false, 'invalid super class', node) + this.assert(false, `invalid super class: ${superClassName.name}`, node) } else this.assertSyntax(false, superClassName) diff --git a/server/tests/transpiler/type-checker.test.ts b/server/tests/transpiler/type-checker.test.ts index 146862d..e66fe2a 100644 --- a/server/tests/transpiler/type-checker.test.ts +++ b/server/tests/transpiler/type-checker.test.ts @@ -3,6 +3,7 @@ import { expect, test } from '@jest/globals' import * as tested from './test-typechecker' import * as types from '../../src/transpiler/types' import * as names from '../../src/transpiler/names' +import * as clazz from '../../src/transpiler/classes' test('syntax error', () => { const src = `function foo(x: float) : number { @@ -189,8 +190,8 @@ test('function type', () => { test('import', () => { const src = ` - function foo(): integer { return 1 } - function bar(): integer { return 2 } + export function foo(): integer { return 1 } + export function bar(): integer { return 2 } ` const src2 = ` import type { integer, float } from 'bluescript.ts' @@ -228,3 +229,25 @@ test('import', () => { expect(() => tested.transpile(src3, 1, 'foo.ts', src)).toThrow(/cannot find/) expect(() => tested.transpile(src5, 1, 'foo.ts', src4)).toThrow(/line 2.*foo\.ts\n.*line 3.*foo\.ts\n.*line 5/) }) + +test('InstanceType.subclasses() and ClassTable.roots()', () => { + const src = ` + class Foo {} + class Bar extends Foo {} + class Baz extends Foo {} + class Foo2 {} +` + const ast = tested.transpile(src) + const table = names.getNameTable(ast.program) + const t = table?.lookup('Foo')?.type + + const subs = (t as clazz.InstanceType).subclasses() + expect(subs.length).toBe(2) + expect(subs[0].name()).toBe('Bar') + expect(subs[1].name()).toBe('Baz') + + const roots = table?.classTable()?.roots() + expect(roots !== undefined && roots.length).toBe(2) + expect(roots !== undefined && roots[0].name()).toBe('Foo') + expect(roots !== undefined && roots[1].name()).toBe('Foo2') +})