diff --git a/HISTORY.md b/HISTORY.md index 98440e2..a73ba45 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,12 @@ History of SixtyPical ===================== +0.15 +---- + +* Symbolic constants can be defined with the `const` keyword, and can + be used in most places where literal values can be used. + 0.14 ---- diff --git a/README.md b/README.md index c5f30a8..8456707 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,6 @@ is probably NP-complete. But doing it adequately is probably not that hard. ### And at some point... * `low` and `high` address operators - to turn `word` type into `byte`. -* `const`s that can be used in defining the size of tables, etc. * Tests, and implementation, ensuring a routine can be assigned to a vector of "wider" type * Related: can we simply view a (small) part of a buffer as a byte table? If not, why not? * Related: add constant to buffer to get new buffer. (Or to table, but... well, maybe.) diff --git a/doc/SixtyPical.md b/doc/SixtyPical.md index 22b5604..1955fe8 100644 --- a/doc/SixtyPical.md +++ b/doc/SixtyPical.md @@ -1,7 +1,7 @@ SixtyPical ========== -This document describes the SixtyPical programming language version 0.14, +This document describes the SixtyPical programming language version 0.15, both its static semantics (the capabilities and limits of the static analyses it defines) and its runtime semantics (with reference to the semantics of 6502 machine code.) @@ -555,9 +555,10 @@ The block is always executed as least once. Grammar ------- - Program ::= {TypeDefn} {Defn} {Routine}. + Program ::= {ConstDefn | TypeDefn} {Defn} {Routine}. + ConstDefn::= "const" Ident Const. TypeDefn::= "typedef" Type Ident. - Defn ::= Type Ident [Constraints] (":" Literal | "@" LitWord). + Defn ::= Type Ident [Constraints] (":" Const | "@" LitWord). Type ::= TypeTerm ["table" TypeSize]. TypeExpr::= "byte" | "word" @@ -573,12 +574,13 @@ Grammar | "routine" Ident Constraints (Block | "@" LitWord) . LocExprs::= LocExpr {"," LocExpr}. - LocExpr ::= Register | Flag | Literal | Ident. + LocExpr ::= Register | Flag | Const | Ident. Register::= "a" | "x" | "y". Flag ::= "c" | "z" | "n" | "v". + Const ::= Literal | Ident. Literal ::= LitByte | LitWord | LitBit. LitByte ::= "0" ... "255". - LitWord ::= "0" ... "65535". + LitWord ::= ["word"] "0" ... "65535". LitBit ::= "on" | "off". Block ::= "{" {Instr} "}". Instr ::= "ld" LocExpr "," LocExpr ["+" LocExpr] @@ -598,6 +600,6 @@ Grammar | "copy" LocExpr "," LocExpr ["+" LocExpr] | "if" ["not"] LocExpr Block ["else" Block] | "repeat" Block ("until" ["not"] LocExpr | "forever") - | "for" LocExpr ("up"|"down") "to" Literal Block + | "for" LocExpr ("up"|"down") "to" Const Block | "with" "interrupts" LitBit Block . diff --git a/src/sixtypical/parser.py b/src/sixtypical/parser.py index 2391d05..22ab9ec 100644 --- a/src/sixtypical/parser.py +++ b/src/sixtypical/parser.py @@ -24,6 +24,7 @@ class Parser(object): self.symbols = {} # token -> SymEntry self.current_statics = {} # token -> SymEntry self.typedefs = {} # token -> Type AST + self.consts = {} # token -> Loc for token in ('a', 'x', 'y'): self.symbols[token] = SymEntry(None, LocationRef(TYPE_BYTE, token)) for token in ('c', 'z', 'n', 'v'): @@ -51,8 +52,11 @@ class Parser(object): def program(self): defns = [] routines = [] - while self.scanner.on('typedef'): - typedef = self.typedef() + while self.scanner.on('typedef', 'const'): + if self.scanner.on('typedef'): + self.typedef() + if self.scanner.on('const'): + self.defn_const() typenames = ['byte', 'word', 'table', 'vector', 'buffer', 'pointer'] # 'routine', typenames.extend(self.typedefs.keys()) while self.scanner.on(*typenames): @@ -110,6 +114,15 @@ class Parser(object): self.typedefs[name] = type_ return type_ + def defn_const(self): + self.scanner.expect('const') + name = self.defn_name() + if name in self.consts: + self.syntax_error('Const "%s" already declared' % name) + loc = self.const() + self.consts[name] = loc + return loc + def defn(self): type_ = self.defn_type() name = self.defn_name() @@ -118,10 +131,9 @@ class Parser(object): if self.scanner.consume(':'): if isinstance(type_, TableType) and self.scanner.on_type('string literal'): initial = self.scanner.token + self.scanner.scan() else: - self.scanner.check_type('integer literal') - initial = int(self.scanner.token) - self.scanner.scan() + initial = self.const().value addr = None if self.scanner.consume('@'): @@ -136,21 +148,31 @@ class Parser(object): return Defn(self.scanner.line_number, name=name, addr=addr, initial=initial, location=location) - def literal_int(self): - self.scanner.check_type('integer literal') - c = int(self.scanner.token) - self.scanner.scan() - return c - - def literal_int_const(self): - value = self.literal_int() - type_ = TYPE_WORD if value > 255 else TYPE_BYTE - loc = ConstantRef(type_, value) - return loc + def const(self): + if self.scanner.token in ('on', 'off'): + loc = ConstantRef(TYPE_BIT, 1 if self.scanner.token == 'on' else 0) + self.scanner.scan() + return loc + elif self.scanner.on_type('integer literal'): + value = int(self.scanner.token) + self.scanner.scan() + type_ = TYPE_WORD if value > 255 else TYPE_BYTE + loc = ConstantRef(type_, value) + return loc + elif self.scanner.consume('word'): + loc = ConstantRef(TYPE_WORD, int(self.scanner.token)) + self.scanner.scan() + return loc + elif self.scanner.token in self.consts: + loc = self.consts[self.scanner.token] + self.scanner.scan() + return loc + else: + self.syntax_error('bad constant "%s"' % self.scanner.token) def defn_size(self): self.scanner.expect('[') - size = self.literal_int() + size = self.const().value self.scanner.expect(']') return size @@ -294,16 +316,8 @@ class Parser(object): return accum def locexpr(self, forward=False): - if self.scanner.token in ('on', 'off'): - loc = ConstantRef(TYPE_BIT, 1 if self.scanner.token == 'on' else 0) - self.scanner.scan() - return loc - elif self.scanner.on_type('integer literal'): - return self.literal_int_const() - elif self.scanner.consume('word'): - loc = ConstantRef(TYPE_WORD, int(self.scanner.token)) - self.scanner.scan() - return loc + if self.scanner.token in ('on', 'off', 'word') or self.scanner.token in self.consts or self.scanner.on_type('integer literal'): + return self.const() elif forward: name = self.scanner.token self.scanner.scan() @@ -387,7 +401,7 @@ class Parser(object): else: self.syntax_error('expected "up" or "down", found "%s"' % self.scanner.token) self.scanner.expect('to') - final = self.literal_int_const() + final = self.const() block = self.block() return For(self.scanner.line_number, dest=dest, direction=direction, final=final, block=block) elif self.scanner.token in ("ld",): diff --git a/tests/SixtyPical Syntax.md b/tests/SixtyPical Syntax.md index 2add5ea..fd2b432 100644 --- a/tests/SixtyPical Syntax.md +++ b/tests/SixtyPical Syntax.md @@ -228,6 +228,31 @@ Can't have two typedefs with the same name. | } ? SyntaxError +Constants. + + | const lives 3 + | const days lives + | const w1 1000 + | const w2 word 0 + | + | typedef byte table[days] them + | + | byte lark: lives + | + | routine main { + | ld a, lives + | } + = ok + +Can't have two constants with the same name. + + | const w1 1000 + | const w1 word 0 + | + | routine main { + | } + ? SyntaxError + Explicit memory address. | byte screen @ 1024