Add default parameter values, fix relative imports, and add WaitQueue deque ops
Parser: - Fix parseGenericConstraint to recognize '=' as a stop token, enabling fn foo(x: int64 = 0) syntax - Fix parseModulePath and parseImportPath to handle '../' as a single token (the lexer greedily merges '..' and '/') Typechecker: - Relax isCapable to accept fewer arguments when trailing parameters have defaults - Fill in default values for missing arguments in lowerResolvedCall C runtime: - Add peon_wait_queue_pop_back and peon_wait_queue_wake_last for O(1) tail removal, making WaitQueue a proper deque - Rename wake_one -> wake_first for symmetry with wake_last Stdlib: - Move sync.pn into sync/ directory (events.pn, queues.pn) - Fix Event.set() missing self.signaled = true assignment (deadlock bug) - Add wakeLast and isEmpty bindings for WaitQueue - Fix WaitQueue clone to use UFCS init() style Syntax highlighting: - Fix import path regex to support multiple ../ prefixes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -222,7 +222,7 @@
|
||||
"imports": {
|
||||
"patterns": [
|
||||
{
|
||||
"match": "\\b(from)\\b\\s+((?:\\.\\./|[A-Za-z_][A-Za-z0-9_]*)(?:/[A-Za-z_][A-Za-z0-9_]*)*)\\s+(import)\\b",
|
||||
"match": "\\b(from)\\b\\s+((?:\\.\\./)*[A-Za-z_][A-Za-z0-9_]*(?:/[A-Za-z_][A-Za-z0-9_]*)*)\\s+(import)\\b",
|
||||
"captures": {
|
||||
"1": {
|
||||
"name": "keyword.control.import.peon"
|
||||
@@ -236,7 +236,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"match": "\\b(import)\\b\\s+((?:\\.\\./|[A-Za-z_][A-Za-z0-9_]*)(?:/[A-Za-z_][A-Za-z0-9_]*)*)",
|
||||
"match": "\\b(import)\\b\\s+((?:\\.\\./)*[A-Za-z_][A-Za-z0-9_]*(?:/[A-Za-z_][A-Za-z0-9_]*)*)",
|
||||
"captures": {
|
||||
"1": {
|
||||
"name": "keyword.control.import.peon"
|
||||
|
||||
@@ -356,8 +356,10 @@ PeonSpawnHandle peon_spawn(PeonNursery *nursery,
|
||||
bool peon_spawn_handle_is_complete(PeonSpawnHandle handle);
|
||||
void peon_spawn_handle_take_result(PeonSpawnHandle handle, void *out_value);
|
||||
void peon_wait_queue_init(PeonWaitQueue *queue);
|
||||
bool peon_wait_queue_wake_one(PeonWaitQueue *queue);
|
||||
bool peon_wait_queue_wake_first(PeonWaitQueue *queue);
|
||||
bool peon_wait_queue_wake_last(PeonWaitQueue *queue);
|
||||
int64_t peon_wait_queue_wake_all(PeonWaitQueue *queue);
|
||||
bool peon_wait_queue_is_empty(PeonWaitQueue *queue);
|
||||
PeonAsyncOp peon_async_checkpoint(void);
|
||||
PeonAsyncOp peon_async_await_task(PeonSpawnHandle handle);
|
||||
PeonAsyncOp peon_async_drain_nursery(PeonNursery *nursery);
|
||||
|
||||
@@ -971,6 +971,23 @@ static PeonTask *peon_wait_queue_pop(PeonWaitQueue *queue) {
|
||||
return task;
|
||||
}
|
||||
|
||||
static PeonTask *peon_wait_queue_pop_back(PeonWaitQueue *queue) {
|
||||
if (queue == NULL || queue->tail == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
PeonTask *task = (PeonTask *)queue->tail;
|
||||
queue->tail = task->wait_prev;
|
||||
if (queue->tail == NULL) {
|
||||
queue->head = NULL;
|
||||
} else {
|
||||
((PeonTask *)queue->tail)->wait_next = NULL;
|
||||
}
|
||||
task->wait_prev = NULL;
|
||||
task->wait_next = NULL;
|
||||
task->wait_queue = NULL;
|
||||
return task;
|
||||
}
|
||||
|
||||
static bool peon_task_waiter_list_remove(PeonTask **head, PeonTask *task) {
|
||||
if (head == NULL || task == NULL) {
|
||||
return false;
|
||||
@@ -1924,7 +1941,7 @@ void peon_wait_queue_init(PeonWaitQueue *queue) {
|
||||
queue->tail = NULL;
|
||||
}
|
||||
|
||||
bool peon_wait_queue_wake_one(PeonWaitQueue *queue) {
|
||||
bool peon_wait_queue_wake_first(PeonWaitQueue *queue) {
|
||||
if (queue == NULL) {
|
||||
peon_panic("attempted to wake an invalid wait queue");
|
||||
}
|
||||
@@ -1943,17 +1960,43 @@ bool peon_wait_queue_wake_one(PeonWaitQueue *queue) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool peon_wait_queue_wake_last(PeonWaitQueue *queue) {
|
||||
if (queue == NULL) {
|
||||
peon_panic("attempted to wake an invalid wait queue");
|
||||
}
|
||||
PeonTask *task = peon_wait_queue_pop_back(queue);
|
||||
if (task == NULL) {
|
||||
return false;
|
||||
}
|
||||
if (task->scheduler == NULL) {
|
||||
peon_panic("attempted to wake a wait-queue task without a scheduler");
|
||||
}
|
||||
if (task->scheduler->parked_wait_queue_tasks == 0u) {
|
||||
peon_panic("async wait-queue waiter count underflow");
|
||||
}
|
||||
task->scheduler->parked_wait_queue_tasks -= 1u;
|
||||
peon_scheduler_push_ready(task->scheduler, task);
|
||||
return true;
|
||||
}
|
||||
|
||||
int64_t peon_wait_queue_wake_all(PeonWaitQueue *queue) {
|
||||
if (queue == NULL) {
|
||||
peon_panic("attempted to wake an invalid wait queue");
|
||||
}
|
||||
int64_t woke = 0;
|
||||
while (peon_wait_queue_wake_one(queue)) {
|
||||
while (peon_wait_queue_wake_first(queue)) {
|
||||
woke += 1;
|
||||
}
|
||||
return woke;
|
||||
}
|
||||
|
||||
bool peon_wait_queue_is_empty(PeonWaitQueue *queue) {
|
||||
if (queue == NULL) {
|
||||
peon_panic("attempted to check size of an invalid wait queue");
|
||||
}
|
||||
return queue->head == queue->tail && queue->head == NULL;
|
||||
}
|
||||
|
||||
PeonAsyncOp peon_async_checkpoint(void) {
|
||||
return (PeonAsyncOp){
|
||||
.kind = PEON_ASYNC_OP_CHECKPOINT,
|
||||
|
||||
@@ -179,7 +179,11 @@ proc scanOperators(tokens: seq[Token]): OperatorScanResult =
|
||||
|
||||
proc parseImportPath(tokens: seq[Token], idx: var int): string =
|
||||
while idx < tokens.len and tokens[idx].kind != Semicolon:
|
||||
if tokens[idx].lexeme == "..":
|
||||
if tokens[idx].lexeme == "../":
|
||||
# The lexer greedily merges ".." and "/" into a single "../" token
|
||||
result &= "../"
|
||||
inc(idx)
|
||||
elif tokens[idx].lexeme == "..":
|
||||
if idx + 1 >= tokens.len or tokens[idx + 1].lexeme != "/":
|
||||
break
|
||||
result &= "../"
|
||||
|
||||
@@ -1027,8 +1027,12 @@ proc isCapable*(self: TypeChecker, name: Name, sig: TypeSignature, args: seq[Typ
|
||||
allowConverters = true): int =
|
||||
if name.isNil() or name.valueType.isNil() or name.valueType.kind != TypeKind.Function:
|
||||
return -1
|
||||
if sig.len() != name.valueType.signature.len() or args.len() != sig.len():
|
||||
if sig.len() > name.valueType.signature.len():
|
||||
return -1
|
||||
if sig.len() < name.valueType.signature.len():
|
||||
for i in sig.len() ..< name.valueType.signature.len():
|
||||
if name.valueType.signature[i].default.isNil():
|
||||
return -1
|
||||
var score = 0
|
||||
if name.valueType.isBuiltin and args.len() == 1:
|
||||
let builtin = self.builtinMagic(name)
|
||||
@@ -1194,8 +1198,18 @@ proc matchCandidatesResolved*(self: TypeChecker, label: string, pool: seq[Name],
|
||||
msg &= &"\n - in {relativePath(name.file, getCurrentDir())}:{name.ident.token.line}:{name.ident.token.relPos.start} -> {self.stringify(name.valueType)}"
|
||||
if name.valueType.kind notin [Function, Structure]:
|
||||
msg &= ": not callable"
|
||||
elif sig.len() != name.valueType.signature.len():
|
||||
elif sig.len() > name.valueType.signature.len():
|
||||
msg &= &": wrong number of arguments (expected {name.valueType.signature.len()}, got {sig.len()} instead)"
|
||||
elif sig.len() < name.valueType.signature.len():
|
||||
var hasMissingRequired = false
|
||||
var requiredCount = 0
|
||||
for j in 0 ..< name.valueType.signature.len():
|
||||
if name.valueType.signature[j].default.isNil():
|
||||
inc(requiredCount)
|
||||
if j >= sig.len():
|
||||
hasMissingRequired = true
|
||||
if hasMissingRequired:
|
||||
msg &= &": wrong number of arguments (expected at least {requiredCount}, got {sig.len()} instead)"
|
||||
else:
|
||||
for i, arg in sig:
|
||||
if arg.name != "" and name.valueType.signature[i].name != "" and arg.name != name.valueType.signature[i].name:
|
||||
@@ -1341,6 +1355,10 @@ proc lowerResolvedCall*(self: TypeChecker, node: CallExpr,
|
||||
if matched.genericArgs[i].kind == ConstGenericArg:
|
||||
constBindings[genericBindingKey(parameter.symbol)] = matched.genericArgs[i].value
|
||||
tc_pragmas.dispatchDelayedPragmas(self, impl)
|
||||
# Fill in default values for missing trailing arguments
|
||||
if argExpr.len() < callableType.signature.len():
|
||||
for i in argExpr.len() ..< callableType.signature.len():
|
||||
argExpr.add(callableType.signature[i].default)
|
||||
if impl.valueType.isBuiltin and builtinMagic == MagicBorrow:
|
||||
return self.builtinBorrowExpr(node, rawArgExpr[0])
|
||||
if impl.valueType.isBuiltin and builtinMagic == MagicMove:
|
||||
|
||||
@@ -1245,11 +1245,14 @@ proc returnStmt(self: Parser): Statement =
|
||||
proc parseModulePath(self: Parser, stop: openArray[TokenType], context: string): string =
|
||||
## Parses a module path such as foo/bar or ../foo
|
||||
while not self.done() and self.peek().kind notin stop:
|
||||
if self.match(".."):
|
||||
if not self.check("/"):
|
||||
self.error(&"expecting '/' after '..' in {context}")
|
||||
if self.match("../") or self.match(".."):
|
||||
# The lexer may greedily merge ".." and "/" into a single "../" token,
|
||||
# or keep them separate depending on what follows. Handle both forms.
|
||||
if self.peek(-1).lexeme == "..":
|
||||
if not self.check("/"):
|
||||
self.error(&"expecting '/' after '..' in {context}")
|
||||
discard self.step()
|
||||
result &= "../"
|
||||
discard self.step()
|
||||
elif self.match("/"):
|
||||
self.expect(Identifier, &"expecting identifier after '/' in {context}")
|
||||
result &= &"/{self.peek(-1).lexeme}"
|
||||
@@ -1490,8 +1493,8 @@ proc parseGenericConstraint(self: Parser, endToken: TokenType or string): Expres
|
||||
of "|", "&":
|
||||
result = newBinaryExpr(result, self.step(), self.parseGenericConstraint(endToken))
|
||||
result.file = self.file
|
||||
of ",":
|
||||
discard # Comma is handled in parseGenerics()
|
||||
of ",", "=":
|
||||
discard # Comma is handled in parseGenerics(), = in parseDeclParams()
|
||||
else:
|
||||
self.error("invalid type constraint in generic declaration")
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import variants;
|
||||
|
||||
|
||||
type AsyncCompletion*[T] = enum {
|
||||
# The result of a runAsync() call
|
||||
Completed {
|
||||
value: T;
|
||||
},
|
||||
@@ -31,7 +32,13 @@ type AsyncClock* = object with ImplicitCopy {
|
||||
}
|
||||
|
||||
type AsyncRuntimeOptions* = object with ImplicitCopy {
|
||||
# Maximum number of idling threads in the pool used
|
||||
# for blocking calls. Calls block until there is a
|
||||
# free thread to pick them up
|
||||
maxBlockingThreads: int64;
|
||||
# Maximum number of blocking jobs that can be scheduled
|
||||
# at once. Calls block until there is enough space on the
|
||||
# job queue
|
||||
maxBlockingJobs: int64;
|
||||
}
|
||||
|
||||
@@ -93,32 +100,46 @@ type WaitQueue* = object {
|
||||
tail: ptr cvoid;
|
||||
}
|
||||
|
||||
type peon_i64 = clonglong; #pragma[importc: "int64_t", header: "<stdint.h>"]
|
||||
|
||||
fn peonWaitQueueInitRaw(queue: ptr WaitQueue); #pragma[importc: "peon_wait_queue_init", header: "<peon_runtime.h>", noDecl]
|
||||
fn WaitQueue*: WaitQueue {
|
||||
#pragma[constructor]
|
||||
return WaitQueue(head = unsafe { cast[ptr cvoid](0) }, tail = unsafe { cast[ptr cvoid](0) });
|
||||
}
|
||||
|
||||
fn peonWaitQueueWakeOneRaw(queue: ptr WaitQueue): cbool; #pragma[importc: "peon_wait_queue_wake_one", header: "<peon_runtime.h>", noDecl]
|
||||
|
||||
fn peonWaitQueueWakeAllRaw(queue: ptr WaitQueue): peon_i64; #pragma[importc: "peon_wait_queue_wake_all", header: "<peon_runtime.h>", noDecl]
|
||||
fn initRaw(queue: ptr WaitQueue); #pragma[importc: "peon_wait_queue_init", header: "<peon_runtime.h>", noDecl]
|
||||
fn wakeFirstRaw(queue: ptr WaitQueue): cbool; #pragma[importc: "peon_wait_queue_wake_first", header: "<peon_runtime.h>", noDecl]
|
||||
fn wakeLastRaw(queue: ptr WaitQueue): cbool; #pragma[importc: "peon_wait_queue_wake_last", header: "<peon_runtime.h>", noDecl]
|
||||
fn wakeAllRaw(queue: ptr WaitQueue): clonglong; #pragma[importc: "peon_wait_queue_wake_all", header: "<peon_runtime.h>", noDecl]
|
||||
fn isEmptyRaw(queue: ptr WaitQueue): cbool; #pragma[importc: "peon_wait_queue_is_empty", header: "<peon_runtime.h>", noDecl]
|
||||
|
||||
fn initWaitQueue*(queue: mut lent WaitQueue) {
|
||||
|
||||
fn init*(queue: mut lent WaitQueue) {
|
||||
unsafe {
|
||||
peonWaitQueueInitRaw(unsafePtr(queue));
|
||||
initRaw(unsafePtr(queue));
|
||||
}
|
||||
}
|
||||
|
||||
fn wakeOne*(queue: mut lent WaitQueue): bool {
|
||||
return toBool(unsafe { peonWaitQueueWakeOneRaw(unsafePtr(queue)) });
|
||||
fn wakeFirst*(queue: mut lent WaitQueue): bool {
|
||||
return bool(unsafe { wakeFirstRaw(unsafePtr(queue)) });
|
||||
}
|
||||
|
||||
fn wakeLast*(queue: mut lent WaitQueue): bool {
|
||||
return bool(unsafe { wakeLastRaw(unsafePtr(queue)) });
|
||||
}
|
||||
|
||||
fn wakeAll*(queue: mut lent WaitQueue): int64 {
|
||||
return toInt64(unsafe { peonWaitQueueWakeAllRaw(unsafePtr(queue)) });
|
||||
return int64(unsafe { wakeAllRaw(unsafePtr(queue)) });
|
||||
}
|
||||
|
||||
fn isEmpty*(queue: mut lent WaitQueue): bool {
|
||||
return bool(unsafe { isEmptyRaw(unsafePtr(queue)) });
|
||||
}
|
||||
|
||||
|
||||
fn `clone=`*(src: WaitQueue): WaitQueue {
|
||||
var copied = defaultClone();
|
||||
initWaitQueue(mut &copied);
|
||||
copied.init();
|
||||
return copied;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import builtins/math;
|
||||
import arrays;
|
||||
import ranges;
|
||||
import strings;
|
||||
import sync;
|
||||
import sync/events;
|
||||
import net;
|
||||
|
||||
|
||||
@@ -32,5 +32,5 @@ export math;
|
||||
export arrays;
|
||||
export ranges;
|
||||
export strings;
|
||||
export sync;
|
||||
export events;
|
||||
export net;
|
||||
|
||||
@@ -12,57 +12,53 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import builtins/values;
|
||||
import builtins/async_runtime;
|
||||
import builtins/misc;
|
||||
import cinterop;
|
||||
import ../builtins/values;
|
||||
import ../builtins/async_runtime;
|
||||
import ../builtins/misc;
|
||||
import ../cinterop;
|
||||
|
||||
|
||||
type Event* = object {
|
||||
type Event* = object with Copy {
|
||||
#pragma[noWarn: RawPointerLeak]
|
||||
# We silence the warning because even though
|
||||
# WaitQueue does contain raw pointers it does
|
||||
# not own them, so there is nothing that needs
|
||||
# freeing from the peon side: the C runtime owns
|
||||
# the tasks! TODO: Should we have a custom destructor
|
||||
# to wake all tasks?
|
||||
waiters: WaitQueue;
|
||||
signaled: bool;
|
||||
}
|
||||
|
||||
|
||||
fn init*(self: mut lent Event) {
|
||||
initWaitQueue(self.waiters);
|
||||
self.signaled = false;
|
||||
}
|
||||
|
||||
|
||||
fn Event*(): Event {
|
||||
fn Event*: Event {
|
||||
#pragma[constructor]
|
||||
return Event(waiters = WaitQueue(head = unsafe { cast[ptr cvoid](0) },
|
||||
tail = unsafe { cast[ptr cvoid](0) }),
|
||||
signaled = false);
|
||||
}
|
||||
|
||||
|
||||
fn newEvent*(): Event {
|
||||
return Event();
|
||||
return Event(waiters=WaitQueue(), signaled=false);
|
||||
}
|
||||
|
||||
|
||||
fn `clone=`*(src: Event): Event {
|
||||
var copied = defaultClone();
|
||||
initWaitQueue(mut &copied.waiters);
|
||||
copied.waiters.init();
|
||||
return copied;
|
||||
}
|
||||
|
||||
|
||||
fn `destroy=`*(value: mut lent Event) {
|
||||
defaultDestroy();
|
||||
}
|
||||
|
||||
|
||||
fn set*(self: mut lent Event) {
|
||||
if self.signaled {
|
||||
return;
|
||||
}
|
||||
self.signaled = true;
|
||||
wakeAll(self.waiters);
|
||||
self.waiters.wakeAll();
|
||||
}
|
||||
|
||||
|
||||
fn reset*(self: mut lent Event) {
|
||||
if not self.waiters.isEmpty() {
|
||||
panic("attempted to reset event with some tasks awaiting on it!");
|
||||
}
|
||||
self.signaled = false;
|
||||
self.waiters.init();
|
||||
}
|
||||
|
||||
|
||||
@@ -71,13 +67,13 @@ fn isSet*(self: lent Event): bool {
|
||||
}
|
||||
|
||||
|
||||
fn waitQueue(self: mut lent Event): mut lent WaitQueue {
|
||||
return self.waiters;
|
||||
}
|
||||
|
||||
|
||||
async fn wait*(self: mut lent Event) {
|
||||
while not isSet(self) {
|
||||
await park(self.waitQueue());
|
||||
if self.isSet() {
|
||||
# Every await must be a checkpoint!
|
||||
await checkpoint();
|
||||
return;
|
||||
}
|
||||
while not self.isSet() {
|
||||
await self.waiters.park();
|
||||
}
|
||||
}
|
||||
69
src/peon/stdlib/sync/queues.pn
Normal file
69
src/peon/stdlib/sync/queues.pn
Normal file
@@ -0,0 +1,69 @@
|
||||
import events;
|
||||
import ../cinterop;
|
||||
import ../builtins/seq;
|
||||
import ../builtins/misc;
|
||||
import ../builtins/values;
|
||||
import ../builtins/async_runtime;
|
||||
|
||||
|
||||
type FIFOQueue*[T] = object {
|
||||
# A first-in, first-out asynchronous queue. If
|
||||
# a max size is defined, pushing elements once
|
||||
# the queue is full causes the caller to block
|
||||
# until space is made on it by some other task
|
||||
# popping items off the queue. If no items are
|
||||
# on the queue, attempting to pop one off will
|
||||
# block the caller until some other task pushes
|
||||
# a value back on the queue
|
||||
getters: WaitQueue;
|
||||
putters: WaitQueue;
|
||||
maxSize: int64;
|
||||
data: seq[T];
|
||||
}
|
||||
|
||||
|
||||
fn FIFOQueue*[T](maxSize: int64 = 0): FIFOQueue[T] {
|
||||
# Constructs a new FIFO queue of the provided maximum
|
||||
# size. A value of 0 for maxSize indicates the queue
|
||||
# grows without bounds
|
||||
return FIFOQueue(getters=WaitQueue(),
|
||||
putters=WaitQueue(),
|
||||
maxSize=maxSize,
|
||||
data=newSeqOfCap[T](maxSize)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
fn len*[T](self: FIFOQueue[T]): int64 {
|
||||
return self.data.len();
|
||||
}
|
||||
|
||||
fn high*[T](self: FIFOQueue[T]): int64 {
|
||||
return self.data.high();
|
||||
}
|
||||
|
||||
|
||||
async fn push*[T](self: FIFOQueue[T], v: T) {
|
||||
while self.maxSize > 0 and self.len() == self.maxSize {
|
||||
await self.putters.park();
|
||||
}
|
||||
if not self.getters.isEmpty() {
|
||||
self.getters.wakeFirst();
|
||||
}
|
||||
self.data.append(v);
|
||||
# Holy rule of peon's async stdlib: every await is ALWAYS a checkpoint
|
||||
await checkpoint();
|
||||
}
|
||||
|
||||
|
||||
async fn pop*[T](self: FIFOQueue[T]): T {
|
||||
while self.len() == 0 {
|
||||
await self.getters.park();
|
||||
}
|
||||
if not self.putters.isEmpty() {
|
||||
self.putters.wakeFirst();
|
||||
}
|
||||
await checkpoint();
|
||||
return self.data.pop();
|
||||
}
|
||||
|
||||
@@ -260,7 +260,7 @@ unsafe fn unsafePtr[T](value: mut lent T): ptr T {
|
||||
|
||||
fn peonWaitQueueInitRaw(queue: ptr WaitQueue); #pragma[importc: "peon_wait_queue_init", header: "<peon_runtime.h>", noDecl]
|
||||
|
||||
fn initWaitQueue(queue: mut lent WaitQueue) {
|
||||
fn init(queue: mut lent WaitQueue) {
|
||||
unsafe {
|
||||
peonWaitQueueInitRaw(unsafePtr(queue));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user