Skip to content

Commit

Permalink
feat: adding collumn info during define replacements
Browse files Browse the repository at this point in the history
  • Loading branch information
deleterium committed Mar 8, 2024
1 parent beb21b7 commit a5097dd
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 71 deletions.
26 changes: 13 additions & 13 deletions src/preprocessor/__tests__/preprocessor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,68 +33,68 @@ function createFakeProgram (sourceCode: string) {
describe('preprocessor right tests', () => {
it('#define test', () => {
const code = createFakeProgram('#define MAX 4\nlong a; a=MAX; long MAXimus=2;')
const result = '\nlong a; a=4; long MAXimus=2;'
const result = '\nlong a; a=4#13#; long MAXimus=2;'
// @ts-expect-error TS2345
expect(preprocessor(code)).toBe(result)
})
it('#define test', () => {
const code = createFakeProgram('#define MAX 4\n long a; a=MAX;\n #define MAX 2\n long MAXimus=MAX;')
const result = '\n long a; a=4;\n\n long MAXimus=2;'
const result = '\n long a; a=4#14#;\n\n long MAXimus=2#17#;'
// @ts-expect-error TS2345
expect(preprocessor(code)).toBe(result)
})
it('#define test', () => {
const code = createFakeProgram('#define MAX 4\n long a; a=MAX;\n #define MAX \n long MAXimus=MAX;')
const result = '\n long a; a=4;\n\n long MAXimus=;'
const result = '\n long a; a=4#14#;\n\n long MAXimus=#17#;'
// @ts-expect-error TS2345
expect(preprocessor(code)).toBe(result)
})
it('#define test', () => {
const code = createFakeProgram('#define 444 4\nlong a; a=444;\n #undef 444\nlong MAXimus=444;')
const result = '\nlong a; a=4;\n\nlong MAXimus=444;'
const result = '\nlong a; a=4#13#;\n\nlong MAXimus=444;'
// @ts-expect-error TS2345
expect(preprocessor(code)).toBe(result)
})
it('#define test', () => {
const code = createFakeProgram('#define MAX 4\n#define MAX1 (MAX + 1)\n long a; if (a > MAX1) a++;')
const result = '\n\n long a; if (a > (4 + 1)) a++;'
const result = '\n\n long a; if (a > (4#4# + 1)#21#) a++;'
// @ts-expect-error TS2345
expect(preprocessor(code)).toBe(result)
})
it('#define test', () => {
const code = createFakeProgram('#define MAX 4\n#define MAX1 (MAX + 1)\n#undef MAX\n long a; if (a > MAX1) a++;')
const result = '\n\n\n long a; if (a > (4 + 1)) a++;'
const result = '\n\n\n long a; if (a > (4#4# + 1)#21#) a++;'
// @ts-expect-error TS2345
expect(preprocessor(code)).toBe(result)
})

it('#define macro (simple)', () => {
const code = createFakeProgram('#define DEF(top, bottom) (((top) << 8) | (bottom))\nlong a,b,c;\na = DEF(b, c);\n')
const result = '\nlong a,b,c;\na = (((b) << 8) | (c));\n'
const result = '\nlong a,b,c;\na = (((b) << 8) | (c))#13#;\n'
// @ts-expect-error TS2345
expect(preprocessor(code)).toBe(result)
})
it('#define macro (empty argument)', () => {
const code = createFakeProgram('#define DEF() (Get_Current_Timestamp( ) >> 32)\nlong a;\na = DEF() ;\n')
const result = '\nlong a;\na = (Get_Current_Timestamp( ) >> 32) ;\n'
const result = '\nlong a;\na = (Get_Current_Timestamp( ) >> 32)#9# ;\n'
// @ts-expect-error TS2345
expect(preprocessor(code)).toBe(result)
})
it('#define macro (complex)', () => {
const code = createFakeProgram('#define DEF(top, bottom) (((top) << 8) | (bottom))\nlong a,b,c;\na = DEF(mdv(a,b, c), c) + DEF(22, 25);\n')
const result = '\nlong a,b,c;\na = (((mdv(a,b, c)) << 8) | (c)) + (((22) << 8) | (25));\n'
const result = '\nlong a,b,c;\na = (((mdv(a,b, c)) << 8) | (c))#23# + (((22) << 8) | (25))#37#;\n'
// @ts-expect-error TS2345
expect(preprocessor(code)).toBe(result)
})
it('#define macro (with define constant)', () => {
const code = createFakeProgram('#define ONE n1\n#define DEF(top, bottom) (((top) << 8) | (bottom + ONE))\n#undef ONE\nlong a,b,c;\na = DEF(mdv(a,b, c), c);\n')
const result = '\n\n\nlong a,b,c;\na = (((mdv(a,b, c)) << 8) | (c + n1));\n'
const result = '\n\n\nlong a,b,c;\na = (((mdv(a,b, c)) << 8) | (c + n1#29#))#23#;\n'
// @ts-expect-error TS2345
expect(preprocessor(code)).toBe(result)
})
it('#define replace multiple times same line', () => {
const code = createFakeProgram('long a, b, c; if (a == NULL && b == NULL) c++;')
const result = 'long a, b, c; if (a == (void *)(0) && b == (void *)(0)) c++;'
const result = 'long a, b, c; if (a == (void *)(0)#27# && b == (void *)(0)#40#) c++;'
// @ts-expect-error TS2345
expect(preprocessor(code)).toBe(result)
})
Expand Down Expand Up @@ -166,13 +166,13 @@ describe('preprocessor right tests', () => {
})
it('#define, #undef at line not active test', () => {
const code = createFakeProgram('#ifdef debug\n#define A1 44\n#ifndef impossible\na++;\n#endif\n#define A2\n#undef true\n#endif\nlong a; a=A1+A2+true;')
const result = '\n\n\n\n\n\n\n\nlong a; a=A1+A2+1;'
const result = '\n\n\n\n\n\n\n\nlong a; a=A1+A2+1#20#;'
// @ts-expect-error TS2345
expect(preprocessor(code)).toBe(result)
})
it('#define, #undef at line active test', () => {
const code = createFakeProgram('#define debug \n#ifdef debug\n#define A1 44\n#define A2\n#undef true\n#endif\nlong a; a=A1+A2+true;')
const result = '\n\n\n\n\n\nlong a; a=44++true;'
const result = '\n\n\n\n\n\nlong a; a=44#12#+#15#+true;'
// @ts-expect-error TS2345
expect(preprocessor(code)).toBe(result)
})
Expand Down
157 changes: 103 additions & 54 deletions src/preprocessor/preprocessor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BitField, StringStream, parseDecimalNumber } from '../repository/repository'
import { BitField, STREAM_PAIR, StringStream, parseDecimalNumber } from '../repository/repository'
import { CONTRACT } from '../typings/contractTypes'

type IF_INFO = {
Expand Down Expand Up @@ -60,7 +60,7 @@ export default function preprocessor (Program: CONTRACT) : string {
return retLines.join('\n')
}

/** Moves escaped lines (ended with \ ) to the first line */
/** Moves escaped lines (ended with \ ) to the first line, analizes and return the preprocessor token line */
function prepare () : PREP_LINE[] {
const retTokens : PREP_LINE[] = []
let afterEscapedLine = false
Expand Down Expand Up @@ -112,6 +112,7 @@ export default function preprocessor (Program: CONTRACT) : string {
return retTokens
}

/** Parse the line finding a directive and extracting the fields */
function parseDirective (codeLine: string, line: number) : string[] {
const Stream = new StringStream(codeLine)
const firstField = findFirstField()
Expand Down Expand Up @@ -193,79 +194,127 @@ export default function preprocessor (Program: CONTRACT) : string {
}
}

/** OK... We parse the line backwards to find the string to be replaced.
* This way we can maintain the right collumn index for the remaining string
* in the most of cases. Collumns still wrong if there was a escaped line or
* a replacement inside other replacement.
*/
function replaceDefines (codeline: string, line: string) : string {
let retLine = codeline
preprocessorReplacements.forEach((Replacement) => {
if (Replacement.macro) {
while (true) {
const foundCname = Replacement.regex.exec(retLine)
if (foundCname === null) {
return
let current : STREAM_PAIR
let state = 0
let wordEndIndex = 0
let word : string
let Replacement: REPLACEMENTS | undefined
while (true) {
let wasReplaced = false
const Stream = new StringStream(retLine)
Stream.setBack()
while (true) {
current = Stream.back()
switch (state) {
case 0:
if (BitField.typeTable[current.code] & BitField.isWord || BitField.typeTable[current.code] & BitField.isDigit) {
wordEndIndex = Stream.col
state = 1
}
if (current.char) {
continue
}
let replaced = Replacement.macro
const currExtArgs = extractArgs(retLine, foundCname.index + 1, line)
const origExtArgs = extractArgs(Replacement.value, 0, line)
if (origExtArgs.argArray.length !== currExtArgs.argArray.length) {
throw new Error(Program.Context.formatError(line,
`Wrong number of arguments for macro '${Replacement.cname}'. ` +
`Expected ${origExtArgs.argArray.length}, got ${currExtArgs.argArray.length}.`))
break
case 1:
if (BitField.typeTable[current.code] & BitField.isWord || BitField.typeTable[current.code] & BitField.isDigit) {
continue
}
for (let currArg = 0; currArg < origExtArgs.argArray.length; currArg++) {
replaced = replaced.replace(new RegExp(`\\b${origExtArgs.argArray[currArg]}\\b`, 'g'), currExtArgs.argArray[currArg])
word = retLine.slice(Stream.col, wordEndIndex)
Replacement = preprocessorReplacements.find(item => item.cname === word)
if (Replacement) {
retLine = executeReplacement(Replacement, retLine, Stream.col, wordEndIndex, line)
wasReplaced = true
}
retLine = retLine.slice(0, foundCname.index) + replaced + retLine.slice(currExtArgs.endPosition)
state = 0
}
if (wasReplaced || current.char === undefined) {
// We need to reload the string OR end of parse
break
}
}
retLine = retLine.replace(Replacement.regex, Replacement.value)
})
if (Stream.index < 0) {
break
}
}
return retLine
}

function executeReplacement (Replacement: REPLACEMENTS, code: string, startIndex: number, endIndex: number, line: string) {
if (Replacement.macro === undefined) {
return code.slice(0, startIndex) + Replacement.value + '#' + endIndex + '#' + code.slice(endIndex)
}
let replaced = Replacement.macro
const currExtArgs = extractArgs(code, startIndex + 1, line)
const origExtArgs = extractArgs(Replacement.value, 0, line)
if (origExtArgs.argArray.length !== currExtArgs.argArray.length) {
throw new Error(Program.Context.formatError(line,
`Wrong number of arguments for macro '${Replacement.cname}'. ` +
`Expected ${origExtArgs.argArray.length}, got ${currExtArgs.argArray.length}.`))
}
for (let currArg = 0; currArg < origExtArgs.argArray.length; currArg++) {
replaced = replaced.replace(new RegExp(`\\b${origExtArgs.argArray[currArg]}\\b`, 'g'), currExtArgs.argArray[currArg])
}
return code.slice(0, startIndex) + replaced + `#${currExtArgs.endPosition}#` + code.slice(currExtArgs.endPosition)
}

function extractArgs (fnArgString: string, needle: number, line: string): { argArray: string[], endPosition: number} {
const argArray : string [] = []
let currArg: string = ''
let pLevel = 0
let started = false
for (;;needle++) {
const currChar = fnArgString.charAt(needle)
if (currChar === '') {
const Stream = new StringStream(fnArgString)
Stream.index = needle - 1
let stage = 0
while (true) {
const current = Stream.advance()
if (current.char === undefined) {
throw new Error(Program.Context.formatError(line, 'Unmatched parenthesis or unexpected end of line.'))
}
if (currChar === '(') {
pLevel++
if (pLevel === 1) {
started = true
continue
switch (stage) {
case 0:
if (current.char === '(') {
pLevel = 1
stage = 1
}
}
if (!started) continue
if (currChar === ')') {
pLevel--
if (pLevel === 0) {
const endArg = currArg.trim()
if (endArg.length === 0 && argArray.length !== 0) {
throw new Error(Program.Context.formatError(line, 'Found empty argument on macro declaration.'))
}
if (endArg.length !== 0) {
argArray.push(currArg.trim())
}
continue
case 1:
if (current.char === '(') {
pLevel++
break
}
}
if (currChar === ',' && pLevel === 1) {
const newArg = currArg.trim()
if (newArg.length === 0) {
throw new Error(Program.Context.formatError(line, 'Found empty argument on macro declaration.'))
if (current.char === ')') {
pLevel--
if (pLevel === 0) {
const endArg = currArg.trim()
if (endArg.length === 0 && argArray.length !== 0) {
throw new Error(Program.Context.formatError(line, 'Found empty argument on macro declaration.'))
}
if (endArg.length !== 0) {
argArray.push(currArg.trim())
}
return {
argArray,
endPosition: Stream.index + 1
}
}
}
if (current.char === ',' && pLevel === 1) {
const newArg = currArg.trim()
if (newArg.length === 0) {
throw new Error(Program.Context.formatError(line, 'Found empty argument on macro declaration.'))
}
argArray.push(currArg.trim())
currArg = ''
continue
}
argArray.push(currArg.trim())
currArg = ''
continue
}
currArg += currChar
}
return {
argArray,
endPosition: needle + 1
currArg += current.char
}
}

Expand Down
16 changes: 12 additions & 4 deletions src/tokenizer/tokenizerV3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,15 +251,23 @@ export default function tokenizer (Program: CONTRACT, inputSourceCode: string):
}
}

function stateReadMacro () : PRE_TOKEN {
function stateReadMacro () : undefined {
const line = Stream.lineCol
const tokenStartIndex = Stream.index
let current: STREAM_PAIR
do {
while (true) {
current = Stream.advance()
} while (current.char && current.code !== '\n'.charCodeAt(0))
if (BitField.typeTable[current.code] & BitField.isDigit) {
continue
}
if (current.char === '#') {
break
}
throw new Error(Program.Context.formatError(line, 'Wrong use of preprocessor directive'))
}
const tokenValue = inputSourceCode.slice(tokenStartIndex + 1, Stream.index)
return { type: 'Macro', precedence: 0, value: tokenValue, line }
Stream.col = parseInt(tokenValue)
return undefined
}

function stateReadString () : PRE_TOKEN {
Expand Down

0 comments on commit a5097dd

Please sign in to comment.