diff --git a/CloudCoder/src/org/cloudcoder/app/client/CloudCoder.java b/CloudCoder/src/org/cloudcoder/app/client/CloudCoder.java index d425921a..810e5078 100644 --- a/CloudCoder/src/org/cloudcoder/app/client/CloudCoder.java +++ b/CloudCoder/src/org/cloudcoder/app/client/CloudCoder.java @@ -23,7 +23,6 @@ import org.cloudcoder.app.client.model.Session; import org.cloudcoder.app.client.model.StatusMessage; import org.cloudcoder.app.client.page.CloudCoderPage; -import org.cloudcoder.app.client.page.CoursesAndProblemsPage2; import org.cloudcoder.app.client.page.CoursesAndProblemsPage3; import org.cloudcoder.app.client.page.DevelopmentPage; import org.cloudcoder.app.client.page.EditProblemPage; @@ -174,7 +173,7 @@ public void onSuccess(User result) { changePage(page); } else { // Default behavior: navigate to the home page - changePage(new CoursesAndProblemsPage2()); + changePage(createPageForPageId(PageId.COURSES_AND_PROBLEMS, "")); } } } @@ -271,7 +270,7 @@ private CloudCoderPage createPageForPageId(PageId pageId) { // This shouldn't happen (can't find page for Activity), // but if it does, go to the courses and problems page. GWT.log("Don't know what kind of page to create for " + pageId); - page = new CoursesAndProblemsPage2(); + page = new CoursesAndProblemsPage3(); break; } return page; diff --git a/CloudCoder/src/org/cloudcoder/app/client/page/EditProblemPage.java b/CloudCoder/src/org/cloudcoder/app/client/page/EditProblemPage.java index fd246d1b..77d9cc3a 100644 --- a/CloudCoder/src/org/cloudcoder/app/client/page/EditProblemPage.java +++ b/CloudCoder/src/org/cloudcoder/app/client/page/EditProblemPage.java @@ -23,14 +23,16 @@ import org.cloudcoder.app.client.model.PageId; import org.cloudcoder.app.client.model.PageStack; import org.cloudcoder.app.client.model.Session; -import org.cloudcoder.app.client.model.StatusMessage; +import org.cloudcoder.app.client.model.StatusMessage; import org.cloudcoder.app.client.rpc.RPC; import org.cloudcoder.app.client.view.ChoiceDialogBox; import org.cloudcoder.app.client.view.EditBooleanField; import org.cloudcoder.app.client.view.EditDateField; import org.cloudcoder.app.client.view.EditDateTimeField; +import org.cloudcoder.app.client.view.EditDummyButton; import org.cloudcoder.app.client.view.EditEnumField; import org.cloudcoder.app.client.view.EditModelObjectField; +import org.cloudcoder.app.client.view.EditOptionalStringFieldWithAceEditor; import org.cloudcoder.app.client.view.EditStringField; import org.cloudcoder.app.client.view.EditStringFieldWithAceEditor; import org.cloudcoder.app.client.view.PageNavPanel; @@ -50,6 +52,7 @@ import org.cloudcoder.app.shared.model.ProblemLicense; import org.cloudcoder.app.shared.model.ProblemType; import org.cloudcoder.app.shared.model.TestCase; +import org.cloudcoder.app.shared.util.EvaluatorUtil; import org.cloudcoder.app.shared.util.SubscriptionRegistrar; import com.google.gwt.core.shared.GWT; @@ -95,6 +98,7 @@ private class UI extends ResizeComposite implements SessionObserver { private Button addTestCaseButton; private FlowPanel addTestCaseButtonPanel; private ProblemAndTestCaseList problemAndTestCaseListOrig; + EditOptionalStringFieldWithAceEditor evaluatorEditor; public UI() { this.dockLayoutPanel = new DockLayoutPanel(Unit.PX); @@ -278,6 +282,63 @@ private void setLanguage() { skeletonEditor.setEditorTheme(AceEditorTheme.VIBRANT_INK); problemFieldEditorList.add(skeletonEditor); + // In the editor for the evaluator, we keep the editor mode in sync + // with the problem type, like for the skeleton. + evaluatorEditor = + new EditOptionalStringFieldWithAceEditor("Evaluator code (only Python)", "Use custom evaluator", ProblemData.EVALUATOR) { + @Override + public void update() { + super.update(); + setLanguage(); + } + @Override + public void onModelObjectChange() { + setLanguage(); + } + private void setLanguage() { + AceEditorMode editorMode = ViewUtil.getModeForLanguage(getModelObject().getProblemType().getLanguage()); + setEditorMode(editorMode); + + if(EvaluatorUtil.isEvaluatorUsedForProblemType(getModelObject().getProblemType())) { + setEnabled(true); + } else { + setEnabled(false); + } + } + + }; + evaluatorEditor.setEditorThemes(AceEditorTheme.VIBRANT_INK, AceEditorTheme.SOLARIZED_DARK); + problemFieldEditorList.add(evaluatorEditor); + + EditDummyButton defaultEvaluatorButton = + new EditDummyButton("Reset to default evaluator", ProblemData.EVALUATOR) { + @Override + public void onButtonClick() { + setField(EvaluatorUtil.getDefaultEvaluator(getModelObject())); + evaluatorEditor.update(); + } + + @Override + public void update() { + super.update(); + setLanguage(); + } + + @Override + public void onModelObjectChange() { + setLanguage(); + } + + private void setLanguage() { + if(EvaluatorUtil.isEvaluatorUsedForProblemType(getModelObject().getProblemType())) { + setEnabled(true); + } else { + setEnabled(false); + } + } + }; + + problemFieldEditorList.add(defaultEvaluatorButton); // We don't need an editor for schema version - problems/testcases are // automatically converted to the latest version when they are imported. diff --git a/CloudCoder/src/org/cloudcoder/app/client/page/LoginPage.java b/CloudCoder/src/org/cloudcoder/app/client/page/LoginPage.java index 26c9fb1e..4573036f 100644 --- a/CloudCoder/src/org/cloudcoder/app/client/page/LoginPage.java +++ b/CloudCoder/src/org/cloudcoder/app/client/page/LoginPage.java @@ -176,7 +176,7 @@ public void setPubTextInstitution(String result) { /** * Default constructor. - * Will take the user to the {@link CoursesAndProblemsPage2} + * Will take the user to the {@link CoursesAndProblemsPage3} * upon a successful login. */ public LoginPage() { diff --git a/CloudCoder/src/org/cloudcoder/app/client/view/EditDummyButton.java b/CloudCoder/src/org/cloudcoder/app/client/view/EditDummyButton.java new file mode 100644 index 00000000..82609f8a --- /dev/null +++ b/CloudCoder/src/org/cloudcoder/app/client/view/EditDummyButton.java @@ -0,0 +1,118 @@ +// CloudCoder - a web-based pedagogical programming environment +// Copyright (C) 2015, Andras Eisenberger +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package org.cloudcoder.app.client.view; + +import org.cloudcoder.app.client.page.EditProblemPage; +import org.cloudcoder.app.shared.model.ModelObjectField; + +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.IsWidget; + +/** + * Implementation of {@link EditModelObjectField} which does not edit any + * field. It extends {@link EditModelObjectField}, because only these can be + * on the {@link EditProblemPage} UI. + * + * @author Andras Eisenberger + */ +public abstract class EditDummyButton + extends EditModelObjectField { + + private class UI extends Composite { + private Button button; + + public UI() { + FlowPanel panel = new FlowPanel(); + panel.setStyleName("cc-fieldEditor"); + + this.button = new Button(getDescription(), new ClickHandler() { + public void onClick(ClickEvent event) { + onButtonClick(); + } + }); + button.setStyleName("cc-emphButton"); + panel.add(button); + + initWidget(panel); + } + + public void setEnabled(boolean enabled) { + button.setEnabled(enabled); + } + } + + private UI ui; + + /** + * Constructor. + * + * @param desc the human-readable description of the field, which is also + * the text of the button + * @param field any of the fields, it doesn't touch it by default + */ + public EditDummyButton(String desc, ModelObjectField field) { + super(desc, field); + ui = new UI(); + } + + /* (non-Javadoc) + * @see org.cloudcoder.app.client.view.EditModelObjectField#getUI() + */ + @Override + public IsWidget getUI() { + return ui; + } + + /* (non-Javadoc) + * @see org.cloudcoder.app.client.view.EditModelObjectField#commit() + */ + @Override + public void commit() { + } + + /* (non-Javadoc) + * @see org.cloudcoder.app.client.view.EditModelObjectField#update() + */ + @Override + public void update() { + } + + /* (non-Javadoc) + * @see org.cloudcoder.app.client.view.EditModelObjectField#isCommitError() + */ + @Override + public boolean isCommitError() { + // It's connected to no field, so no + return false; + } + + /** + * Do this when the button is clicked + */ + public abstract void onButtonClick(); + + /** + * @param enabled whether the button should be enabled + */ + public void setEnabled(boolean enabled) { + ui.setEnabled(enabled); + } +} diff --git a/CloudCoder/src/org/cloudcoder/app/client/view/EditOptionalStringFieldWithAceEditor.java b/CloudCoder/src/org/cloudcoder/app/client/view/EditOptionalStringFieldWithAceEditor.java new file mode 100644 index 00000000..4ec3916f --- /dev/null +++ b/CloudCoder/src/org/cloudcoder/app/client/view/EditOptionalStringFieldWithAceEditor.java @@ -0,0 +1,258 @@ +// CloudCoder - a web-based pedagogical programming environment +// Copyright (C) 2011-2012, Jaime Spacco +// Copyright (C) 2011-2012, David H. Hovemeyer +// Copyright (C) 2015, Andras Eisenberger +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package org.cloudcoder.app.client.view; + +import org.cloudcoder.app.shared.model.ModelObjectField; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.logical.shared.ValueChangeEvent; +import com.google.gwt.event.logical.shared.ValueChangeHandler; +import com.google.gwt.user.client.ui.CheckBox; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.IsWidget; +import com.google.gwt.user.client.ui.Label; + +import edu.ycp.cs.dh.acegwt.client.ace.AceEditor; +import edu.ycp.cs.dh.acegwt.client.ace.AceEditorMode; +import edu.ycp.cs.dh.acegwt.client.ace.AceEditorTheme; + +/** + * Edit an optional string field of a model object using an {@link AceEditor}. + * + * @author Andras Eisenberger + */ +public class EditOptionalStringFieldWithAceEditor + extends EditModelObjectField { + + private class UI extends EditModelObjectFieldUI { + private CheckBox checkBox; + private AceEditor editor; + private boolean editorStarted; + private AceEditorMode currentMode; + + public UI(String checkboxLabel) { + FlowPanel panel = new FlowPanel(); + panel.setStyleName("cc-fieldEditor", true); + + Label label = new Label(getDescription()); + label.setStyleName("cc-fieldEditorLabel", true); + panel.add(label); + + panel.add(getErrorLabel()); + + this.checkBox = new CheckBox(checkboxLabel); + + this.editor = new AceEditor(); + editor.setSize("600px", "300px"); + + checkBox.addValueChangeHandler(new ValueChangeHandler() { + + @Override + public void onValueChange(ValueChangeEvent event) { + if(event.getValue()) { + editor.setReadOnly(false); + editor.setTheme(editorEnabledTheme); + } else { + editor.setReadOnly(true); + editor.setTheme(editorDisabledTheme); + } + + } + }); + + panel.add(checkBox); + panel.add(editor); + editorStarted = false; + + initWidget(panel); + } + + public boolean isEditorStarted() { + return editorStarted; + } + + public void startEditor() { + editor.startEditor(); + if (editorMode != null) { + editor.setMode(editorMode); + currentMode = editorMode; + } + + resetEditorTheme(); + + if(checkBox.getValue()) { + editor.setReadOnly(false); + } else { + editor.setReadOnly(true); + } + + editor.setFontSize("14px"); + editorStarted = true; + } + + /** + * @param editorReadOnly whether the editor should be read-only now + */ + public void resetEnabled() { + checkBox.setEnabled(enabled); + } + + public void setText(String text) { + editor.setText(text); + } + + public String getText() { + return editor.getText(); + } + + public void setCheckBox(boolean value) { + checkBox.setValue(value, true); + } + + public boolean getCheckBox() { + return checkBox.getValue(); + } + + public void resetEditorMode() { + if (editorMode != null && editorMode != currentMode) { + editor.setMode(editorMode); + currentMode = editorMode; + GWT.log("Changing editor mode to " + editorMode); + } + } + + public void resetEditorTheme() { + if(getCheckBox()) { + if (editorEnabledTheme != null) { + editor.setTheme(editorEnabledTheme); + } + } else { + if (editorDisabledTheme != null) { + editor.setTheme(editorDisabledTheme); + } + } + } + } + + private AceEditorMode editorMode; + private AceEditorTheme editorEnabledTheme; + private AceEditorTheme editorDisabledTheme; + private boolean enabled = true; + private UI ui; + + /** + * Constructor. + * + * @param desc human-readable description of field being edited + * @param checkboxLabel label to display next to checkbox + * @param field the {@link ModelObjectField} being edited + */ + public EditOptionalStringFieldWithAceEditor(String desc, String checkboxLabel, ModelObjectField field) { + super(desc, field); + this.ui = new UI(checkboxLabel); + } + + /** + * Set the editor mode. + * + * @param editorMode the editorMode to set + */ + public void setEditorMode(AceEditorMode editorMode) { + this.editorMode = editorMode; + if (ui.isEditorStarted()) { + ui.resetEditorMode(); + } + } + + /** + * Set the editor theme. + * + * @param editorTheme the editorTheme to set + */ + public void setEditorThemes(AceEditorTheme editorEnabledTheme, AceEditorTheme editorDisabledTheme) { + this.editorEnabledTheme = editorEnabledTheme; + this.editorDisabledTheme = editorDisabledTheme; + if (ui.isEditorStarted()) { + ui.resetEditorTheme(); + } + } + + /** + * @param enabled whether the editor should be enabled. Also unticks the + * checkbox when disabled. + */ + public void setEnabled(boolean enabled) { + if(this.enabled != enabled) { + this.enabled = enabled; + ui.resetEnabled(); + if(enabled) { + if(!ui.getText().trim().isEmpty()) { + ui.setCheckBox(true); + } + } else { + ui.setCheckBox(false); + } + } + } + + /* (non-Javadoc) + * @see org.cloudcoder.app.client.view.EditModelObjectField#getUI() + */ + @Override + public IsWidget getUI() { + return ui; + } + + /* (non-Javadoc) + * @see org.cloudcoder.app.client.view.EditModelObjectField#commit() + */ + @Override + public void commit() { + String text; + if(ui.getCheckBox()) { + text = ui.getText(); + } else { + text = ""; + } + + if (text.length() > getModelObjectField().getSize()) { + setCommitError(true); + ui.setError("Value cannot be longer than " + getModelObjectField().getSize() + " characters"); + } else { + setCommitError(false); + ui.clearError(); + setField(text); + } + } + + @Override + public void update() { + if (!ui.isEditorStarted()) { + // At this point, we'll assume that the UI has been added to the page DOM, + // so it's safe to start the AceEditor. + ui.startEditor(); + } + + String field = getField(); + ui.setText(getField()); + if(!field.isEmpty()) { + ui.setCheckBox(true); + } + } +} diff --git a/CloudCoderBuilder2/.classpath b/CloudCoderBuilder2/.classpath index 59a773ea..29fc9517 100644 --- a/CloudCoderBuilder2/.classpath +++ b/CloudCoderBuilder2/.classpath @@ -17,7 +17,7 @@ - + diff --git a/CloudCoderBuilder2/src/org/cloudcoder/builder2/pythonfunction/AddPythonFunctionScaffoldingBuildStep.java b/CloudCoderBuilder2/src/org/cloudcoder/builder2/pythonfunction/AddPythonFunctionScaffoldingBuildStep.java index d2d8e226..26d36600 100644 --- a/CloudCoderBuilder2/src/org/cloudcoder/builder2/pythonfunction/AddPythonFunctionScaffoldingBuildStep.java +++ b/CloudCoderBuilder2/src/org/cloudcoder/builder2/pythonfunction/AddPythonFunctionScaffoldingBuildStep.java @@ -23,6 +23,7 @@ import org.cloudcoder.app.shared.model.Problem; import org.cloudcoder.app.shared.model.ProblemType; import org.cloudcoder.app.shared.model.TestCase; +import org.cloudcoder.app.shared.util.EvaluatorUtil; import org.cloudcoder.builder2.model.BuilderSubmission; import org.cloudcoder.builder2.model.IBuildStep; import org.cloudcoder.builder2.model.InternalBuilderException; @@ -70,7 +71,15 @@ private ProgramSource addScaffolding(ProgramSource programSource, Problem proble String programText = programSource.getProgramText(); test.append(programText + "\n"); programTextLength=StringUtil.countLines(programText); - int spaces=getIndentationIncrementFromPythonCode(programText); + + // Add code for evaluator + String evalText = problem.getEvaluator(); + if(evalText.trim().isEmpty()) { + // Default evaluator if no evaluator is given + evalText = EvaluatorUtil.getDefaultPythonFunctionEvaluator(problem); + } + + test.append(evalText + "\n"); for (TestCase t : testCaseList) { // each test case is a function that invokes the function being tested @@ -92,11 +101,8 @@ private ProgramSource addScaffolding(ProgramSource programSource, Problem proble // the test case passed, and a String containing the // actual output. // - test.append(indent(spaces)+"_output="+problem.getTestname() + - "(" +t.getInput()+ ")\n"); - test.append(indent(spaces)+"_expected=" + t.getOutput() + "\n"); - test.append(indent(spaces)+"_result=(_expected == _output) if (type(_output) != float and type(_expected) != float) else (math.fabs(_output-_expected) < 0.00001)\n"); - test.append(indent(spaces)+"return (_result, _output)\n"); + String in = t.getInput(); + test.append(" return _eval((" + in + (in.trim().isEmpty() ? "" : ",") + "), " + t.getOutput() + ")\n"); } // Convert to string, determine epilogue length @@ -107,17 +113,4 @@ private ProgramSource addScaffolding(ProgramSource programSource, Problem proble // Done! return new ProgramSource(result, prologueLength, epilogueLength); } - - private int getIndentationIncrementFromPythonCode(String programText) { - //TODO: Figure out the indentation scheme of the student submitted programTest - return 2; - } - - private String indent(int n) { - StringBuilder b=new StringBuilder(); - for (int i=0; iWrite a function called compute_sum<\/code> that\n takes two parameters and returns their sum.<\/p>\n

Example calls:<\/p>\n

    \n
  • compute_sum(2, 3)<\/code> => 5<\/code><\/li>\n
  • compute_sum(4, -11)<\/code> => -7<\/code><\/li>\n
  • compute_sum(42, 0)<\/code> => 42<\/code><\/li>\n<\/ul>","skeleton":"","schema_version":4,"author_name":"David Hovemeyer","author_email":"dhovemey@ycp.edu","author_website":"http:\/\/faculty.ycp.edu\/~dhovemey","timestamp_utc":1377615525510,"license":1,"parent_hash":"","external_library_url":"","external_library_md5":""},"test_case_data_list":[{"test_case_name":"twoPlusThree","input":"2, 3","output":"5","secret":false},{"test_case_name":"fourPlusMinusEleven","input":"4, -11","output":"-7","secret":false},{"test_case_name":"fortyTwoPlusZero","input":"42, 0","output":"42","secret":false}]} \ No newline at end of file +{"problem_data":{"problem_type":1,"testname":"compute_sum","brief_description":"compute the sum of two numbers","description":"

    Write a function called compute_sum<\/code> that\n takes two parameters and returns their sum.<\/p>\n

    Example calls:<\/p>\n

      \n
    • compute_sum(2, 3)<\/code> => 5<\/code><\/li>\n
    • compute_sum(4, -11)<\/code> => -7<\/code><\/li>\n
    • compute_sum(42, 0)<\/code> => 42<\/code><\/li>\n<\/ul>","skeleton":"","schema_version":4,"author_name":"David Hovemeyer","author_email":"dhovemey@ycp.edu","author_website":"http:\/\/faculty.ycp.edu\/~dhovemey","timestamp_utc":1377615525510,"license":1,"parent_hash":"","external_library_url":"","external_library_md5":"","evaluator":""},"test_case_data_list":[{"test_case_name":"twoPlusThree","input":"2, 3","output":"5","secret":false},{"test_case_name":"fourPlusMinusEleven","input":"4, -11","output":"-7","secret":false},{"test_case_name":"fortyTwoPlusZero","input":"42, 0","output":"42","secret":false}]} \ No newline at end of file diff --git a/CloudCoderBuilder2/tests/junit/org/cloudcoder/builder2/tests/res/gravity.json b/CloudCoderBuilder2/tests/junit/org/cloudcoder/builder2/tests/res/gravity.json index 19cced23..3adf1054 100644 --- a/CloudCoderBuilder2/tests/junit/org/cloudcoder/builder2/tests/res/gravity.json +++ b/CloudCoderBuilder2/tests/junit/org/cloudcoder/builder2/tests/res/gravity.json @@ -1 +1 @@ -{"problem_data":{"problem_type":1,"testname":"gravity","brief_description":"How far do objects travel when thrown off a building?","description":"

      \nConsider the problem of trying to determine how far an object\nfalls when thrown from the top of a very tall building. Because\nthe building is on Earth, accelaration due to gravity should be \npart of your calculations (i.e., assume 9.8 m\/s^2 as the G<\/em> \nconstant). To make the calculation reasonable, assume the \nbuilding is infinitely tall and located in a vacuum on Earth\n(i.e., ignore any forces due to friction, air-resistance, etc.).\n<\/p>\n\n

      \nReturn the number of meters the object has fallen after \ntime<\/code> seconds have elapsed when the object is \nthrown with given initial velocity.\n<\/p>\n","skeleton":"def gravity (time, initalVelocity):\n \"\"\"\n return float indicating number of meters an object has fallen \n after being thrown with an initial velocity (given in meters per\n second) and after falling for time seconds\n \"\"\"\n # TODO: complete code here\n ","schema_version":4,"author_name":"Kannan Raju","author_email":"kr88@duke.edu","author_website":"","timestamp_utc":1378456148639,"license":1,"parent_hash":"","external_library_url":"","external_library_md5":""},"test_case_data_list":[{"test_case_name":"t0","input":"0, 0","output":"0","secret":false},{"test_case_name":"t1","input":"1, 1","output":"5.9","secret":false},{"test_case_name":"t2","input":"1, 5","output":"9.9","secret":false},{"test_case_name":"t3","input":"3, 5","output":"59.1","secret":false},{"test_case_name":"t4","input":"3, 0","output":"44.1","secret":false},{"test_case_name":"t5","input":"3200, 0","output":"50176000.0","secret":false},{"test_case_name":"t6","input":"86400, 0","output":"36578304000.0","secret":false},{"test_case_name":"t7","input":"12.5, 20","output":"1015.625","secret":false},{"test_case_name":"t8","input":"12.5, 200","output":"3265.625","secret":false},{"test_case_name":"t9","input":"12.5, 2000","output":"25765.625","secret":false}]} \ No newline at end of file +{"problem_data":{"problem_type":1,"testname":"gravity","brief_description":"How far do objects travel when thrown off a building?","description":"

      \nConsider the problem of trying to determine how far an object\nfalls when thrown from the top of a very tall building. Because\nthe building is on Earth, accelaration due to gravity should be \npart of your calculations (i.e., assume 9.8 m\/s^2 as the G<\/em> \nconstant). To make the calculation reasonable, assume the \nbuilding is infinitely tall and located in a vacuum on Earth\n(i.e., ignore any forces due to friction, air-resistance, etc.).\n<\/p>\n\n

      \nReturn the number of meters the object has fallen after \ntime<\/code> seconds have elapsed when the object is \nthrown with given initial velocity.\n<\/p>\n","skeleton":"def gravity (time, initalVelocity):\n \"\"\"\n return float indicating number of meters an object has fallen \n after being thrown with an initial velocity (given in meters per\n second) and after falling for time seconds\n \"\"\"\n # TODO: complete code here\n ","schema_version":4,"author_name":"Kannan Raju","author_email":"kr88@duke.edu","author_website":"","timestamp_utc":1378456148639,"license":1,"parent_hash":"","external_library_url":"","external_library_md5":"","evaluator":""},"test_case_data_list":[{"test_case_name":"t0","input":"0, 0","output":"0","secret":false},{"test_case_name":"t1","input":"1, 1","output":"5.9","secret":false},{"test_case_name":"t2","input":"1, 5","output":"9.9","secret":false},{"test_case_name":"t3","input":"3, 5","output":"59.1","secret":false},{"test_case_name":"t4","input":"3, 0","output":"44.1","secret":false},{"test_case_name":"t5","input":"3200, 0","output":"50176000.0","secret":false},{"test_case_name":"t6","input":"86400, 0","output":"36578304000.0","secret":false},{"test_case_name":"t7","input":"12.5, 20","output":"1015.625","secret":false},{"test_case_name":"t8","input":"12.5, 200","output":"3265.625","secret":false},{"test_case_name":"t9","input":"12.5, 2000","output":"25765.625","secret":false}]} \ No newline at end of file diff --git a/CloudCoderBuilder2/tests/junit/org/cloudcoder/builder2/tests/res/skip3.json b/CloudCoderBuilder2/tests/junit/org/cloudcoder/builder2/tests/res/skip3.json index fb4b5652..7ff1df8e 100644 --- a/CloudCoderBuilder2/tests/junit/org/cloudcoder/builder2/tests/res/skip3.json +++ b/CloudCoderBuilder2/tests/junit/org/cloudcoder/builder2/tests/res/skip3.json @@ -1 +1 @@ -{"problem_data":{"problem_type":3,"testname":"skip 3","brief_description":"print integers in range, skipping by increments of 3","description":"

      \n The program will receive two integer input values,\n start<\/i> and end<\/i>. The\n output of the program should be a line of text\n with all of the integer values between\n start<\/i> and end<\/i>, inclusive, such that\n each successive integer is 3 greater than the previous\n integer.\n<\/p>\n

      \n For example, if the input is 1 10<\/b>, then the output\n should be\n<\/p>\n

      1 4 7 10<\/pre><\/blockquote>\n

      \n Another example: if the input is 19 35<\/b>, then the\n output should be\n<\/p>\n

      19 22 25 28 31 34<\/pre><\/blockquote>\n

      Hints:<\/p>\n

        \n
      • The program already declares variables for start<\/i>\n and end<\/i>, and uses scanf<\/code> to read\n their values<\/li>\n
      • Make sure to print a space after each integer<\/li>\n
      • Think about how to generate only every third integer;\n planning<\/i> the loop will be helpful<\/li>\n<\/ul>","skeleton":"#include \n\nint main(void) {\n \/\/ Declare variables for input (start and end)\n int start, end;\n \n \/\/ Read input values\n scanf(\"%i\", &start);\n scanf(\"%i\", &end);\n \n \/\/ Use a loop to print the output values\n \/\/ TODO\n \n return 0;\n}","schema_version":1,"author_name":"David Hovemeyer","author_email":"dhovemey@ycp.edu","author_website":"http:\/\/faculty.ycp.edu\/~dhovemey\/","timestamp_utc":1360782949618,"license":1,"parent_hash":"","external_library_url":"","external_library_md5":""},"test_case_data_list":[{"test_case_name":"OneToTenByThree","input":"1 10","output":"^(.*[^0-9])?1\\s+4\\s+7\\s+10([^0-9]*)?$","secret":false},{"test_case_name":"OneToTenPartialCredit","input":"1 10","output":"^(.*[^0-9])?1\\s+(2(\\s+3\\s+)?)?4\\s+(5(\\s+6\\s+)?)?7\\s+(8(\\s+9\\s+)?)?10([^0-9]*)?$","secret":false},{"test_case_name":"NineteenToThirtyFive","input":"19 35","output":"^(.*[^0-9])?19\\s+22\\s+25\\s+28\\s+31\\s+34([^0-9]*)?$","secret":false},{"test_case_name":"NineteenToThirtyFivePartialCredit","input":"19 35","output":"^(.*[^0-9])?19\\s+(20(\\s+21\\s+)?)?22\\s+(23(\\s+24\\s+)?)?25\\s+(26(\\s+27\\s+)?)?28\\s+(29(\\s+30\\s+)?)?31\\s+(32(\\s+33\\s+)?)?34(\\s+35)?([^0-9]*)?$","secret":false},{"test_case_name":"Just42","input":"42 42","output":"^(.*[^0-9])?42([^0-9]*)$","secret":false}]} \ No newline at end of file +{"problem_data":{"problem_type":3,"testname":"skip 3","brief_description":"print integers in range, skipping by increments of 3","description":"

        \n The program will receive two integer input values,\n start<\/i> and end<\/i>. The\n output of the program should be a line of text\n with all of the integer values between\n start<\/i> and end<\/i>, inclusive, such that\n each successive integer is 3 greater than the previous\n integer.\n<\/p>\n

        \n For example, if the input is 1 10<\/b>, then the output\n should be\n<\/p>\n

        1 4 7 10<\/pre><\/blockquote>\n

        \n Another example: if the input is 19 35<\/b>, then the\n output should be\n<\/p>\n

        19 22 25 28 31 34<\/pre><\/blockquote>\n

        Hints:<\/p>\n

          \n
        • The program already declares variables for start<\/i>\n and end<\/i>, and uses scanf<\/code> to read\n their values<\/li>\n
        • Make sure to print a space after each integer<\/li>\n
        • Think about how to generate only every third integer;\n planning<\/i> the loop will be helpful<\/li>\n<\/ul>","skeleton":"#include \n\nint main(void) {\n \/\/ Declare variables for input (start and end)\n int start, end;\n \n \/\/ Read input values\n scanf(\"%i\", &start);\n scanf(\"%i\", &end);\n \n \/\/ Use a loop to print the output values\n \/\/ TODO\n \n return 0;\n}","schema_version":1,"author_name":"David Hovemeyer","author_email":"dhovemey@ycp.edu","author_website":"http:\/\/faculty.ycp.edu\/~dhovemey\/","timestamp_utc":1360782949618,"license":1,"parent_hash":"","external_library_url":"","external_library_md5":"","evaluator":""},"test_case_data_list":[{"test_case_name":"OneToTenByThree","input":"1 10","output":"^(.*[^0-9])?1\\s+4\\s+7\\s+10([^0-9]*)?$","secret":false},{"test_case_name":"OneToTenPartialCredit","input":"1 10","output":"^(.*[^0-9])?1\\s+(2(\\s+3\\s+)?)?4\\s+(5(\\s+6\\s+)?)?7\\s+(8(\\s+9\\s+)?)?10([^0-9]*)?$","secret":false},{"test_case_name":"NineteenToThirtyFive","input":"19 35","output":"^(.*[^0-9])?19\\s+22\\s+25\\s+28\\s+31\\s+34([^0-9]*)?$","secret":false},{"test_case_name":"NineteenToThirtyFivePartialCredit","input":"19 35","output":"^(.*[^0-9])?19\\s+(20(\\s+21\\s+)?)?22\\s+(23(\\s+24\\s+)?)?25\\s+(26(\\s+27\\s+)?)?28\\s+(29(\\s+30\\s+)?)?31\\s+(32(\\s+33\\s+)?)?34(\\s+35)?([^0-9]*)?$","secret":false},{"test_case_name":"Just42","input":"42 42","output":"^(.*[^0-9])?42([^0-9]*)$","secret":false}]} \ No newline at end of file diff --git a/CloudCoderBuilderWebService/src/org/cloudcoder/builderwebservice/servlets/ProblemBuilder.java b/CloudCoderBuilderWebService/src/org/cloudcoder/builderwebservice/servlets/ProblemBuilder.java index 77a93bea..84d4c8be 100644 --- a/CloudCoderBuilderWebService/src/org/cloudcoder/builderwebservice/servlets/ProblemBuilder.java +++ b/CloudCoderBuilderWebService/src/org/cloudcoder/builderwebservice/servlets/ProblemBuilder.java @@ -62,6 +62,7 @@ public Problem build() throws BadRequestException { problem.setTimestampUtc(0L); problem.setLicense(ProblemLicense.NOT_REDISTRIBUTABLE); problem.setParentHash(""); + problem.setEvaluator(""); // None of the Problem-specific fields are significant // Important: the builder must ignore the problem id. diff --git a/CloudCoderModelClasses/src/org/cloudcoder/app/shared/model/EditProblemAdapter.java b/CloudCoderModelClasses/src/org/cloudcoder/app/shared/model/EditProblemAdapter.java index 8d6e1a9a..e8b58b46 100644 --- a/CloudCoderModelClasses/src/org/cloudcoder/app/shared/model/EditProblemAdapter.java +++ b/CloudCoderModelClasses/src/org/cloudcoder/app/shared/model/EditProblemAdapter.java @@ -88,6 +88,17 @@ public String getSkeleton() { return delegate.getSkeleton(); } + @Override + public void setEvaluator(String evaluator) { + delegate.setEvaluator(evaluator); + onChange(); + } + + @Override + public String getEvaluator() { + return delegate.getEvaluator(); + } + @Override public void setSchemaVersion(int schemaVersion) { delegate.setSchemaVersion(schemaVersion); diff --git a/CloudCoderModelClasses/src/org/cloudcoder/app/shared/model/HashProblemAndTestCaseData.java b/CloudCoderModelClasses/src/org/cloudcoder/app/shared/model/HashProblemAndTestCaseData.java index 8bef07e7..0844a695 100644 --- a/CloudCoderModelClasses/src/org/cloudcoder/app/shared/model/HashProblemAndTestCaseData.java +++ b/CloudCoderModelClasses/src/org/cloudcoder/app/shared/model/HashProblemAndTestCaseData.java @@ -84,9 +84,11 @@ private void hashProblemData(IProblemData problemData) { updateString(problemData.getLicense().toString()); */ - // Digest external library URL and MD5 if they are present + // Digest external library URL, MD5 and problem evaluator if they are present updateStringIfNonEmpty(problemData.getExternalLibraryUrl()); updateStringIfNonEmpty(problemData.getExternalLibraryMD5()); + + updateStringIfNonEmpty(problemData.getEvaluator()); } private void hashTestCaseData(ITestCaseData testCaseData) { diff --git a/CloudCoderModelClasses/src/org/cloudcoder/app/shared/model/IProblemData.java b/CloudCoderModelClasses/src/org/cloudcoder/app/shared/model/IProblemData.java index 675d06a6..101eab5a 100644 --- a/CloudCoderModelClasses/src/org/cloudcoder/app/shared/model/IProblemData.java +++ b/CloudCoderModelClasses/src/org/cloudcoder/app/shared/model/IProblemData.java @@ -2,6 +2,7 @@ // Copyright (C) 2011-2013, Jaime Spacco // Copyright (C) 2011-2013, David H. Hovemeyer // Copyright (C) 2013, York College of Pennsylvania +// Copyright (C) 2015, Eisenberger Andras // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by @@ -68,6 +69,16 @@ public interface IProblemData { */ public abstract String getSkeleton(); + /** + * @param evaluator the evaluator to set + */ + public abstract void setEvaluator(String evaluator); + + /** + * @return the evaluator, the code used to evaluate tests for a submission + */ + public abstract String getEvaluator(); + /** * Set the schema version. * diff --git a/CloudCoderModelClasses/src/org/cloudcoder/app/shared/model/Problem.java b/CloudCoderModelClasses/src/org/cloudcoder/app/shared/model/Problem.java index d815bb22..2970fa66 100644 --- a/CloudCoderModelClasses/src/org/cloudcoder/app/shared/model/Problem.java +++ b/CloudCoderModelClasses/src/org/cloudcoder/app/shared/model/Problem.java @@ -219,10 +219,20 @@ public class Problem extends ProblemData implements IProblem, IModelObject SCHEMA_V11 = ModelObjectSchema.basedOn(SCHEMA_V10) .finishDelta(); + /** + * Description of fields (schema version 12). + * This version incorporates schema changes from version 8 + * of {@link IProblemData}'s schema. + */ + public static final ModelObjectSchema SCHEMA_V12 = + ModelObjectSchema.basedOn(SCHEMA_V11) + .addDeltasFrom(ProblemData.SCHEMA_V8) + .finishDelta(); + /** * Description of fields (current schema version). */ - public static final ModelObjectSchema SCHEMA = SCHEMA_V11; + public static final ModelObjectSchema SCHEMA = SCHEMA_V12; /** * Number of fields. diff --git a/CloudCoderModelClasses/src/org/cloudcoder/app/shared/model/ProblemData.java b/CloudCoderModelClasses/src/org/cloudcoder/app/shared/model/ProblemData.java index d50d8810..b0c0a3b0 100644 --- a/CloudCoderModelClasses/src/org/cloudcoder/app/shared/model/ProblemData.java +++ b/CloudCoderModelClasses/src/org/cloudcoder/app/shared/model/ProblemData.java @@ -50,6 +50,7 @@ public class ProblemData implements Serializable, IProblemData { private String parentHash; private String externalLibraryUrl; private String externalLibraryMD5; + private String evaluator; // Schema version 0 fields @@ -156,6 +157,14 @@ public class ProblemData implements Serializable, IProblemData { public String get(IProblemData obj) { return obj.getExternalLibraryMD5(); } }; + // Schema version 8 fields + + public static final ModelObjectField EVALUATOR = + new ModelObjectField("evaluator", String.class, 2000, ModelObjectIndexType.NONE, ModelObjectField.LITERAL) { + public void set(IProblemData obj, String value) { obj.setEvaluator(value); } + public String get(IProblemData obj) { return obj.getEvaluator(); } + }; + /** * Description of fields (version 0 schema). */ @@ -228,11 +237,18 @@ public class ProblemData implements Serializable, IProblemData { */ public static final ModelObjectSchema SCHEMA_V7 = ModelObjectSchema.basedOn(SCHEMA_V6) .finishDelta(); + + /** + * Description of fields (schema version 8). + */ + public static final ModelObjectSchema SCHEMA_V8 = ModelObjectSchema.basedOn(SCHEMA_V7) + .addAfter(EXTERNAL_LIBRARY_MD5, EVALUATOR) + .finishDelta(); /** * Description of fields (current schema). */ - public static final ModelObjectSchema SCHEMA = SCHEMA_V7; + public static final ModelObjectSchema SCHEMA = SCHEMA_V8; /** * Constructor. @@ -338,6 +354,22 @@ public boolean hasSkeleton() { return skeleton != null; } + /* (non-Javadoc) + * @see org.cloudcoder.app.shared.model.ProblemData#setEvaluator(java.lang.String) + */ + @Override + public void setEvaluator(String evaluator) { + this.evaluator = evaluator; + } + + /* (non-Javadoc) + * @see org.cloudcoder.app.shared.model.ProblemData#getEvaluator() + */ + @Override + public String getEvaluator() { + return evaluator; + } + /* (non-Javadoc) * @see org.cloudcoder.app.shared.model.ProblemData#setSchemaVersion(int) */ @@ -484,6 +516,7 @@ public void copyFrom(ProblemData other) { this.parentHash = other.parentHash; this.externalLibraryUrl = other.externalLibraryUrl; this.externalLibraryMD5 = other.externalLibraryMD5; + this.evaluator = other.evaluator; } @Override @@ -505,7 +538,8 @@ public boolean equals(Object obj) { && ModelObjectUtil.equals(this.license, other.license) && ModelObjectUtil.equals(this.parentHash, other.parentHash) && ModelObjectUtil.equals(this.externalLibraryUrl, other.externalLibraryUrl) - && ModelObjectUtil.equals(this.externalLibraryMD5, other.externalLibraryMD5); + && ModelObjectUtil.equals(this.externalLibraryMD5, other.externalLibraryMD5) + && ModelObjectUtil.equals(this.evaluator, other.evaluator); } /* @@ -527,5 +561,6 @@ public static void initEmpty(ProblemData empty) { empty.setTimestampUtc(System.currentTimeMillis()); empty.setLicense(ProblemLicense.NOT_REDISTRIBUTABLE); empty.setParentHash(""); + empty.setEvaluator(""); } } \ No newline at end of file diff --git a/CloudCoderModelClasses/src/org/cloudcoder/app/shared/model/RepoProblem.java b/CloudCoderModelClasses/src/org/cloudcoder/app/shared/model/RepoProblem.java index 5009beb4..edfceee1 100644 --- a/CloudCoderModelClasses/src/org/cloudcoder/app/shared/model/RepoProblem.java +++ b/CloudCoderModelClasses/src/org/cloudcoder/app/shared/model/RepoProblem.java @@ -127,10 +127,20 @@ public class RepoProblem extends ProblemData implements IModelObject SCHEMA_V7 = ModelObjectSchema.basedOn(SCHEMA_V6) .finishDelta(); + /** + * Description of fields (schema version 8). + * This version incorporates schema changes from version 8 + * of {@link IProblemData}'s schema. + */ + public static final ModelObjectSchema SCHEMA_V8 = + ModelObjectSchema.basedOn(SCHEMA_V7) + .addDeltasFrom(ProblemData.SCHEMA_V8) + .finishDelta(); + /** * Description of fields (current schema version). */ - public static final ModelObjectSchema SCHEMA = SCHEMA_V7; + public static final ModelObjectSchema SCHEMA = SCHEMA_V8; /** Number of fields. */ public static final int NUM_FIELDS = SCHEMA.getNumFields(); diff --git a/CloudCoderModelClasses/src/org/cloudcoder/app/shared/util/EvaluatorUtil.java b/CloudCoderModelClasses/src/org/cloudcoder/app/shared/util/EvaluatorUtil.java new file mode 100644 index 00000000..5dc4581d --- /dev/null +++ b/CloudCoderModelClasses/src/org/cloudcoder/app/shared/util/EvaluatorUtil.java @@ -0,0 +1,65 @@ +// CloudCoder - a web-based pedagogical programming environment +// Copyright (C) 2015, Andras Eisenberger +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package org.cloudcoder.app.shared.util; + +import org.cloudcoder.app.shared.model.IProblem; +import org.cloudcoder.app.shared.model.ProblemType; + +/** + * Utility methods problem evaluators + * + * @author Andras Eisenberger + */ +public class EvaluatorUtil { + /** + * Determine whether custom evaluators can be used for {@link ProblemType}. + * + * @param problemType the problem type + * @return the AceEditorMode for the Language, or null if the Language is not known + */ + public static boolean isEvaluatorUsedForProblemType(ProblemType problemType) { + switch (problemType) { + case PYTHON_FUNCTION: + return true; + default: + return false; + } + } + + /** + * Generate the default evaluator for the given problem. Returns empty + * {@link String} for problem types which don't use custom evaluators. + */ + public static String getDefaultEvaluator(IProblem problem) { + switch (problem.getProblemType()) { + case PYTHON_FUNCTION: + return getDefaultPythonFunctionEvaluator(problem); + default: + return ""; + } + } + + /** + * Generate the default evaluator for a python problem + */ + public static String getDefaultPythonFunctionEvaluator(IProblem problem) { + return "def _eval(_input, _expected):\n" + + " _output=" + problem.getTestname() + "(*_input)\n" + + " _result=(_expected == _output) if (type(_output) != float and type(_expected) != float) else (math.fabs(_output-_expected) < 0.00001)\n" + + " return (_result, _output)\n"; + } +} diff --git a/CloudCoderModelClassesPersistence/src/org/cloudcoder/app/server/persist/CreateSampleData.java b/CloudCoderModelClassesPersistence/src/org/cloudcoder/app/server/persist/CreateSampleData.java index 9b931582..014a711c 100644 --- a/CloudCoderModelClassesPersistence/src/org/cloudcoder/app/server/persist/CreateSampleData.java +++ b/CloudCoderModelClassesPersistence/src/org/cloudcoder/app/server/persist/CreateSampleData.java @@ -105,6 +105,7 @@ public static void populateSampleProblemData(IProblemData problemData) { problemData.setParentHash(""); problemData.setExternalLibraryUrl(""); problemData.setExternalLibraryMD5(""); + problemData.setEvaluator(""); } public static void populateSampleCFunctionProblem(IProblem problem, int courseId) { @@ -140,6 +141,7 @@ private static void populateSampleCFunctionProblemData(IProblemData problemData) problemData.setParentHash(""); problemData.setExternalLibraryUrl(""); problemData.setExternalLibraryMD5(""); + problemData.setEvaluator(""); } public static void populateSampleCFunctionTestCases(ITestCase[] testCases, int problemId) { diff --git a/default.deps b/default.deps index 3b779d6f..0a82d47e 100644 --- a/default.deps +++ b/default.deps @@ -21,8 +21,8 @@ http://repo1.maven.org/maven2/commons-io/commons-io/2.1/commons-io-2.1.jar CloudCoderLoadTester/lib/commons-io-2.1.jar CloudCoderLogging/lib/commons-io-2.1.jar CloudCoderAnalysis/lib/commons-io-2.1.jar -http://repo1.maven.org/maven2/org/python/jython-standalone/2.5.3/jython-standalone-2.5.3.jar - CloudCoderBuilder2/lib/jython-standalone-2.5.3.jar +http://repo1.maven.org/maven2/org/python/jython-standalone/2.7.0/jython-standalone-2.7.0.jar + CloudCoderBuilder2/lib/jython-standalone-2.7.0.jar https://s3.amazonaws.com/org.cloudcoder.daemon.binaries/daemon-${daemon_version}.jar CloudCoderJetty/lib/daemon/daemon-${daemon_version}.jar CloudCoderBuilder2/lib/daemon-${daemon_version}.jar