| 1 | -- Prosody IM |
| 2 | -- Copyright (C) 2008-2010 Matthew Wild |
| 3 | -- Copyright (C) 2008-2010 Waqas Hussain |
| 4 | -- |
| 5 | -- This project is MIT/X11 licensed. Please see the |
| 6 | -- COPYING file in the source package for more information. |
| 7 | -- |
| 8 | |
| 9 | local string_byte, string_char = string.byte, string.char; |
| 10 | local t_concat, t_insert = table.concat, table.insert; |
| 11 | local type, tonumber, tostring = type, tonumber, tostring; |
| 12 | |
| 13 | local file = nil; |
| 14 | local last = nil; |
| 15 | local line = 1; |
| 16 | local function read(expected) |
| 17 | local ch; |
| 18 | if last then |
| 19 | ch = last; last = nil; |
| 20 | else |
| 21 | ch = file:read(1); |
| 22 | if ch == "\n" then line = line + 1; end |
| 23 | end |
| 24 | if expected and ch ~= expected then error("expected: "..expected.."; got: "..(ch or "nil").." on line "..line); end |
| 25 | return ch; |
| 26 | end |
| 27 | local function pushback(ch) |
| 28 | if last then error(); end |
| 29 | last = ch; |
| 30 | end |
| 31 | local function peek() |
| 32 | if not last then last = read(); end |
| 33 | return last; |
| 34 | end |
| 35 | |
| 36 | local _A, _a, _Z, _z, _0, _9, __, _at, _space, _minus = string_byte("AaZz09@_ -", 1, 10); |
| 37 | local function isLowerAlpha(ch) |
| 38 | ch = string_byte(ch) or 0; |
| 39 | return (ch >= _a and ch <= _z); |
| 40 | end |
| 41 | local function isNumeric(ch) |
| 42 | ch = string_byte(ch) or 0; |
| 43 | return (ch >= _0 and ch <= _9) or ch == _minus; |
| 44 | end |
| 45 | local function isAtom(ch) |
| 46 | ch = string_byte(ch) or 0; |
| 47 | return (ch >= _A and ch <= _Z) or (ch >= _a and ch <= _z) or (ch >= _0 and ch <= _9) or ch == __ or ch == _at; |
| 48 | end |
| 49 | local function isSpace(ch) |
| 50 | ch = string_byte(ch) or "x"; |
| 51 | return ch <= _space; |
| 52 | end |
| 53 | |
| 54 | local escapes = {["\\b"]="\b", ["\\d"]="\d", ["\\e"]="\e", ["\\f"]="\f", ["\\n"]="\n", ["\\r"]="\r", ["\\s"]="\s", ["\\t"]="\t", ["\\v"]="\v", ["\\\""]="\"", ["\\'"]="'", ["\\\\"]="\\"}; |
| 55 | local function readString() |
| 56 | read("\""); -- skip quote |
| 57 | local slash = nil; |
| 58 | local str = {}; |
| 59 | while true do |
| 60 | local ch = read(); |
| 61 | if slash then |
| 62 | slash = slash..ch; |
| 63 | if not escapes[slash] then error("Unknown escape sequence: "..slash); end |
| 64 | str[#str+1] = escapes[slash]; |
| 65 | slash = nil; |
| 66 | elseif ch == "\"" then |
| 67 | break; |
| 68 | elseif ch == "\\" then |
| 69 | slash = ch; |
| 70 | else |
| 71 | str[#str+1] = ch; |
| 72 | end |
| 73 | end |
| 74 | return t_concat(str); |
| 75 | end |
| 76 | local function readAtom1() |
| 77 | local var = { read() }; |
| 78 | while isAtom(peek()) do |
| 79 | var[#var+1] = read(); |
| 80 | end |
| 81 | return t_concat(var); |
| 82 | end |
| 83 | local function readAtom2() |
| 84 | local str = { read("'") }; |
| 85 | local slash = nil; |
| 86 | while true do |
| 87 | local ch = read(); |
| 88 | str[#str+1] = ch; |
| 89 | if ch == "'" and not slash then break; end |
| 90 | end |
| 91 | return t_concat(str); |
| 92 | end |
| 93 | local function readNumber() |
| 94 | local num = { read() }; |
| 95 | while isNumeric(peek()) do |
| 96 | num[#num+1] = read(); |
| 97 | end |
| 98 | return tonumber(t_concat(num)); |
| 99 | end |
| 100 | local readItem = nil; |
| 101 | local function readTuple() |
| 102 | local t = {}; |
| 103 | local s = {}; -- string representation |
| 104 | read(); -- read {, or [, or < |
| 105 | while true do |
| 106 | local item = readItem(); |
| 107 | if not item then break; end |
| 108 | if type(item) ~= "number" or item > 255 then |
| 109 | s = nil; |
| 110 | elseif s then |
| 111 | s[#s+1] = string_char(item); |
| 112 | end |
| 113 | t_insert(t, item); |
| 114 | end |
| 115 | read(); -- read }, or ], or > |
| 116 | if s and #s > 0 then |
| 117 | return t_concat(s) |
| 118 | else |
| 119 | return t |
| 120 | end; |
| 121 | end |
| 122 | local function readBinary() |
| 123 | read("<"); -- read < |
| 124 | -- Discard PIDs |
| 125 | if isNumeric(peek()) then |
| 126 | while peek() ~= ">" do read(); end |
| 127 | read(">"); |
| 128 | return {}; |
| 129 | end |
| 130 | local t = readTuple(); |
| 131 | read(">") -- read > |
| 132 | local ch = peek(); |
| 133 | if type(t) == "string" then |
| 134 | -- binary is a list of integers |
| 135 | return t; |
| 136 | elseif type(t) == "table" then |
| 137 | if t[1] then |
| 138 | -- binary contains string |
| 139 | return t[1]; |
| 140 | else |
| 141 | -- binary is empty |
| 142 | return ""; |
| 143 | end; |
| 144 | else |
| 145 | error(); |
| 146 | end |
| 147 | end |
| 148 | readItem = function() |
| 149 | local ch = peek(); |
| 150 | if ch == nil then return nil end |
| 151 | if ch == "{" or ch == "[" then |
| 152 | return readTuple(); |
| 153 | elseif isLowerAlpha(ch) then |
| 154 | return readAtom1(); |
| 155 | elseif ch == "'" then |
| 156 | return readAtom2(); |
| 157 | elseif isNumeric(ch) then |
| 158 | return readNumber(); |
| 159 | elseif ch == "\"" then |
| 160 | return readString(); |
| 161 | elseif ch == "<" then |
| 162 | return readBinary(); |
| 163 | elseif isSpace(ch) or ch == "," or ch == "|" then |
| 164 | read(); |
| 165 | return readItem(); |
| 166 | else |
| 167 | --print("Unknown char: "..ch); |
| 168 | return nil; |
| 169 | end |
| 170 | end |
| 171 | local function readChunk() |
| 172 | local x = readItem(); |
| 173 | if x then read("."); end |
| 174 | return x; |
| 175 | end |
| 176 | local function readFile(filename) |
| 177 | file = io.open(filename); |
| 178 | if not file then error("File not found: "..filename); os.exit(0); end |
| 179 | return function() |
| 180 | local x = readChunk(); |
| 181 | if not x and peek() then error("Invalid char: "..peek()); end |
| 182 | return x; |
| 183 | end; |
| 184 | end |
| 185 | |
| 186 | module "erlparse" |
| 187 | |
| 188 | function parseFile(file) |
| 189 | return readFile(file); |
| 190 | end |
| 191 | |
| 192 | return _M; |