// Lexical analysis of the device tree source format.
//
// The scanner produces tokens and records the line and column of each one, so
// a syntax error can point at a place in the file rather than at a byte
// offset. Byte offsets would be useless here: DTS is edited by hand and the
// reader is looking at a text editor, not a hex dump.
//
// The format has no string interpolation and no numeric literals with a sign,
// which keeps the scanner small. The one unusual rule is that a slash starts
// three different things: a node close `/`, a directive like `/dts-v1/`, and a
// comment `//` or `/*`, so the scanner looks ahead by one character in every
// case where a slash appears.
///|
/// A position in the source file, 1-based so that it matches what an editor
/// shows.
pub(all) struct Span {
/// Line number, starting at 1.
line : Int32
/// Column number, starting at 1.
column : Int32
}
///|
/// Renders a span the way a compiler does: `line:column`.
pub impl Show for Span with fn output(self, logger) {
logger.write_string("\{self.line}:\{self.column}")
}
///|
/// Exposes the span text as a plain method.
pub extend Span with Show::{to_string, output}
///|
/// The kinds of token the source format needs.
///
/// A label appears before a node rather than inside the value grammar, so it
/// needs no separate token kind: the parser sees `Ident`, `Colon` and decides
/// from what follows.
pub(all) enum TokenKind {
/// An identifier: a node name, a property name, a label, a directive word.
Ident(String)
/// A double-quoted string with escapes already resolved.
String(String)
/// An unsigned integer literal.
Number(UInt64)
/// `{`
LBrace
/// `}`
RBrace
/// `<`
LAngle
/// `>`
RAngle
/// `[`
LBracket
/// `]`
RBracket
/// `(`
LParen
/// `)`
RParen
/// `,`
Comma
/// `;`
Semicolon
/// `=`
Equals
/// `/`
Slash
/// `&`, which introduces a reference
Amp
/// `:` used to bind a label to a node
Colon
/// `-`, used by `/delete-property/` style directives and by `/omit-if-no-ref/`
Minus
/// A two-digit hex byte from inside a `[...]` byte string.
///
/// The scanner cannot decide what a digit run means without knowing that it
/// is inside brackets, so it tracks that state. Emitting bytes rather than
/// numbers avoids re-splitting `11` back into digits at the parser, which is
/// where the first version got `00 11` wrong: `00` folded to `0` and `11` to
/// decimal eleven.
HexByte(Byte)
/// End of input
Eof
}
///|
/// Renders a token for error messages: the kind, not the span.
pub impl Show for TokenKind with fn output(self, logger) {
let text = match self {
Ident(s) => "identifier '\{s}'"
String(_) => "string"
Number(n) => "number \{n}"
LBrace => "'{'"
RBrace => "'}'"
LAngle => "'<'"
RAngle => "'>'"
LBracket => "'['"
RBracket => "']'"
LParen => "'('"
RParen => "')'"
Comma => "','"
Semicolon => "';'"
Equals => "'='"
Slash => "'/'"
Amp => "'&'"
Colon => "':'"
Minus => "'-'"
HexByte(b) => "byte 0x\{b.to_int()}"
Eof => "end of input"
}
logger.write_string(text)
}
///|
/// Exposes the token text as a plain method.
pub extend TokenKind with Show::{to_string, output}
///|
/// A token together with where it came from.
///
/// Declared after `TokenKind` because MoonBit resolves a struct's field types
/// at the point the struct is declared, and the scanner returns these by
/// value, so the order matters.
pub(all) struct Token {
/// What the token is.
kind : TokenKind
/// Where it starts.
span : Span
}
///|
/// The scanner state: the source text plus a position in it.
///
/// The position is a byte offset into the string together with the line and
/// column that offset sits at. Both are advanced together so that `Span` never
/// has to be recomputed by rescanning from the start of the file.
pub(all) struct Lexer {
source : String
mut pos : Int32
mut line : Int32
mut column : Int32
/// Whether the scanner is inside a `[...]` byte string.
///
/// A digit run means a byte there and a number everywhere else, and the two
/// cannot be told apart after the fact: `00` as a number is zero, losing the
/// digit count that makes it two hex digits. Tracking one bit here keeps the
/// parser from having to reconstruct it.
mut in_bytes : Bool
}
///|
/// Copies the scanner so a caller can look at the next token without
/// consuming it.
///
/// The alternative is a token buffer in the parser, which would mean every
/// directive check allocating. A clone is two integers and a string reference,
/// and the string is immutable, so this is the cheaper of the two.
pub fn Lexer::clone(self : Lexer) -> Lexer {
Lexer::{
source: self.source,
pos: self.pos,
line: self.line,
column: self.column,
in_bytes: self.in_bytes,
}
}
///|
/// Creates a scanner at the first character of `source`.
pub fn Lexer::new(source : String) -> Lexer {
Lexer::{ source, pos: 0, line: 1, column: 1, in_bytes: false, }
}
///|
/// Whether the scanner has reached the end.
pub fn Lexer::at_end(self : Lexer) -> Bool {
self.pos >= self.source.length()
}
///|
/// Returns the character `ahead` places past the cursor without consuming it.
///
/// Used for the one-character lookahead that every slash and every number
/// needs. Returns NUL past the end so callers can compare without an option.
fn Lexer::peek(self : Lexer, ahead : Int32) -> Char {
let at = self.pos + ahead
if at >= self.source.length() {
'\u{0}'
} else {
self.source.get_char(at).unwrap()
}
}
///|
/// Consumes one character and keeps the line and column in step.
fn Lexer::bump(self : Lexer) -> Unit {
if self.at_end() {
return
}
if self.peek(0) == '\n' {
self.line = self.line + 1
self.column = 1
} else {
self.column = self.column + 1
}
self.pos = self.pos + 1
}
///|
/// Skips whitespace and comments. DTS has `//` line comments and `/* */`
/// block comments, the same set C has minus the preprocessor cases.
///
/// A `#` at the start of a line is a preprocessor marker left behind by cpp,
/// such as `# 1 "board.dts"`. Sources that were expanded before being handed
/// here begin with several, and they carry no meaning once the expansion is
/// done, so they are dropped here rather than being reported as a syntax
/// error. A `#` anywhere else is part of a name such as `#address-cells`.
fn Lexer::skip_trivia(self : Lexer) -> Unit raise LexerError {
while !self.at_end() {
let c = self.peek(0)
if c == ' ' || c == '\t' || c == '\r' || c == '\n' {
self.bump()
} else if c == '#' && self.at_line_start() && self.looks_like_line_marker() {
while !self.at_end() && self.peek(0) != '\n' {
self.bump()
}
} else if c == '/' && self.peek(1) == '/' {
while !self.at_end() && self.peek(0) != '\n' {
self.bump()
}
} else if c == '/' && self.peek(1) == '*' {
let start = self.span()
self.bump()
self.bump()
let mut closed = false
while !self.at_end() {
if self.peek(0) == '*' && self.peek(1) == '/' {
self.bump()
self.bump()
closed = true
break
}
self.bump()
}
if !closed {
raise UnterminatedComment(start~)
}
} else {
return
}
}
}
///|
/// The current position as a span.
fn Lexer::span(self : Lexer) -> Span {
Span::{ line: self.line, column: self.column, }
}