added a priority system
This commit is contained in:
1
SPEC.md
1
SPEC.md
@@ -38,6 +38,7 @@ This document reflects the implementation that ships in this repository today (`
|
|||||||
|
|
||||||
## 5. Definitions, Control Flow, and Syntax Sugar
|
## 5. Definitions, Control Flow, and Syntax Sugar
|
||||||
- **Word definitions** – Always `word name ... end`. Redefinitions overwrite the previous entry (a warning prints to stderr). `inline word name ... end` marks the definition for inline expansion; recursive inline calls are rejected. `immediate` and `compile-only` apply to the most recently defined word.
|
- **Word definitions** – Always `word name ... end`. Redefinitions overwrite the previous entry (a warning prints to stderr). `inline word name ... end` marks the definition for inline expansion; recursive inline calls are rejected. `immediate` and `compile-only` apply to the most recently defined word.
|
||||||
|
- **Priority-based redefinition** – Use `priority <int>` before `word`, `:asm`, `:py`, or `extern` to control conflicts for the same name. Higher priority wins; lower-priority definitions are ignored. Equal priority keeps last definition (with a redefinition warning). The compiler prints a note indicating which priority was selected.
|
||||||
- **Control forms** – Built-in tokens drive code emission:
|
- **Control forms** – Built-in tokens drive code emission:
|
||||||
- `if ... end` and `if ... else ... end`. To express additional branches, place `if` on the same line as the preceding `else` (e.g., `else <condition> if ...`); the reader treats that form as an implicit chained clause, so each inline `if` consumes one flag and jumps past later clauses on success.
|
- `if ... end` and `if ... else ... end`. To express additional branches, place `if` on the same line as the preceding `else` (e.g., `else <condition> if ...`); the reader treats that form as an implicit chained clause, so each inline `if` consumes one flag and jumps past later clauses on success.
|
||||||
- `while <condition> do <body> end`; the conditional block lives between `while` and `do` and re-runs every iteration.
|
- `while <condition> do <body> end`; the conditional block lives between `while` and `do` and re-runs every iteration.
|
||||||
|
|||||||
155
main.py
155
main.py
@@ -333,6 +333,7 @@ _WORD_EFFECT_ALIASES: Dict[str, str] = {
|
|||||||
@dataclass
|
@dataclass
|
||||||
class Word:
|
class Word:
|
||||||
name: str
|
name: str
|
||||||
|
priority: int = 0
|
||||||
immediate: bool = False
|
immediate: bool = False
|
||||||
definition: Optional[Union[Definition, AsmDefinition]] = None
|
definition: Optional[Union[Definition, AsmDefinition]] = None
|
||||||
macro: Optional[MacroHandler] = None
|
macro: Optional[MacroHandler] = None
|
||||||
@@ -354,10 +355,39 @@ class Word:
|
|||||||
class Dictionary:
|
class Dictionary:
|
||||||
words: Dict[str, Word] = field(default_factory=dict)
|
words: Dict[str, Word] = field(default_factory=dict)
|
||||||
|
|
||||||
def register(self, word: Word) -> None:
|
def register(self, word: Word) -> Word:
|
||||||
if word.name in self.words:
|
existing = self.words.get(word.name)
|
||||||
sys.stderr.write(f"[warn] redefining word {word.name}\n")
|
if existing is None:
|
||||||
|
self.words[word.name] = word
|
||||||
|
return word
|
||||||
|
|
||||||
|
# Preserve existing intrinsic handlers unless explicitly replaced.
|
||||||
|
if word.runtime_intrinsic is None and existing.runtime_intrinsic is not None:
|
||||||
|
word.runtime_intrinsic = existing.runtime_intrinsic
|
||||||
|
if word.compile_time_intrinsic is None and existing.compile_time_intrinsic is not None:
|
||||||
|
word.compile_time_intrinsic = existing.compile_time_intrinsic
|
||||||
|
|
||||||
|
if word.priority > existing.priority:
|
||||||
|
self.words[word.name] = word
|
||||||
|
sys.stderr.write(
|
||||||
|
f"[note] word {word.name}: using priority {word.priority} over {existing.priority}\n"
|
||||||
|
)
|
||||||
|
return word
|
||||||
|
|
||||||
|
if word.priority < existing.priority:
|
||||||
|
sys.stderr.write(
|
||||||
|
f"[note] word {word.name}: keeping priority {existing.priority}, ignored {word.priority}\n"
|
||||||
|
)
|
||||||
|
return existing
|
||||||
|
|
||||||
|
# Same priority: allow replacing placeholder bootstrap words silently.
|
||||||
|
if existing.definition is None and word.definition is not None:
|
||||||
|
self.words[word.name] = word
|
||||||
|
return word
|
||||||
|
|
||||||
|
sys.stderr.write(f"[warn] redefining word {word.name} (priority {word.priority})\n")
|
||||||
self.words[word.name] = word
|
self.words[word.name] = word
|
||||||
|
return word
|
||||||
|
|
||||||
def lookup(self, name: str) -> Optional[Word]:
|
def lookup(self, name: str) -> Optional[Word]:
|
||||||
return self.words.get(name)
|
return self.words.get(name)
|
||||||
@@ -380,7 +410,7 @@ class Parser:
|
|||||||
self._token_iter_exhausted = True
|
self._token_iter_exhausted = True
|
||||||
self.pos = 0
|
self.pos = 0
|
||||||
self.context_stack: List[Context] = []
|
self.context_stack: List[Context] = []
|
||||||
self.definition_stack: List[Word] = []
|
self.definition_stack: List[Tuple[Word, bool]] = []
|
||||||
self.last_defined: Optional[Word] = None
|
self.last_defined: Optional[Word] = None
|
||||||
self.source: str = ""
|
self.source: str = ""
|
||||||
self.macro_recording: Optional[MacroDefinition] = None
|
self.macro_recording: Optional[MacroDefinition] = None
|
||||||
@@ -395,6 +425,7 @@ class Parser:
|
|||||||
self.custom_prelude: Optional[List[str]] = None
|
self.custom_prelude: Optional[List[str]] = None
|
||||||
self.custom_bss: Optional[List[str]] = None
|
self.custom_bss: Optional[List[str]] = None
|
||||||
self._pending_inline_definition: bool = False
|
self._pending_inline_definition: bool = False
|
||||||
|
self._pending_priority: Optional[int] = None
|
||||||
|
|
||||||
def location_for_token(self, token: Token) -> SourceLocation:
|
def location_for_token(self, token: Token) -> SourceLocation:
|
||||||
for span in self.file_spans:
|
for span in self.file_spans:
|
||||||
@@ -501,6 +532,7 @@ class Parser:
|
|||||||
self.custom_prelude = None
|
self.custom_prelude = None
|
||||||
self.custom_bss = None
|
self.custom_bss = None
|
||||||
self._pending_inline_definition = False
|
self._pending_inline_definition = False
|
||||||
|
self._pending_priority = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
while not self._eof():
|
while not self._eof():
|
||||||
@@ -511,6 +543,17 @@ class Parser:
|
|||||||
if self._handle_macro_recording(token):
|
if self._handle_macro_recording(token):
|
||||||
continue
|
continue
|
||||||
lexeme = token.lexeme
|
lexeme = token.lexeme
|
||||||
|
if self._pending_priority is not None and lexeme not in {
|
||||||
|
"word",
|
||||||
|
":asm",
|
||||||
|
":py",
|
||||||
|
"extern",
|
||||||
|
"inline",
|
||||||
|
"priority",
|
||||||
|
}:
|
||||||
|
raise ParseError(
|
||||||
|
f"priority {self._pending_priority} must be followed by definition/extern"
|
||||||
|
)
|
||||||
if lexeme == "[":
|
if lexeme == "[":
|
||||||
self._handle_list_begin()
|
self._handle_list_begin()
|
||||||
continue
|
continue
|
||||||
@@ -537,6 +580,9 @@ class Parser:
|
|||||||
if lexeme == "extern":
|
if lexeme == "extern":
|
||||||
self._parse_extern(token)
|
self._parse_extern(token)
|
||||||
continue
|
continue
|
||||||
|
if lexeme == "priority":
|
||||||
|
self._parse_priority_directive(token)
|
||||||
|
continue
|
||||||
if lexeme == "if":
|
if lexeme == "if":
|
||||||
self._handle_if_control()
|
self._handle_if_control()
|
||||||
continue
|
continue
|
||||||
@@ -567,6 +613,8 @@ class Parser:
|
|||||||
|
|
||||||
if self.macro_recording is not None:
|
if self.macro_recording is not None:
|
||||||
raise ParseError("unterminated macro definition (missing ';')")
|
raise ParseError("unterminated macro definition (missing ';')")
|
||||||
|
if self._pending_priority is not None:
|
||||||
|
raise ParseError(f"dangling priority {self._pending_priority} without following definition")
|
||||||
|
|
||||||
if len(self.context_stack) != 1:
|
if len(self.context_stack) != 1:
|
||||||
raise ParseError("unclosed definition at EOF")
|
raise ParseError("unclosed definition at EOF")
|
||||||
@@ -591,6 +639,25 @@ class Parser:
|
|||||||
label = entry["label"]
|
label = entry["label"]
|
||||||
self._append_op(Op(op="list_end", data=label))
|
self._append_op(Op(op="list_end", data=label))
|
||||||
|
|
||||||
|
def _parse_priority_directive(self, token: Token) -> None:
|
||||||
|
if self._eof():
|
||||||
|
raise ParseError(f"priority value missing at {token.line}:{token.column}")
|
||||||
|
value_tok = self._consume()
|
||||||
|
try:
|
||||||
|
value = int(value_tok.lexeme, 0)
|
||||||
|
except ValueError:
|
||||||
|
raise ParseError(
|
||||||
|
f"invalid priority '{value_tok.lexeme}' at {value_tok.line}:{value_tok.column}"
|
||||||
|
)
|
||||||
|
self._pending_priority = value
|
||||||
|
|
||||||
|
def _consume_pending_priority(self) -> int:
|
||||||
|
if self._pending_priority is None:
|
||||||
|
return 0
|
||||||
|
value = self._pending_priority
|
||||||
|
self._pending_priority = None
|
||||||
|
return value
|
||||||
|
|
||||||
# Internal helpers ---------------------------------------------------------
|
# Internal helpers ---------------------------------------------------------
|
||||||
|
|
||||||
def _parse_extern(self, token: Token) -> None:
|
def _parse_extern(self, token: Token) -> None:
|
||||||
@@ -601,17 +668,18 @@ class Parser:
|
|||||||
if self._eof():
|
if self._eof():
|
||||||
raise ParseError(f"extern missing name at {token.line}:{token.column}")
|
raise ParseError(f"extern missing name at {token.line}:{token.column}")
|
||||||
|
|
||||||
|
priority = self._consume_pending_priority()
|
||||||
first_token = self._consume()
|
first_token = self._consume()
|
||||||
if self._try_parse_c_extern(first_token):
|
if self._try_parse_c_extern(first_token, priority=priority):
|
||||||
return
|
return
|
||||||
self._parse_legacy_extern(first_token)
|
self._parse_legacy_extern(first_token, priority=priority)
|
||||||
|
|
||||||
def _parse_legacy_extern(self, name_token: Token) -> None:
|
def _parse_legacy_extern(self, name_token: Token, *, priority: int = 0) -> None:
|
||||||
name = name_token.lexeme
|
name = name_token.lexeme
|
||||||
word = self.dictionary.lookup(name)
|
candidate = Word(name=name, priority=priority)
|
||||||
if word is None:
|
word = self.dictionary.register(candidate)
|
||||||
word = Word(name=name)
|
if word is not candidate:
|
||||||
self.dictionary.register(word)
|
return
|
||||||
word.is_extern = True
|
word.is_extern = True
|
||||||
|
|
||||||
peek = self.peek_token()
|
peek = self.peek_token()
|
||||||
@@ -626,7 +694,7 @@ class Parser:
|
|||||||
word.extern_inputs = 0
|
word.extern_inputs = 0
|
||||||
word.extern_outputs = 0
|
word.extern_outputs = 0
|
||||||
|
|
||||||
def _try_parse_c_extern(self, first_token: Token) -> bool:
|
def _try_parse_c_extern(self, first_token: Token, *, priority: int = 0) -> bool:
|
||||||
saved_pos = self.pos
|
saved_pos = self.pos
|
||||||
prefix_tokens: List[str] = [first_token.lexeme]
|
prefix_tokens: List[str] = [first_token.lexeme]
|
||||||
|
|
||||||
@@ -660,7 +728,7 @@ class Parser:
|
|||||||
ret_type = _normalize_c_type_tokens(prefix_tokens, allow_default=True)
|
ret_type = _normalize_c_type_tokens(prefix_tokens, allow_default=True)
|
||||||
inputs, arg_types = self._parse_c_param_list()
|
inputs, arg_types = self._parse_c_param_list()
|
||||||
outputs = 0 if ret_type == "void" else 1
|
outputs = 0 if ret_type == "void" else 1
|
||||||
self._register_c_extern(name_lexeme, inputs, outputs, arg_types, ret_type)
|
self._register_c_extern(name_lexeme, inputs, outputs, arg_types, ret_type, priority=priority)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _parse_c_param_list(self) -> Tuple[int, List[str]]:
|
def _parse_c_param_list(self) -> Tuple[int, List[str]]:
|
||||||
@@ -729,11 +797,13 @@ class Parser:
|
|||||||
outputs: int,
|
outputs: int,
|
||||||
arg_types: List[str],
|
arg_types: List[str],
|
||||||
ret_type: str,
|
ret_type: str,
|
||||||
|
*,
|
||||||
|
priority: int = 0,
|
||||||
) -> None:
|
) -> None:
|
||||||
word = self.dictionary.lookup(name)
|
candidate = Word(name=name, priority=priority)
|
||||||
if word is None:
|
word = self.dictionary.register(candidate)
|
||||||
word = Word(name=name)
|
if word is not candidate:
|
||||||
self.dictionary.register(word)
|
return
|
||||||
word.is_extern = True
|
word.is_extern = True
|
||||||
word.extern_inputs = inputs
|
word.extern_inputs = inputs
|
||||||
word.extern_outputs = outputs
|
word.extern_outputs = outputs
|
||||||
@@ -937,6 +1007,7 @@ class Parser:
|
|||||||
f"definition name missing after '{token.lexeme}' at {token.line}:{token.column}"
|
f"definition name missing after '{token.lexeme}' at {token.line}:{token.column}"
|
||||||
)
|
)
|
||||||
name_token = self._consume()
|
name_token = self._consume()
|
||||||
|
priority = self._consume_pending_priority()
|
||||||
definition = Definition(
|
definition = Definition(
|
||||||
name=name_token.lexeme,
|
name=name_token.lexeme,
|
||||||
body=[],
|
body=[],
|
||||||
@@ -944,13 +1015,12 @@ class Parser:
|
|||||||
inline=inline,
|
inline=inline,
|
||||||
)
|
)
|
||||||
self.context_stack.append(definition)
|
self.context_stack.append(definition)
|
||||||
word = self.dictionary.lookup(definition.name)
|
candidate = Word(name=definition.name, priority=priority)
|
||||||
if word is None:
|
candidate.definition = definition
|
||||||
word = Word(name=definition.name)
|
candidate.inline = inline
|
||||||
self.dictionary.register(word)
|
active_word = self.dictionary.register(candidate)
|
||||||
word.definition = definition
|
is_active = active_word is candidate
|
||||||
word.inline = inline
|
self.definition_stack.append((candidate, is_active))
|
||||||
self.definition_stack.append(word)
|
|
||||||
|
|
||||||
def _end_definition(self, token: Token) -> None:
|
def _end_definition(self, token: Token) -> None:
|
||||||
if len(self.context_stack) <= 1:
|
if len(self.context_stack) <= 1:
|
||||||
@@ -962,7 +1032,9 @@ class Parser:
|
|||||||
raise ParseError(
|
raise ParseError(
|
||||||
f"definition '{ctx.name}' expects terminator '{ctx.terminator}' but got '{token.lexeme}'"
|
f"definition '{ctx.name}' expects terminator '{ctx.terminator}' but got '{token.lexeme}'"
|
||||||
)
|
)
|
||||||
word = self.definition_stack.pop()
|
word, is_active = self.definition_stack.pop()
|
||||||
|
if not is_active:
|
||||||
|
return
|
||||||
ctx.immediate = word.immediate
|
ctx.immediate = word.immediate
|
||||||
ctx.compile_only = word.compile_only
|
ctx.compile_only = word.compile_only
|
||||||
ctx.inline = word.inline
|
ctx.inline = word.inline
|
||||||
@@ -1033,21 +1105,22 @@ class Parser:
|
|||||||
if block_end is None:
|
if block_end is None:
|
||||||
raise ParseError("missing '}' to terminate asm body")
|
raise ParseError("missing '}' to terminate asm body")
|
||||||
asm_body = self.source[block_start:block_end]
|
asm_body = self.source[block_start:block_end]
|
||||||
|
priority = self._consume_pending_priority()
|
||||||
definition = AsmDefinition(name=name_token.lexeme, body=asm_body)
|
definition = AsmDefinition(name=name_token.lexeme, body=asm_body)
|
||||||
if effect_names is not None:
|
if effect_names is not None:
|
||||||
definition.effects = set(effect_names)
|
definition.effects = set(effect_names)
|
||||||
word = self.dictionary.lookup(definition.name)
|
candidate = Word(name=definition.name, priority=priority)
|
||||||
if word is None:
|
candidate.definition = definition
|
||||||
word = Word(name=definition.name)
|
word = self.dictionary.register(candidate)
|
||||||
self.dictionary.register(word)
|
if word is candidate:
|
||||||
word.definition = definition
|
definition.immediate = word.immediate
|
||||||
definition.immediate = word.immediate
|
definition.compile_only = word.compile_only
|
||||||
definition.compile_only = word.compile_only
|
|
||||||
module = self.context_stack[-1]
|
module = self.context_stack[-1]
|
||||||
if not isinstance(module, Module):
|
if not isinstance(module, Module):
|
||||||
raise ParseError("asm definitions must be top-level forms")
|
raise ParseError("asm definitions must be top-level forms")
|
||||||
module.forms.append(definition)
|
if word is candidate:
|
||||||
self.last_defined = word
|
module.forms.append(definition)
|
||||||
|
self.last_defined = word
|
||||||
if self._eof():
|
if self._eof():
|
||||||
raise ParseError("asm definition missing terminator ';'")
|
raise ParseError("asm definition missing terminator ';'")
|
||||||
terminator = self._consume()
|
terminator = self._consume()
|
||||||
@@ -1071,9 +1144,16 @@ class Parser:
|
|||||||
if block_end is None:
|
if block_end is None:
|
||||||
raise ParseError("missing '}' to terminate py body")
|
raise ParseError("missing '}' to terminate py body")
|
||||||
py_body = textwrap.dedent(self.source[block_start:block_end])
|
py_body = textwrap.dedent(self.source[block_start:block_end])
|
||||||
word = self.dictionary.lookup(name_token.lexeme)
|
priority = self._consume_pending_priority()
|
||||||
if word is None:
|
candidate = Word(name=name_token.lexeme, priority=priority)
|
||||||
word = Word(name=name_token.lexeme)
|
word = self.dictionary.register(candidate)
|
||||||
|
if word is not candidate:
|
||||||
|
if self._eof():
|
||||||
|
raise ParseError("py definition missing terminator ';'")
|
||||||
|
terminator = self._consume()
|
||||||
|
if terminator.lexeme != ";":
|
||||||
|
raise ParseError(f"expected ';' after py definition at {terminator.line}:{terminator.column}")
|
||||||
|
return
|
||||||
namespace = self._py_exec_namespace()
|
namespace = self._py_exec_namespace()
|
||||||
try:
|
try:
|
||||||
exec(py_body, namespace)
|
exec(py_body, namespace)
|
||||||
@@ -1088,7 +1168,6 @@ class Parser:
|
|||||||
word.immediate = True
|
word.immediate = True
|
||||||
if intrinsic_fn is not None:
|
if intrinsic_fn is not None:
|
||||||
word.intrinsic = intrinsic_fn
|
word.intrinsic = intrinsic_fn
|
||||||
self.dictionary.register(word)
|
|
||||||
if self._eof():
|
if self._eof():
|
||||||
raise ParseError("py definition missing terminator ';'")
|
raise ParseError("py definition missing terminator ';'")
|
||||||
terminator = self._consume()
|
terminator = self._consume()
|
||||||
|
|||||||
Reference in New Issue
Block a user