Skip to content

Commit

Permalink
try for peml
Browse files Browse the repository at this point in the history
  • Loading branch information
keweizhan committed Apr 13, 2024
1 parent c7223e4 commit 361c3e2
Show file tree
Hide file tree
Showing 16 changed files with 800 additions and 50 deletions.
106 changes: 106 additions & 0 deletions app/assets/javascripts/dag.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// 根据 DAG 描述构建图
function buildGraph(dag, nodeValueToLabel) {
const graph = {};
const lines = dag.trim().split('\n');

for (const line of lines) {
const [node, ...parents] = line.split(':').map(item => item.trim());
const label = nodeValueToLabel[node];
if (parents.length === 0 || parents[0] === '') {
graph[label] = [];
} else {
const validParents = parents.map(parent => nodeValueToLabel[parent]).filter(parent => parent !== '');
graph[label] = validParents;
}
}

return graph;
}

// 构建入度数组并初始化队列
function initializeCounts(graph) {
const inDegree = {};
const queue = [];

for (const node in graph) {
inDegree[node] = graph[node].length;
if (inDegree[node] === 0) {
queue.push(node);
}
}

return { inDegree, queue };
}


function processSolution(graph, inDegree, queue, solution, nodeValueToLabel) {
const visited = new Set();
if (Array.isArray(solution)) {
solution = solution.join('\n');
} else if (typeof solution !== 'string') {
throw new TypeError('The solution must be a string or an array.');
}

const solutionNodes = solution.split('\n').map(line => line.trim());
const graphNodes = Object.keys(graph).filter(node => node !== '__root__'); // 排除虚拟根节点

console.log("Solution nodes:", solutionNodes);
console.log("Graph nodes:", graphNodes);

// 检查学生的解答中的项目数量是否与图中的节点数量匹配
if (solutionNodes.length !== graphNodes.length) {
throw new Error('Number of items in student solution does not match the number of nodes in the graph.');
}

for (const node of solutionNodes) { // 修改这里
console.log("Current node:", node);
console.log("Current queue:", queue);

// 查找节点对应的标签
const label = node; // 修改这里
if (!label) {
console.log("Node label not found, returning false");
return false;
}

// 如果当前节点的标签不在队列中,返回false
if (!queue.includes(label)) {
console.log("Node label not in queue, returning false");
return false;
}

// 将当前节点的标签从队列中移除
queue.splice(queue.indexOf(label), 1);
visited.add(label);

// 更新相邻节点的入度,并将入度变为0的节点加入队列
for (const neighbor in graph) {
if (graph[neighbor].includes(label)) {
inDegree[neighbor]--;
if (inDegree[neighbor] === 0) {
queue.push(neighbor);
}
}
}
console.log("Updated in-degree:", inDegree);
console.log("Updated queue:", queue);
}

// 如果所有节点都被访问过,返回true,否则返回false
const allVisited = visited.size === Object.keys(graph).length;
console.log("All nodes visited:", allVisited);
return allVisited;
}

function processDAG(dag, solution) {
const nodeValueToLabel = {
"one": "print('Hello')",
"two": "print('Parsons')",
"three": "print('Problems!')"
};

const graph = buildGraph(dag, nodeValueToLabel);
const { inDegree, queue } = initializeCounts(graph);
const result = processSolution(graph, inDegree, queue, solution, nodeValueToLabel);
return result;
}
60 changes: 60 additions & 0 deletions app/assets/javascripts/peml_code.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
const url = "https://skynet.cs.vt.edu/peml-live/api/parse";

// 获取 PEML 数据
fetch('/data/s7.peml')
.then(response => {
return response.text();
})
.then(pemlText => {
const payload = {
"peml": pemlText,
"output_format": "json",
"render_to_html": "true"
};

// 发送 POST 请求进行解析
$.post(url, payload, function(data, status) {
console.log('Post status:', status);
console.log('Post data:', data);

// 检查服务器响应数据是否包含所需字段
if (data && data.title && data.instructions && data.assets && data.assets.code && data.assets.code.starter && data.assets.code.starter.files && data.assets.code.starter.files[0] && data.assets.code.starter.files[0].content) {
// 获取你需要的字段
var title = data.title.split(" -- ")[0];
var instructions = data.instructions;
var initialArray = data.assets.code.starter.files[0].content.map(item => item.code.split('\\n'));

// 在这里使用你获取的字段
document.getElementById("title").innerHTML = title;
document.getElementById("instructions").innerHTML = instructions;

var parson = new ParsonsWidget();
parson.init(initialArray);
parson.shuffleLines();

$("#newInstanceLink").click(function(event) {
event.preventDefault();
parson.shuffleLines();
});

$("#feedbackLink").click(function(event) {
event.preventDefault();
var fb = parson.getFeedback();
$("#feedback").html(fb.feedback);
if (fb.success) {
score = 50;
} else {
score = 0;
}
updateScore(score);
});

function updateScore(score) {
// 更新分数的代码
}
} else {
console.error('服务器响应数据不完整或格式不正确');
// 在这里处理服务器响应数据不完整或格式不正确的情况
}
});
});
1 change: 0 additions & 1 deletion app/assets/javascripts/simple_code.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ $(document).ready(function(){
var fb = parson.getFeedback();
if (data[index]['parsonsConfig']['turtleModelCode']){
if (fb.success) {
//把$("#feedback").html(fb.feedback)改成“Great job, you solved the exercise!”
$("#feedback").html("Great job, you solved the exercise!");
} else {
$("#feedback").html("Sorry, your solution does not match the model image");
Expand Down
155 changes: 146 additions & 9 deletions app/controllers/exercises_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ class ExercisesController < ApplicationController
require 'zip'
require 'tempfile'
require 'json'
require 'parsons_prompt_representer'


skip_before_action :verify_authenticity_token, only: [:execute_peml]
load_and_authorize_resource
skip_authorize_resource only: [:practice, :call_open_pop]

Expand Down Expand Up @@ -89,6 +90,60 @@ def update_score

end
# -------------------------------------------------------------
def execute_peml
payload = JSON.parse(request.body.read)
user_solution = payload['user_solution'].join("\n")
expected_output = payload['expected_output']
language = payload['language']
external_id = payload['external_id']

# find the specific exercise instance
exercise = Exercise.find_by_external_id(external_id)

# make sure the exercise exists
unless exercise
render json: { status: 'error', message: 'Can not find the specific exercise' }, status: :not_found
return
end

# find the specific exercise version
exercise_id = Exercise.get_exercise_id(external_id)
exercise_version = ExerciseVersion.get_exercise_version(exercise_id)

# create a new attempt
@attempt = Attempt.new(
user: current_user,
exercise_version: exercise_version,
submit_time: Time.current,
submit_num: 1
)

if @attempt.save
# create a temporary directory for the attempt
attempt_dir = "usr/attempts/active/#{@attempt.id}"
FileUtils.mkdir_p(attempt_dir)

# write the user's code to a file
File.write(attempt_dir + '/solution.py', user_solution)


# execute the code using CodeWorker
CodeWorker.new.perform_peml(@attempt.id, expected_output)

# reload the attempt to get the updated status
@attempt.reload

# render the response based on the error status
if @attempt.errors.empty?
render json: { message: "Your answer is correct!", success: true }
else
render json: { message: "Your answer is incorrect.", errors: @attempt.errors.full_messages, success: false }
end
else
render json: { error: "Unable to create attempt: #{@attempt.errors.full_messages.join(', ')}", success: false }
end
end
# -------------------------------------------------------------
# GET /exercises/download.csv
def download
@exercises = Exercise.accessible_by(current_ability)
Expand Down Expand Up @@ -303,12 +358,25 @@ def create
multiplechoiceprompt = {"multiple_choice_prompt" => msg[:multiple_choice_prompt].clone()}
form_hash["current_version"]["prompts"] << multiplechoiceprompt
form_hash.delete("multiple_choice_prompt")
elsif msg[:question_type].to_i == 4
msg[:parsons_prompt].merge!(msg[:prompt])
form_hash["current_version"]["prompts"] = Array.new
parsonsprompt = {"parsons_prompt" => msg[:parsons_prompt].clone()}
form_hash["current_version"]["prompts"] << parsonsprompt
form_hash.delete("parsons_prompt")
elsif msg[:instructions].present? && msg[:assets].present?
parsons_prompt = {}
parsons_prompt["exercise_id"] = msg[:exercise_id]
parsons_prompt["title"] = msg[:title]
parsons_prompt["author"] = msg[:author]
parsons_prompt["license"] = msg[:license]
parsons_prompt["tags"] = msg[:tags]
parsons_prompt["instructions"] = msg[:instructions]
parsons_prompt["assets"] = msg[:assets]

form_hash["current_version"]["prompts"] = [{ "parsons_prompt" => parsons_prompt }]

form_hash.delete("exercise_id")
form_hash.delete("title")
form_hash.delete("author")
form_hash.delete("license")
form_hash.delete("tags")
form_hash.delete("instructions")
form_hash.delete("assets")
end
form_hash.delete("prompt")
form_hash.delete("exercise_version")
Expand Down Expand Up @@ -446,12 +514,52 @@ def upload_create
exercise_version_params = exercise_params[:exercise_version]
use_rights = exercise_params[:exercise_collection_id].to_i
text_representation = exercise_version_params['text_representation']
hash = YAML.load(text_representation)
else
text_representation = File.read(params[:form][:file].path)
hash = YAML.load(text_representation)
use_rights = 0 # Personal exercise
end

# 检查 text_representation 中的 tags.style 字段是否包含 "parsons"
if text_representation.include?("tags.style") && text_representation.include?("parsons")
# 使用自定义解析器解析 text_representation
parsed_data = parse_text_representation(text_representation)

# 使用 ParsonsPromptRepresenter 创建 ParsonsPrompt 对象
parsons_prompt = ParsonsPromptRepresenter.new(ParsonsPrompt.new).from_hash(parsed_data)
exercises = [parsons_prompt.to_hash]
else
# 使用 ExerciseRepresenter 解析 text_representation
exercises = ExerciseRepresenter.for_collection.new([]).from_hash(YAML.load(text_representation))
end

# 后续的处理逻辑保持不变
exercises.each do |e|
if e[:instructions].present? && e[:assets].present?
# 处理 Parsons 问题
parsons_prompt = {}

# 从 e 中获取相应字段的值,并赋值给 parsons_prompt
parsons_prompt["exercise_id"] = e[:exercise_id]
parsons_prompt["title"] = e[:title]
parsons_prompt["author"] = e[:author]
parsons_prompt["license"] = e[:license]
parsons_prompt["tags"] = e[:tags]
parsons_prompt["instructions"] = e[:instructions]
parsons_prompt["assets"] = e[:assets]

# 更新 prompt
e[:prompt] = [{ "parsons_prompt" => parsons_prompt }]

# 删除 e 中已经复制到 parsons_prompt 的字段
e.delete(:exercise_id)
e.delete(:title)
e.delete(:author)
e.delete(:license)
e.delete(:tags)
e.delete(:instructions)
e.delete(:assets)
end
end
if !hash.kind_of?(Array)
hash = [hash]
end
Expand Down Expand Up @@ -1170,5 +1278,34 @@ def count_submission
session[:submit_num] += 1
end
end
# -------------------------------------------------------------
def parse_text_representation(text_representation)
parsed_data = {}

lines = text_representation.split("\r\n")
lines.each do |line|
if line.include?(":") && !line.start_with?("#")
key, value = line.split(":", 2).map(&:strip)

if key.include?(".")
nested_keys = key.split(".")
current_level = parsed_data

nested_keys.each_with_index do |nested_key, index|
if index == nested_keys.length - 1
current_level[nested_key] = value
else
current_level[nested_key] ||= {}
current_level = current_level[nested_key]
end
end
else
parsed_data[key] = value
end
end
end

parsed_data
end

end
Loading

0 comments on commit 361c3e2

Please sign in to comment.