Writing Compilers and Interpreters: A Software Engineering Approach