Merge pull request #28 from ThakeeNathees/fn-name-resolve

Function name resolve implemented
This commit is contained in:
Thakee Nathees 2021-05-20 21:44:19 +05:30 committed by GitHub
commit e94603947f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 140 additions and 28 deletions

View File

@ -2,12 +2,12 @@
// To implement.
[ ] Resolve function name (called before defined).
[ ] Structs.
[ ] Implement math library.
[ ] Implement resolve path in cli.
[ ] Single header for embedding.
[ ] Resolve function name (called before defined).
[ ] Global variable names (also update dumpFunctionCode after).
[ ] Implement fiber from script body and vm run fibers (not scripts).
Then remove vm's root script.
[ ] C Integration api (including add core lib from host application).
@ -15,6 +15,8 @@
[ ] compile expression for a script.
[ ] Var handler implement.
[ ] Make it possible to override function names.
- To do so the functions and global variables should be in the same
buffer as the property of the script.
[ ] Hex, binary literals and floats like ".5".
[ ] Function docstring property.
[ ] Union tagging alter in var.

View File

@ -40,7 +40,7 @@ from '../baz.pk' import *
```
If the local scripts have defined a module name, they'll imported and binded with it's module name unless they've imported with an alias. If the local script don't have a module name and imported without an alias, every symbols (global variables, and functions) will be imported and that's similer to import all statement.
If the local scripts have defined a module name, they'll imported and binded with it's module name if not they've imported with an alias. If the local script don't have a module name and imported without an alias, every symbols (global variables, and functions) will be imported and that's similer to import all statement.
```ruby
# 'foo.pk' isn't defined a module name.

View File

@ -1,7 +1,7 @@
## PocketLang is a small, fast and friendly language for scripting and embedding.
An easy to use programming language with mixed syntax of ruby and python, that can be learned in less than an hour or in no time if you have python/ruby background.
With mixed syntax of ruby and python, that can be learned in less than an hour, and it's easy to embed into another application for scripting.
```ruby
# Python like import statement.
@ -19,4 +19,4 @@ for i in 0..5
end
```
The complete language (including the internals and runtime) is a standalone executable that can be easily copied to a flash drive. And the language itself can be compiled from source in seconds without any dependencies (of cause you need a C compiler and optionally a build system).
The complete language (including the internals and runtime) is a standalone executable with zero external dependency, that can be easily copied to a flash drive. And the language itself can be compiled from source in seconds without any dependencies (of cause you need a C compiler and **optionally** a build system).

View File

@ -22,6 +22,11 @@
// which is using a single byte value to identify the local.
#define MAX_VARIABLES 256
// The maximum number of names that were used before defined. Its just the size
// of the Forward buffer of the compiler. Feel free to increase it if it
// require more.
#define MAX_FORWARD_NAMES 256
// The maximum number of constant literal a script can contain. Also it's
// limited by it's opcode which is using a short value to identify.
#define MAX_CONSTANTS (1 << 16)
@ -268,6 +273,26 @@ typedef struct sLoop {
} Loop;
// To keep track of names used but not defined yet. This is only used for
// functions, because variables can't be accessed before it ever defined.
typedef struct sForwardName {
// Index of the short instruction that has the value of the name (in the
// names buffer of the script).
int instruction;
// The function where the name is used, and the instruction is belongs to.
Fn* func;
// The name string's pointer in the source.
const char* name;
int length;
// Line number of the name used (required for error message).
int line;
} ForwardName;
typedef struct sFunc {
// Scope of the function. -2 for script body, -1 for top level function and
@ -313,6 +338,11 @@ struct Compiler {
Loop* loop; //< Current loop.
Func* func; //< Current function.
// An array of implicitly forward declared names, which will be resolved once
// the script is completely compiled.
ForwardName forwards[MAX_FORWARD_NAMES];
int forwards_count;
// True if the last statement is a new local variable assignment. Because
// the assignment is different than reqular assignment and use this boolean
// to tell the compiler that dont pop it's assigned value because the value
@ -373,6 +403,17 @@ static void parseError(Compiler* compiler, const char* fmt, ...) {
va_end(args);
}
// Error caused when trying to resolve forward names (maybe more in the
// furure), Which will be called once after compiling the script and thus we
// need to pass the line number the error origined from.
static void resolveError(Compiler* compiler, int line, const char* fmt, ...) {
va_list args;
va_start(args, fmt);
const char* path = compiler->script->path->data;
reportError(compiler, path, line, fmt, args);
va_end(args);
}
/*****************************************************************************
* LEXING *
*****************************************************************************/
@ -854,18 +895,21 @@ static NameSearchResult compilerSearchName(Compiler* compiler,
static void emitOpcode(Compiler* compiler, Opcode opcode);
static int emitByte(Compiler* compiler, int byte);
static int emitShort(Compiler* compiler, int arg);
static void patchJump(Compiler* compiler, int addr_index);
static int compilerAddConstant(Compiler* compiler, Var value);
static int compilerGetVariable(Compiler* compiler, const char* name,
int length);
static int compilerAddVariable(Compiler* compiler, const char* name,
int length, int line);
static void patchJump(Compiler* compiler, int addr_index);
static void patchForward(Compiler* compiler, Fn* fn, int addr_index, int name);
static int compilerAddConstant(Compiler* compiler, Var value);
static int compileFunction(Compiler* compiler, FuncType fn_type);
static int compilerGetVariable(Compiler* compiler, const char* name,
int length);
static int compilerAddVariable(Compiler* compiler, const char* name,
int length, int line);
static void compilerAddForward(Compiler* compiler, int instruction, Fn* fn,
const char* name, int length, int line);
// Forward declaration of grammar functions.
static void parsePrecedence(Compiler* compiler, Precedence precedence);
static int compileFunction(Compiler* compiler, FuncType fn_type);
static void compileExpression(Compiler* compiler);
static void exprLiteral(Compiler* compiler, bool can_assign);
@ -1035,9 +1079,17 @@ static void exprName(Compiler* compiler, bool can_assign) {
emitStoreVariable(compiler, (index - compiler->global_count), false);
}
} else {
// TODO: The name could be a function which hasn't been defined at this point.
// Implement opcode to push a named variable.
parseError(compiler, "Name '%.*s' is not defined.", name_len, name_start);
// The name could be a function which hasn't been defined at this point.
if (peek(compiler) == TK_LPARAN) {
emitOpcode(compiler, OP_PUSH_FN);
int index = emitShort(compiler, result.index);
compilerAddForward(compiler, index, _FN, name_start, name_len,
name_line);
} else {
parseError(compiler, "Name '%.*s' is not defined.", name_len, name_start);
}
}
return;
}
@ -1156,7 +1208,7 @@ static void exprChainCall(Compiler* compiler, bool can_assign) {
argc++;
} while (match(compiler, TK_COMMA));
consume(compiler, TK_RBRACE, "Expected '}' after chain call"
"parameter list.");
"parameter list.");
}
}
@ -1406,6 +1458,8 @@ static void compilerInit(Compiler* compiler, PKVM* vm, const char* source,
compiler->loop = NULL;
compiler->func = NULL;
compiler->forwards_count = 0;
compiler->new_local = false;
}
@ -1430,6 +1484,12 @@ static int compilerAddVariable(Compiler* compiler, const char* name,
// TODO: should I validate the name for pre-defined, etc?
if (compiler->var_count == MAX_VARIABLES) {
parseError(compiler, "A script should contain at most %d variables.",
MAX_VARIABLES);
return -1;
}
Variable* variable = &compiler->variables[compiler->var_count];
variable->name = name;
variable->length = length;
@ -1445,6 +1505,22 @@ static int compilerAddVariable(Compiler* compiler, const char* name,
return compiler->var_count++;
}
static void compilerAddForward(Compiler* compiler, int instruction, Fn* fn,
const char* name, int length, int line) {
if (compiler->forwards_count == MAX_VARIABLES) {
parseError(compiler, "A script should contain at most %d implict forward "
"function declarations.", MAX_VARIABLES);
return;
}
ForwardName* forward = &compiler->forwards[compiler->forwards_count++];
forward->instruction = instruction;
forward->func = fn;
forward->name = name;
forward->length = length;
forward->line = line;
}
// Add a literal constant to scripts literals and return it's index.
static int compilerAddConstant(Compiler* compiler, Var value) {
VarBuffer* literals = &compiler->script->literals;
@ -1540,6 +1616,11 @@ static void patchJump(Compiler* compiler, int addr_index) {
_FN->opcodes.data[addr_index + 1] = offset & 0xff;
}
static void patchForward(Compiler* compiler, Fn* fn, int addr_index, int name) {
fn->opcodes.data[addr_index] = (name >> 8) & 0xff;
fn->opcodes.data[addr_index + 1] = name & 0xff;
}
// Jump back to the start of the loop.
static void emitLoopJump(Compiler* compiler) {
emitOpcode(compiler, OP_LOOP);
@ -1572,12 +1653,9 @@ static int compileFunction(Compiler* compiler, FuncType fn_type) {
consume(compiler, TK_NAME, "Expected a function name.");
name = compiler->previous.start;
name_length = compiler->previous.length;
NameSearchResult result = compilerSearchName(compiler, name,
name_length);
NameSearchResult result = compilerSearchName(compiler, name, name_length);
if (result.type != NAME_NOT_DEFINED) {
parseError(compiler, "Name '%.*s' already exists.", name_length,
name);
// Not returning here as to complete for skip cascaded errors.
parseError(compiler, "Name '%.*s' already exists.", name_length, name);
}
} else {
@ -2267,11 +2345,23 @@ bool compile(PKVM* vm, Script* script, const char* source) {
emitOpcode(compiler, OP_RETURN);
emitOpcode(compiler, OP_END);
// Resolve forward names (function names that are used before defined).
for (int i = 0; i < compiler->forwards_count; i++) {
ForwardName* forward = &compiler->forwards[i];
const char* name = forward->name;
int length = forward->length;
int index = scriptSearchFunc(script, name, (uint32_t)length);
if (index != -1) {
patchForward(compiler, forward->func, forward->instruction, index);
} else {
resolveError(compiler, forward->line, "Name '%.*s' is not defined.", length, name);
}
}
// Create script globals.
for (int i = 0; i < compiler->var_count; i++) {
ASSERT(compiler->variables[i].depth == (int)DEPTH_GLOBAL, OOPS);
varBufferWrite(&script->globals, vm, VAR_NULL);
// TODO: add the names to global_names table.
}
vm->compiler = compiler->next_compiler;

View File

@ -197,10 +197,9 @@ void dumpFunctionCode(PKVM* vm, Function* func) {
case OP_STORE_GLOBAL:
{
int index = READ_SHORT();
// TODO: name hasn't implemented yet.
//int name_index = func->owner->global_names.data[index];
//String* name = func->owner->names.data[name_index];
printf("%5d '%s'\n", index, "todo:name"/*name->data*/);
int name_index = func->owner->global_names.data[index];
String* name = func->owner->names.data[name_index];
printf("%5d '%s'\n", index, name->data);
break;
}

View File

@ -297,7 +297,7 @@ struct Function {
bool is_native; //< True if Native function.
union {
pkNativeFn native; //< Native function pointer.
Fn* fn; //< Script function pointer.
Fn* fn; //< Script function pointer.
};
};

21
test/lang/functions.pk Normal file
View File

@ -0,0 +1,21 @@
# Function Tests. (TODO: add more)
def f1 return 'f1' end
assert(f1() == 'f1')
def f2() return 'f2' end
assert(f2() == 'f2')
def f3(a, b, c, d)
return c
end
assert(f3('a', 'b', 'c', 'd') == 'c')
# forward call.
val = before_defn()
def before_defn()
return 'defined after the call'
end
assert(val == 'defined after the call')