3

次の関数があるとしましょう

var action = (function () {

  var a = 42;
  var b = 2;

  function action(c) {
    return a + 4 * b + c;
  }

  return action;
}());

// how would you parse action into it's serialized LISP / AST format?
var parsed = parse(action);

action関数への参照を取得し、LISP 形式を出力する関数を持つことは可能ですか?(lambda (c) (plus (plus 42 (multiply 4 2)) c))

できることにいくつかの制限を加えることが許可されていactionます。

  • 本体は単一の式のみにする必要があります
  • それは純粋な関数であるべきです
  • 任意の自由変数は定数です

主な質問は、さまざまな入力で呼び出すことができる関数が与えられ、そのソース コードで、自由変数を置き換える正しい値を見つけることができますか?

上記の例では、a と b が定数であることがわかっているため、いくつかの値の出力を知的にプロットしてパターンを確認し、定数が何であるかを知ることができます。

質問:

関数参照とそのソース コードを受け取り、実行時の値を自由変数に置き換えて、関数の何らかの形式の AST を生成する関数をどのように記述しますか。

AST フォーマットの例は、コードに相当する LISP です。

基本的に、関数をシリアル化および逆シリアル化し、同じように動作させたい

{ a: a, b: b }分析関数に渡すと、問題は自明になることに注意してください。それは不正行為になります。

使用事例:

ライブラリのユーザーがこの関数を作成するために DSL を使用する必要なく、効果的に C++ に渡すことができるように、純粋な JavaScript 関数の言語に依存しない形式を生成したい

データベースドライバーがあったとしましょう

var cursor = db.table("my-table").map(function (row) {
  return ["foo", row.foo]
})

実行時に関数が何であるかを判断し、それを AST 形式に変換して、効率的なクエリ ビルダーを使用して SQL またはデータベースにあるクエリ エンジンに変換できるようにする必要があります。

つまり、次のように記述する必要はありません。

var cursor = db.table("my-table").map(function (rowQueryObject) {
    return db.createArray(db.StringConstant("foo"), rowQueryObject.getProperty("foo"))
})

これは、DB ライブラリがクエリ オブジェクトを使用して実行できる関数であり、詳細なメソッドを使用せずにクエリ オブジェクトの変換を構築できます。

4

2 に答える 2

1

Here is a full solution (using catalog of variables which is accessible by the parse function):

var CONSTANTS = { 
   a: 42,
   b: 2,
   c: 4
};

function test() {
    return a + 4 * b + c;
}

function getReturnStatement(func) {
    var funcStr = func.toString();
    return (/return\s+(.*?);/g).exec(funcStr)[1];
}

function replaceVariables(expr) {
    var current = '';
    for (var i = 0; i < expr.length; i += 1) {
        while (/[a-zA-Z_$]/.test(expr[i]) && i < expr.length) {
            current += expr[i];
            i += 1;
        }
        if (isNumber(CONSTANTS[current])) {
            expr = expr.replace(current, CONSTANTS[current]);
        }
        current = '';
    }
    return expr;
}

function isNumber(arg) {
    return !isNaN(parseInt(arg, 10));
}      

function tokenize(expr) {
    var tokens = [];
    for (var i = 0; i < expr.length; i += 1) {
        if (isWhitespace(expr[i])) {
            continue;
        } else if (isOperator(expr[i])) {
            tokens.push({
                type: 'operator',
                value: expr[i]
            });
        } else if (isParentheses(expr[i])) {
            tokens.push({
                type: 'parant',
                value: expr[i]
            });
        } else {
            var num = '';
            while (isNumber(expr[i]) && i < expr.length) {
                num += expr[i];
                i += 1;
            }
            i -= 1;
            tokens.push({
                type: 'number',
                value: parseInt(num, 10)
            });
        }
    }
    return tokens;
}

function toPrefix(tokens) {
    var operandStack = [],
        operatorStack = [],
        current,
        top = function (stack) {
            if (stack) {
                return stack[stack.length - 1];
            }
            return undefined;
        };

    while (tokens.length) {
        current = tokens.pop();
        if (current.type === 'number') {
            operandStack.push(current);
        } else if (current.value === '(' || 
                !operatorStack.length || 
                (getPrecendence(current.value) >
                 getPrecendence(top(operatorStack).value))) {

            operatorStack.push(current);
        } else if (current.value === ')') {
            while (top(operatorStack).value !== '(') {
                var tempOperator = operatorStack.pop(),
                    right = operandStack.pop(),
                    left = operandStack.pop();
                operandStack.push(tempOperator, left, right);
            }
            operatorStack.pop();
        } else if (getPrecendence(current.value) <= 
                getPrecendence(top(operatorStack).value)) {
            while (operatorStack.length &&
                    getPrecendence(current.value) <=
                    getPrecendence(top(operatorStack).value)) {

                tempOperator = operatorStack.pop();
                right = operandStack.pop();
                left = operandStack.pop();
                operandStack.push(tempOperator, left, right);
            }
        }

    }

    while (operatorStack.length) {
        tempOperator = operatorStack.pop();
        right = operandStack.pop();
        left = operandStack.pop();
        operandStack.push(tempOperator, left, right);
    }

    return operandStack;
}

function isWhitespace(arg) {
    return (/^\s$/).test(arg);
}

function isOperator(arg) {
    return (/^[*+\/-]$/).test(arg);
}

function isParentheses(arg) {
    return (/^[)(]$/).test(arg);
}

function getPrecendence(operator) {
    console.log(operator);
    switch (operator) {
        case '*':
            return 4;
        case '/':
            return 4;
        case '+':
            return 2;
        case '-':
            return 2;
        default:
            return undefined;
    }
}

function getLispString(tokens) {
    var result = '';
    tokens.forEach(function (e) {
        if (e)
            switch (e.type) {
            case 'number':
            result += e.value;
            break;
            case 'parant':
            result += e.value;
            break;
            case 'operator':
            result += getOperator(e.value);
            break;
            default:
            break;
        }
        result += ' ';
    });
    return result;
}

function getOperator(operator) {
    switch (operator) {
        case '+':
            return 'plus';
        case '*':
            return 'multiplicate';
        case '-':
            return 'minus';
        case '\\':
            return 'divide';
        default:
            return undefined;
    }
}

var res = getReturnStatement(test);
console.log(res);
res = replaceVariables(res);
console.log(res);
var tokens = tokenize(res);
console.log(tokens);
var prefix = toPrefix(tokens);
console.log(prefix);
console.log(getLispString(prefix));

I just wrote it so there might be some problems in the style but I think that the idea is clear.

You can get the function body by using the .toString method. After that you can use regular expression to match the return statement

(/return\s+(.*?);/g).exec(funcStr)[1];

Note that here you must use semicolons for successful match! In the next step all variables are transformed to number values using the CONSTANTS object (I see that you have some parameters left so you may need little modifications here). After that the string is being tokenized, for easier parsing. In next step the infix expression is transformed into a prefix one. At the last step I build a string which will make the output looks like what you need (+ - plus, - - minus and so on).

于 2013-01-16T10:06:49.067 に答える
0

メソッドを呼び出した後にメソッドの本体を取得できるかどうかわからないため、別の解決策を次に示します。

var a = 42;
var b = 2;

function action(c) {
  return a + 4 * b + c;
}

/**
 * get the given func body
 * after having replaced any available var from the given scope
 * by its *real* value
 */
function getFunctionBody(func, scope) {
  // get the method body
  var body = func.toString().replace(/^.*?{\s*((.|[\r\n])*?)\s*}.*?$/igm, "$1");  
  var matches = body.match(/[a-z][a-z0-9]*/igm);
  // for each potential var
  for(var i=0; i<matches.length; i++) {
    var potentialVar = matches[i];
    var scopedValue = scope[potentialVar];
    // if the given scope has the var defined
    if(typeof scopedValue !== "undefined") {
      // add "..." for strings
      if(typeof scopedValue === "string") {
        scopedValue = '"' + scopedValue + '"';
      }
      // replace the var by its scoped value
      var regex = new RegExp("([^a-z0-9]+|^)" + potentialVar + "([^a-z0-9]+|$)", "igm");
      var replacement = "$1" + scopedValue + "$2";
      body = body.replace(regex, replacement);
    }
  }
  return body;
}

// calling
var actionBody = getFunctionBody(action, this);

// log
alert(actionBody);

版画:

return 42 + 4 * 2 + c;

デモ

次に、独自のfunction toLISP(body)機能または必要な機能を実装する必要があります。

などの複雑なスコープ変数では機能しないことに注意してくださいvar a = {foo: "bar"}

于 2013-01-16T09:48:03.877 に答える