~otheb/stf

7523a9aa723966cdf420da5395d5d2857f17e922 — Olie Ayre 3 years ago 2d77b09
Write lexer
5 files changed, 244 insertions(+), 1 deletions(-)

M design
A err.d
A lexer.d
M stf.d
A stf.o
M design => design +0 -1
@@ 4,7 4,6 @@
-?[0-9]+u?l? \ integer
-?[0-9]*\.[0-9]+d? \ float
".*" \ string
(true|false) \ bool

\ line comment
( block comment ( that nests ) )

A err.d => err.d +29 -0
@@ 0,0 1,29 @@
import std.string ;
import std.range  ;
import std.stdio  ;

bool hadErr = false ;

void error( string code , ulong offset , string message ) {
	report( code , offset , message ) ;
	hadErr = true ;
}

void report( string code , ulong offset , string message ) {
	// get line
	auto lines = code.splitLines( Yes.keepTerminator ) ;
	ulong o = 0 ;
	for ( int l = 0 ; l < lines.length ; l ++ ) {
		if ( o + lines[l].length > offset ) {
			// log error
			stderr.writeln( "\x1b[31merr(" , l + 1 , "):\x1b[0m " , message ,
			                "\n" , lines[l] ,
			                cast(string)repeat( ' ' , offset - o ).array ,
			                "\x1b[31m^- Here\x1b[0m\n" ) ;
			return ;
		} else o += lines[l].length ;
	}

	// default if line could not be found
	stderr.writeln( "\x1b[31merr:\x1b[0m " , message ) ;
}

A lexer.d => lexer.d +182 -0
@@ 0,0 1,182 @@
import std.variant   ;
import std.conv      ;
import std.algorithm ;
import std.regex     ;

import err ;

alias Value = Algebraic!( int , long , uint , ulong ,
                          float , double , string ) ;

enum TokenType {
	// literals
	Int , Float , String ,
	// words
	Word ,
	// misc
	EOF ,
}

struct Token {
	TokenType type    ;
	ulong     offset  ;
	string    content ;
	Value     value   ;

	private this( ulong o , string c , TokenType t , Value v = Value() ) {
		offset  = o ;
		content = c ;
		type    = t ;
		value   = v ;
	}

	string toString() {
		return text( type , " " , content ,
		             value.hasValue ? " => " ~ value.text : "" ) ;
	}
}
Token token(T)( ulong o , string c , TokenType t , T v ) {
	return Token( o , c , t , Value( v ) ) ;
}
Token token( ulong o , string c , TokenType t ) { return Token( o , c , t ) ; }

Token[] lex( string code ) {
	return Lexer( code ).getTokens() ;
}

struct Lexer {
	ulong   curr  = 0 ;
	ulong   start = 0 ;
	string  code      ;
	Token[] tokens    ;

	this( string c ) { code = c ; }

	Token[] getTokens() {
		while ( ! atEnd ) {
			start = curr ;
			getToken() ;
		}
		tokens ~= token( code.length , "" , TokenType.EOF ) ;
		return tokens ;
	}

	void getToken() {
		char c = step() ;
		switch ( c ) {
			case '\\' : // line comment
				while ( step() != '\n' && ! atEnd ) { }
				break ;
			// whitespace
			case ' ' : case '\t' : case '\r' : case '\n' : break ;
			// string
			case '"' : stringLit() ; break ;
			default :
				// get rest of word
				while ( ! ( " \t\r\n".canFind( peek() ) ) && ! atEnd )
					step() ;
				string word = code[start..curr] ;

				// float
				if ( matches( word , `-?[0-9]*\.[0-9]+d?` ) ) {
					if ( word.endsWith( 'd' ) ) {
						addToken( TokenType.Float ,
								  word[ 0 .. $ - 1 ].to!double ) ;
						break ;
					}
					double d = word.to!double ;
					if ( d > float.max || d < float.min_exp )
						addToken( TokenType.Float , d ) ;
					else addToken( TokenType.Float , d.to!float ) ;
					break ;
				}
				
				// int
				if ( matches( word , `-?[0-9]+u?l?` ) ) {
					bool u     = false ;
					bool l     = false ;
					bool minus = false ;
					if ( word.endsWith( 'l' ) ) {
						word = word[ 0 .. $ - 1 ] ;
						l = true ;
					}
					if ( word.startsWith( '-' ) ) minus = true ;
					if ( word.endsWith( 'u' ) ) {
						word = word[ 0 .. $ - 1 ] ;
						u = true ;
					}

					if ( u && minus ) {
						error( code , start ,
							"Unsigned number literal cannot start with '-'"
						) ;
						break ;
					}

					if ( u && l ) addToken( TokenType.Int , word.to!ulong ) ;
					else if ( l && minus )
						addToken( TokenType.Int , word.to!long ) ;
					else if ( u ) {
						if ( word.to!ulong > uint.max )
							addToken( TokenType.Int , word.to!ulong ) ;
						else addToken( TokenType.Int , word.to!uint ) ;
					} else if ( l ) {
						if ( word.to!long > int.max )
							addToken( TokenType.Int , word.to!long ) ;
						else addToken( TokenType.Int , word.to!int ) ;
					}
					break ;
				}

				// word
				addToken( TokenType.Word ) ;
				break ;
		}
	}

	void stringLit() {
		string value = "" ;
		while ( ! atEnd ) {
			char c = step() ;
			switch ( c ) {
				case '"' : // end of string
					addToken( TokenType.String , value ) ;
					return ;
				case '\\' : // escaped character
					char n = step() ;
					switch ( n ) {
						case 'n' : value ~= "\n" ; break ;
						case 't' : value ~= "\t" ; break ;
						case 'r' : value ~= "\r" ; break ;
						default : value ~= n ; break ;
					}
					break ;
				default : // normal character
					value ~= c ;
					break ;
			}
		}
		error( code , curr , "Unterminated string literal" ) ;
	}

	private void addToken(T)( TokenType t , T v ) {
		tokens ~= token( curr , code[start..curr] , t , v ) ;
	}
	private void addToken( TokenType t ) {
		tokens ~= token( curr , code[start..curr] , t ) ;
	}
	private bool atEnd() { return curr >= code.length ; }
	private char step() { curr ++ ; return code[ curr - 1 ] ; }
	private char peek() { return atEnd ? '\0' : code[curr] ; }
	private bool match( char c ) {
		if ( atEnd ) return false ;
		if ( code[curr] != c ) return false ;
		curr ++ ;
		return true ;
	}
	private bool isDigit( char c ) { return "0123456789".canFind( c ) ; }
	private bool matches( string s , string r ) {
		auto m = s.matchFirst( r ) ;
		return m.length ? m.front.length == s.length : false ;
	}
}

M stf.d => stf.d +33 -0
@@ 0,0 1,33 @@
import std.stdio ;
import std.file  ;

import lexer ;
import err   ;

int main( string[] args ) {
	if ( args.length > 1 ) {
		// concatenate and evaluate files
		foreach ( a ; args[1..$] ) {
			try a.readText().run() ;
			catch (Throwable) {
				stderr.writeln( "file '" , a ,
				                "' does not exist or could not be read" ) ;
				return 1 ;
			}
		}
		return 0 ;
	}

	// repl
	while ( true ) {
		write( "> " ) ;
		run( readln() ) ;
		hadErr = false ;
	}
}

int run( string code ) {
	auto tokens = code.lex() ;
	foreach ( t ; tokens ) t.writeln() ;
	return 0 ;
}

A stf.o => stf.o +0 -0