2
\$\begingroup\$

This is a RegExp exercice from HackerRank. Basically, I have to write a program (I chose JavaScript) that receives a program as an input (string) and tries to identify the programming language used. We're only concerned by 3 languages: C, Java or Python.

function processData(input) {
   console.log(identifyLanguage(input));
} 

var identifyLanguage = function (code) {    
    if (isJava(code)) 
        return 'Java';
    else if (isC(code))
        return 'C';
    else if (isPython(code))
        return 'Python';
    else return 'C';
}

var isC = function(input) {    
    const cPreprocessor = /#(include.*?>|define.*)/g;
    const cStruct = /\b(typedef.*?;|struct.*?(?=\s*?\{))/g;
    const cPointers = /\b\w+\s*->\s*\w+\b/g;
    const cFunctionPointerReturn = /[a-zA-Z]+\s+\*\s+([a-zA-Z_]\w*)\s*\([a-zA-Z]*\s*([a-zA-Z_]\w*)\)\s*\{/g;
    const cFunctionPointerArg = /[a-zA-Z]+\s*\*?\s*([a-zA-Z_]\w*)\(.*[a-zA-Z]+\s*\*\s*([a-zA-Z_]\w*).*\)\s*\{/g;
    return cPreprocessor.test(input) || cStruct.test(input) || cPointers.test(input) ||
           cFunctionPointerArg.test(input) || cFunctionPointerReturn.test(input);
}

var isPython = function (input) {
    const pythonKeywords = /(class|def|if|while|else|for).*:/g;
    const pythonLists = /\[((\d+|("|').+\3),)*(\d+|("|').+\5)\]/g;
    const pythonPrint = /\bprint(\s*("|').+\2|\(.*\))/g;
    const pythonBoolean = /True|False/g;
    return pythonKeywords.test(input) || pythonLists.test(input) || 
           pythonPrint.test(input) || pythonBoolean.test(input);
}

var isJava = function(input) {
    const javaImport = /\bimport\s*.*?;/g;
    const javaClass = /(public|private)?\s*class.*?\{/g;
    return javaImport.test(input) || javaClass.test(input);
}

I can't say I'm satisfied with this, although it passes all the test cases, but It feels kinda ugly, non-DRY and amateurish. What can I do to improve it?

Update

The previous code updated after the suggestions provided in the chosen answer by Joseph :

var langPatterns = {
    C: [
        /#(include.*?>|define.*)/g, //cPreprocessor
        /\b(typedef.*?;|struct.*?(?=\s*?\{))/g, //cStruct
        /\b\w+\s*->\s*\w+\b/g, //cPointers
        /[a-zA-Z]+\s+\*\s+([a-zA-Z_]\w*)\s*\([a-zA-Z]*\s*([a-zA-Z_]\w*)\)\s*\{/g, //cFunctionPointerReturn
        /[a-zA-Z]+\s*\*?\s*([a-zA-Z_]\w*)\(.*[a-zA-Z]+\s*\*\s*([a-zA-Z_]\w*).*\)\s*\{/g //cFunctionPointerArg
    ],
    Java: [
        /\bimport\s*.*?;/g, //javaImport
        /(public|private)?\s*class.*?\{/g //javaClass
    ],
    Python: [
        /(class|def|if|while|else|for).*:/g, //pythonKeywords
        /\[((\d+|("|').+\3),)*(\d+|("|').+\5)\]/g, //pythonLists
        /\bprint(\s*("|').+\2|\(.*\))/g, //pythonPrint
        ///True|False/g //pythonBoolean
    ],
}

var identifyLanguage2 = function (code) {
    return Object.keys(langPatterns).find(lang => 
        langPatterns[lang].some(pattern => 
            pattern.test(code)));
}
\$\endgroup\$
2
  • 1
    \$\begingroup\$ You can add weights to each pattern inside the arrays with higher numbers on really unique tokens and lower on more generic ones, then sum the weights of all detected patterns, sort by weight, and display the top three results. \$\endgroup\$
    – wOxxOm
    Aug 24, 2017 at 20:20
  • 1
    \$\begingroup\$ I find this challenge very interesting. If I were to approach this in the most thorough way I could think of, I would try to define a grammar for each language that encompasses the entire input string, and then test each grammar to see if the string satisfies its syntax and is completely consumed by an attempt to parse its grammar. While exhausting to implement, this would be the most foolproof method that comes to mind. \$\endgroup\$ Sep 9, 2017 at 7:56

1 Answer 1

5
\$\begingroup\$

Instead of ||, you could stuff the patterns in an array and use array.some(). If one of the patterns match the string, it will return immediately with true and skipping the rest of the array.

const patterns = []
patterns.push(pattern1)
patterns.push(pattern2)
patterns.push(pattern3)
return patterns.some(p => string.match(p))

Taking it further, you can use a key-value pair of language name and patterns. Then use Object.keys and array.find to find which key has the set of patterns that match the string. For the sake of brevity, variable names are shortened.

const l = {
  java: [/* array of java patterns */],
  python: [/* array of python patterns */],
  c: [/* array of c patterns */]
}

// "For each language in languages, find the language that contains a
// pattern that matches the string"
const language = Object.keys(l).find(k => l[k].some(p => s.match(p)))

Also note that some languages have common syntaxes. If you want to go this route, you'll have to find syntax that's unique to the language or risk mistaking one language for another. For instance, True may be a Python boolean... or a variable name in Java. The same with class, is it a Python class or Java class. What if I defined a string in Java that's valid Python?

\$\endgroup\$
1
  • \$\begingroup\$ Exactly what I was hoping for ! this will make the function easily extensible to other languages and constructs. You're right, this is not the best way to solve this problem, lots of ignored edge cases and naive assumptions, i think this is better solved using a machine learning approach. My solution here is kinda specific to the exercise. \$\endgroup\$
    – Acemad
    Aug 24, 2017 at 12:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.