shithub: libmujs

Download patch

ref: bb2279e43d905ca8383d5dec5eee57feec5462b1
parent: c816b1193efeabbeeddcf8c5324e92b895956dd5
author: Sebastian Rasmussen <sebras@gmail.com>
date: Mon Dec 23 14:47:12 EST 2013

Add lexer.

--- /dev/null
+++ b/Makefile
@@ -1,0 +1,25 @@
+SRCS := $(wildcard js*.c)
+HDRS := $(wildcard js*.h)
+OBJS := $(SRCS:%.c=build/%.o)
+
+CFLAGS = -Wall -g
+
+default: build js
+
+build:
+	mkdir -p build
+
+build/%.o : %.c $(HDRS)
+	$(CC) -c $< -o $@ $(CFLAGS)
+
+build/libjs.a: $(OBJS)
+	ar cru $@ $^
+
+js: build/main.o build/libjs.a
+	$(CC) -o $@ $^ -lm
+
+tags:
+	ctags *.c *.h
+
+clean:
+	rm -f build/* js
--- /dev/null
+++ b/js-lex.c
@@ -1,0 +1,468 @@
+#include "js.h"
+
+#define nelem(a) (sizeof (a) / sizeof (a)[0])
+
+struct {
+	const char *string;
+	js_Token token;
+} keywords[] = {
+	{"abstract", JS_ABSTRACT},
+	{"boolean", JS_BOOLEAN},
+	{"break", JS_BREAK},
+	{"byte", JS_BYTE},
+	{"case", JS_CASE},
+	{"catch", JS_CATCH},
+	{"char", JS_CHAR},
+	{"class", JS_CLASS},
+	{"const", JS_CONST},
+	{"continue", JS_CONTINUE},
+	{"debugger", JS_DEBUGGER},
+	{"default", JS_DEFAULT},
+	{"delete", JS_DELETE},
+	{"do", JS_DO},
+	{"double", JS_DOUBLE},
+	{"else", JS_ELSE},
+	{"enum", JS_ENUM},
+	{"export", JS_EXPORT},
+	{"extends", JS_EXTENDS},
+	{"false", JS_FALSE},
+	{"final", JS_FINAL},
+	{"finally", JS_FINALLY},
+	{"float", JS_FLOAT},
+	{"for", JS_FOR},
+	{"function", JS_FUNCTION},
+	{"goto", JS_GOTO},
+	{"if", JS_IF},
+	{"implements", JS_IMPLEMENTS},
+	{"import", JS_IMPORT},
+	{"in", JS_IN},
+	{"instanceof", JS_INSTANCEOF},
+	{"int", JS_INT},
+	{"interface", JS_INTERFACE},
+	{"long", JS_LONG},
+	{"native", JS_NATIVE},
+	{"new", JS_NEW},
+	{"null", JS_NULL},
+	{"package", JS_PACKAGE},
+	{"private", JS_PRIVATE},
+	{"protected", JS_PROTECTED},
+	{"public", JS_PUBLIC},
+	{"return", JS_RETURN},
+	{"short", JS_SHORT},
+	{"static", JS_STATIC},
+	{"super", JS_SUPER},
+	{"switch", JS_SWITCH},
+	{"synchronized", JS_SYNCHRONIZED},
+	{"this", JS_THIS},
+	{"throw", JS_THROW},
+	{"throws", JS_THROWS},
+	{"transient", JS_TRANSIENT},
+	{"true", JS_TRUE},
+	{"try", JS_TRY},
+	{"typeof", JS_TYPEOF},
+	{"var", JS_VAR},
+	{"void", JS_VOID},
+	{"volatile", JS_VOLATILE},
+	{"while", JS_WHILE},
+	{"with", JS_WITH},
+};
+
+const char *tokenstrings[] = {
+	"ERROR", "EOF", "(identifier)", "null", "true", "false", "(number)",
+	"(string)", "(regexp)", "\\n", "{", "}", "(", ")", "[", "]", ".", ";",
+	",", "<", ">", "<=", ">=", "==", "!=", "===", "!==", "+", "-", "*",
+	"%", "++", "--", "<<", ">>", ">>>", "&", "|", "^", "!", "~", "&&",
+	"||", "?", ":", "=", "+=", "-=", "*=", "%=", "<<=", ">>=", ">>>=",
+	"&=", "|=", "^=", "/", "/=", "break", "case", "catch", "continue",
+	"default", "delete", "do", "else", "finally", "for", "function", "if",
+	"in", "instanceof", "new", "return", "switch", "this", "throw", "try",
+	"typeof", "var", "void", "while", "with", "abstract", "boolean",
+	"byte", "char", "class", "const", "debugger", "double", "enum",
+	"export", "extends", "final", "float", "goto", "implements", "import",
+	"int", "interface", "long", "native", "package", "private",
+	"protected", "public", "short", "static", "super", "synchronized",
+	"throws", "transient", "volatile",
+};
+
+const char *js_tokentostring(js_Token t)
+{
+	return tokenstrings[t];
+}
+
+static inline js_Token findkeyword(const char *s)
+{
+	int m, l, r;
+	int c;
+
+	l = 0;
+	r = nelem(keywords) - 1;
+
+	while (l <= r) {
+		m = (l + r) >> 1;
+		c = strcmp(s, keywords[m].string);
+		if (c < 0)
+			r = m - 1;
+		else if (c > 0)
+			l = m + 1;
+		else
+			return keywords[m].token;
+	}
+
+	return JS_IDENTIFIER;
+}
+
+static inline int iswhite(int c)
+{
+	return c == 0x9 || c == 0xb || c == 0xc || c == 0x20 || c == 0xa0;
+}
+
+static inline int isnewline(c)
+{
+	return c == 0xa || c == 0xd || c == 0x2028 || c == 0x2029;
+}
+
+#define GETC() *(*sp)++
+#define UNGETC() (*sp)--
+#define LOOK(x) (**sp == x ? *(*sp)++ : 0)
+
+static inline void lexlinecomment(const char **sp)
+{
+	int c = GETC();
+	while (!isnewline(c))
+		c = GETC();
+	UNGETC();
+}
+
+static inline int lexcomment(const char **sp)
+{
+	while (1) {
+		int c = GETC();
+		if (c == '*') {
+			while (c == '*')
+				c = GETC();
+			if (c == '/')
+				return 0;
+		} else if (c == 0) {
+			return -1;
+		}
+	}
+}
+
+static inline int isidentifierstart(int c)
+{
+	return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '$' || c == '_';
+}
+
+static inline int isidentifierpart(int c)
+{
+	return (c >= '0' && c <= '9') || isidentifierstart(c);
+}
+
+static inline int isdec(int c)
+{
+	return (c >= '0' && c <= '9');
+}
+
+static inline int ishex(int c)
+{
+	return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
+}
+
+static inline int tohex(int c)
+{
+	if (c >= '0' && c <= '9')
+		return c - '0';
+	if (c >= 'a' && c <= 'f')
+		return c - 'a' + 0xa;
+	if (c >= 'A' && c <= 'F')
+		return c - 'A' + 0xa;
+	return 0;
+}
+
+static inline js_Token lexhex(const char **sp, double *yynumber)
+{
+	int c = GETC();
+	double n = 0;
+
+	if (!ishex(c))
+		return JS_ERROR;
+
+	do {
+		n = n * 16 + tohex(c);
+		c = GETC();
+	} while (ishex(c));
+
+	UNGETC();
+	*yynumber = n;
+
+	return JS_NUMBER;
+}
+
+static inline double lexinteger(const char **sp)
+{
+	int c = GETC();
+	double n = 0;
+
+	while (isdec(c)) {
+		n = n * 10 + (c - '0');
+		c = GETC();
+	}
+
+	UNGETC();
+
+	return n;
+}
+
+static inline double lexfraction(const char **sp)
+{
+	int c = GETC();
+	double n = 0;
+	double d = 1;
+
+	while (isdec(c)) {
+		n = n * 10 + (c - '0');
+		d = d * 10;
+		c = GETC();
+	}
+
+	UNGETC();
+
+	return n / d;
+}
+
+static inline js_Token lexnumber(int c, const char **sp, double *yynumber)
+{
+	double i, f, e;
+
+	if (c == '0' && (LOOK('x') || LOOK('X')))
+		return lexhex(sp, yynumber);
+
+	UNGETC();
+
+	i = lexinteger(sp);
+
+	f = 0;
+	if (LOOK('.'))
+		f = lexfraction(sp);
+
+	e = 0;
+	if (LOOK('e') || LOOK('E')) {
+		if (LOOK('-'))
+			e = -lexinteger(sp);
+		else if (LOOK('+'))
+			e = lexinteger(sp);
+		else
+			e = lexinteger(sp);
+	}
+
+	*yynumber = (i + f) * pow(10, e);
+
+	return JS_NUMBER;
+}
+
+static inline int lexescape(const char **sp)
+{
+	int c = GETC();
+	int x, y, z, w;
+
+	switch (c) {
+	case '0': return 0;
+	case 'u':
+		x = tohex(GETC());
+		y = tohex(GETC());
+		z = tohex(GETC());
+		w = tohex(GETC());
+		return (x << 12) | (y << 8) | (z << 4) | w;
+	case 'x':
+		x = tohex(GETC());
+		y = tohex(GETC());
+		return (x << 4) | y;
+	case '\'': return '\'';
+	case '"': return '"';
+	case '\\': return '\\';
+	case 'b': return '\b';
+	case 'f': return '\f';
+	case 'n': return '\n';
+	case 'r': return '\r';
+	case 't': return '\t';
+	case 'v': return '\v';
+	default: return c;
+	}
+}
+
+static inline js_Token lexstring(int q, const char **sp, char *yytext, size_t yylen)
+{
+	char *p = yytext;
+	int c = GETC();
+
+	while (c != q) {
+		if (c == 0 || isnewline(c))
+			return JS_ERROR;
+
+		if (c == '\\')
+			c = lexescape(sp);
+
+		if (p - yytext >= yylen)
+			return JS_ERROR;
+		*p++ = c;
+		c = GETC();
+	}
+
+	*p = 0;
+
+	return JS_STRING;
+}
+
+js_Token js_lex(js_State *J, const char **sp, char *yytext, size_t yylen, double *yynumber)
+{
+	int c = GETC();
+
+	while (c) {
+		while (iswhite(c))
+			c = GETC();
+
+		if (isnewline(c))
+			return JS_NEWLINE;
+
+		if (c == '/') {
+			c = GETC();
+			if (c == '/') {
+				lexlinecomment(sp);
+			} else if (c == '*') {
+				if (lexcomment(sp))
+					return JS_ERROR;
+			} else if (c == '=') {
+				return JS_SLASH_EQ;
+			} else {
+				UNGETC();
+				return JS_SLASH;
+			}
+		}
+
+		if (isidentifierstart(c)) {
+			char *p = yytext;
+
+			do {
+				if (p - yytext >= yylen)
+					return JS_ERROR;
+				*p++ = c;
+				c = GETC();
+			} while (isidentifierpart(c));
+
+			UNGETC();
+			*p = 0;
+
+			return findkeyword(yytext);
+		}
+
+		if ((c >= '0' && c <= '9') || c == '.')
+			return lexnumber(c, sp, yynumber);
+
+		if (c == '\'' || c == '"')
+			return lexstring(c, sp, yytext, yylen);
+
+		switch (c) {
+		case '{': return JS_LCURLY;
+		case '}': return JS_RCURLY;
+		case '(': return JS_LPAREN;
+		case ')': return JS_RPAREN;
+		case '[': return JS_LSQUARE;
+		case ']': return JS_RSQUARE;
+		case '.': return JS_PERIOD;
+		case ';': return JS_SEMICOLON;
+		case ',': return JS_COMMA;
+
+		case '<':
+			if (LOOK('<')) {
+				if (LOOK('='))
+					return JS_LT_LT_EQ;
+				return JS_LT_LT;
+			}
+			if (LOOK('='))
+				return JS_LT_EQ;
+			return JS_LT;
+
+		case '>':
+			if (LOOK('>')) {
+				if (LOOK('>')) {
+					if (LOOK('='))
+						return JS_GT_GT_GT_EQ;
+					return JS_GT_GT_GT;
+				}
+				if (LOOK('='))
+					return JS_GT_GT_EQ;
+				return JS_GT_GT;
+			}
+			if (LOOK('='))
+				return JS_GT_EQ;
+			return JS_GT;
+
+		case '=':
+			if (LOOK('=')) {
+				if (LOOK('='))
+					return JS_EQ_EQ_EQ;
+				return JS_EQ_EQ;
+			}
+			return JS_EQ;
+
+		case '!':
+			if (LOOK('=')) {
+				if (LOOK('='))
+					return JS_EXCL_EQ_EQ;
+				return JS_EXCL_EQ;
+			}
+			return JS_EXCL;
+
+		case '+':
+			if (LOOK('+'))
+				return JS_PLUS_PLUS;
+			if (LOOK('='))
+				return JS_PLUS_EQ;
+			return JS_PLUS;
+
+		case '-':
+			if (LOOK('-'))
+				return JS_MINUS_MINUS;
+			if (LOOK('='))
+				return JS_MINUS_EQ;
+			return JS_MINUS;
+
+		case '*':
+			if (LOOK('='))
+				return JS_STAR_EQ;
+			return JS_STAR;
+
+		case '%':
+			if (LOOK('='))
+				return JS_PERCENT_EQ;
+			return JS_PERCENT;
+
+		case '&':
+			if (LOOK('&'))
+				return JS_AND_AND;
+			if (LOOK('='))
+				return JS_AND_EQ;
+			return JS_AND;
+
+		case '|':
+			if (LOOK('|'))
+				return JS_BAR_BAR;
+			if (LOOK('='))
+				return JS_BAR_EQ;
+			return JS_BAR;
+
+		case '^':
+			if (LOOK('='))
+				return JS_HAT_EQ;
+			return JS_HAT;
+
+		case '~': return JS_TILDE;
+		case '?': return JS_QUESTION;
+		case ':': return JS_COLON;
+		}
+
+		c = GETC();
+	}
+
+	return JS_EOF;
+}
--- /dev/null
+++ b/js-load.c
@@ -1,0 +1,59 @@
+#include "js.h"
+
+int js_loadstring(js_State *J, const char *source)
+{
+	char yytext[512];
+	double yynumber;
+	js_Token t;
+
+	do {
+		t = js_lex(J, &source, yytext, sizeof yytext, &yynumber);
+
+		if (t == JS_NUMBER)
+			printf("%g\n", yynumber);
+		else if (t == JS_IDENTIFIER)
+			printf("id:%s\n", yytext);
+		else if (t == JS_STRING)
+			printf("'%s'\n", yytext);
+		else
+			printf("%s\n", js_tokentostring(t));
+	} while (t != JS_EOF && t != JS_ERROR);
+
+	return 0;
+}
+
+int js_loadfile(js_State *J, const char *filename)
+{
+	FILE *f;
+	char *s;
+	int n, t;
+
+	f = fopen(filename, "r");
+	if (!f)
+		return js_error(J, "cannot open file: '%s'", filename);
+
+	fseek(f, 0, SEEK_END);
+	n = ftell(f);
+	fseek(f, 0, SEEK_SET);
+
+	s = malloc(n + 1); /* add space for string terminator */
+	if (!s) {
+		fclose(f);
+		return js_error(J, "cannot allocate storage for file contents: '%s'", filename);
+	}
+
+	t = fread(s, 1, n, f);
+	if (t != n) {
+		free(s);
+		fclose(f);
+		return js_error(J, "cannot read data from file: '%s'", filename);
+	}
+
+	s[n] = 0; /* zero-terminate string containing file data */
+
+	t = js_loadstring(J, s);
+
+	free(s);
+	fclose(f);
+	return t;
+}
--- /dev/null
+++ b/js-state.c
@@ -1,0 +1,28 @@
+#include "js.h"
+
+js_State *js_newstate(void)
+{
+	js_State *J = malloc(sizeof *J);
+	memset(J, 0, sizeof(*J));
+	return J;
+}
+
+void js_close(js_State *J)
+{
+	free(J);
+}
+
+int js_error(js_State *J, const char *fmt, ...)
+{
+	va_list ap;
+
+	fprintf(stderr, "error: ");
+
+	va_start(ap, fmt);
+	vfprintf(stderr, fmt, ap);
+	va_end(ap);
+
+	fprintf(stderr, "\n");
+
+	return 0;
+}
--- /dev/null
+++ b/js.h
@@ -1,0 +1,162 @@
+#ifndef js_h
+#define js_h
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <string.h>
+#include <math.h>
+
+typedef struct js_State js_State;
+
+typedef int (*js_CFunction)(js_State *J);
+
+js_State *js_newstate(void);
+void js_close(js_State *J);
+
+int js_error(js_State *J, const char *fmt, ...);
+
+int js_loadstring(js_State *J, const char *s);
+int js_loadfile(js_State *J, const char *filename);
+
+/* private */
+
+typedef enum js_Token js_Token;
+
+enum js_Token
+{
+	JS_ERROR,
+	JS_EOF,
+
+	JS_IDENTIFIER,
+	JS_NULL,
+	JS_TRUE,
+	JS_FALSE,
+	JS_NUMBER,
+	JS_STRING,
+	JS_REGEXP,
+	JS_NEWLINE,
+
+	/* punctuators */
+	JS_LCURLY,
+	JS_RCURLY,
+	JS_LPAREN,
+	JS_RPAREN,
+	JS_LSQUARE,
+	JS_RSQUARE,
+	JS_PERIOD,
+	JS_SEMICOLON,
+	JS_COMMA,
+	JS_LT,
+	JS_GT,
+	JS_LT_EQ,
+	JS_GT_EQ,
+	JS_EQ_EQ,
+	JS_EXCL_EQ,
+	JS_EQ_EQ_EQ,
+	JS_EXCL_EQ_EQ,
+	JS_PLUS,
+	JS_MINUS,
+	JS_STAR,
+	JS_PERCENT,
+	JS_PLUS_PLUS,
+	JS_MINUS_MINUS,
+	JS_LT_LT,
+	JS_GT_GT,
+	JS_GT_GT_GT,
+	JS_AND,
+	JS_BAR,
+	JS_HAT,
+	JS_EXCL,
+	JS_TILDE,
+	JS_AND_AND,
+	JS_BAR_BAR,
+	JS_QUESTION,
+	JS_COLON,
+	JS_EQ,
+	JS_PLUS_EQ,
+	JS_MINUS_EQ,
+	JS_STAR_EQ,
+	JS_PERCENT_EQ,
+	JS_LT_LT_EQ,
+	JS_GT_GT_EQ,
+	JS_GT_GT_GT_EQ,
+	JS_AND_EQ,
+	JS_BAR_EQ,
+	JS_HAT_EQ,
+	JS_SLASH,
+	JS_SLASH_EQ,
+
+	/* keywords */
+	JS_BREAK,
+	JS_CASE,
+	JS_CATCH,
+	JS_CONTINUE,
+	JS_DEFAULT,
+	JS_DELETE,
+	JS_DO,
+	JS_ELSE,
+	JS_FINALLY,
+	JS_FOR,
+	JS_FUNCTION,
+	JS_IF,
+	JS_IN,
+	JS_INSTANCEOF,
+	JS_NEW,
+	JS_RETURN,
+	JS_SWITCH,
+	JS_THIS,
+	JS_THROW,
+	JS_TRY,
+	JS_TYPEOF,
+	JS_VAR,
+	JS_VOID,
+	JS_WHILE,
+	JS_WITH,
+
+	/* future reserved words */
+	JS_ABSTRACT,
+	JS_BOOLEAN,
+	JS_BYTE,
+	JS_CHAR,
+	JS_CLASS,
+	JS_CONST,
+	JS_DEBUGGER,
+	JS_DOUBLE,
+	JS_ENUM,
+	JS_EXPORT,
+	JS_EXTENDS,
+	JS_FINAL,
+	JS_FLOAT,
+	JS_GOTO,
+	JS_IMPLEMENTS,
+	JS_IMPORT,
+	JS_INT,
+	JS_INTERFACE,
+	JS_LONG,
+	JS_NATIVE,
+	JS_PACKAGE,
+	JS_PRIVATE,
+	JS_PROTECTED,
+	JS_PUBLIC,
+	JS_SHORT,
+	JS_STATIC,
+	JS_SUPER,
+	JS_SYNCHRONIZED,
+	JS_THROWS,
+	JS_TRANSIENT,
+	JS_VOLATILE,
+
+};
+
+struct js_State
+{
+	char yytext[512];
+	int top;
+};
+
+js_Token js_lex(js_State *J, const char **sp, char *yytext, size_t yylen, double *yynumber);
+const char *js_tokentostring(js_Token t);
+
+#endif
--- /dev/null
+++ b/main.c
@@ -1,0 +1,19 @@
+#include "js.h"
+
+int
+main(int argc, char **argv)
+{
+	js_State *J;
+	int i;
+
+	J = js_newstate();
+
+	for (i = 1; i < argc; i++) {
+		js_loadfile(J, argv[1]);
+		// js_run(J);
+	}
+
+	js_close(J);
+
+	return 0;
+}