├── .gitignore
├── LICENSE
├── README.md
├── build.sbt
├── design
├── datamodel.pdf
└── db-layout.pdf
├── formatterPreferences.properties
├── project
├── build.properties
└── plugins.sbt
├── sbt
├── sbt-launch-0.12.1.jar
├── sbt-launch.jar
├── sbt.bat
└── src
├── main
├── resources
│ ├── .keep
│ ├── logback.xml
│ └── props
│ │ ├── default.props.template
│ │ ├── production.default.props.template
│ │ └── test.default.props
├── scala
│ ├── bootstrap
│ │ └── liftweb
│ │ │ └── Boot.scala
│ └── com
│ │ └── automatatutor
│ │ ├── BuchiGame.scala
│ │ ├── lib
│ │ ├── Binder.scala
│ │ ├── Config.scala
│ │ ├── DownloadHelper.scala
│ │ ├── SOAPConnection.scala
│ │ ├── TableHelper.scala
│ │ └── TemplateLoader.scala
│ │ ├── model
│ │ ├── .keep
│ │ ├── Attendance.scala
│ │ ├── Course.scala
│ │ ├── DFAConstructionProblem.scala
│ │ ├── NFAConstructionProblem.scala
│ │ ├── NFAToDFAProblem.scala
│ │ ├── PosedProblem.scala
│ │ ├── PosedProblemSet.scala
│ │ ├── Problem.scala
│ │ ├── ProblemSet.scala
│ │ ├── PumpingLemmaProblem.scala
│ │ ├── RegExConstructionProblem.scala
│ │ ├── Role.scala
│ │ ├── SolutionAttempt.scala
│ │ └── User.scala
│ │ ├── renderer
│ │ ├── CourseRenderer.scala
│ │ ├── ProblemRenderer.scala
│ │ ├── ProblemSetRenderer.scala
│ │ └── UserRenderer.scala
│ │ └── snippet
│ │ ├── Courses.scala
│ │ ├── Courses.scala~
│ │ ├── DFAProblemSnippet.scala
│ │ ├── Identity.scala
│ │ ├── NFAProblemSnippet.scala
│ │ ├── NFAToDFAProblemSnippet.scala
│ │ ├── PracticeSets.scala
│ │ ├── ProblemSets.scala
│ │ ├── ProblemSnippet.scala
│ │ ├── PumpingLemmaProblemSnippet.scala
│ │ ├── RegExProblemSnippet.scala
│ │ └── Users.scala
└── webapp
│ ├── WEB-INF
│ └── web.xml
│ ├── about
│ ├── index.html
│ ├── privacy-statement.html
│ └── terms-of-service.html
│ ├── applets
│ └── buchi-game.html
│ ├── buchi-solving
│ ├── applet.html
│ └── create.html
│ ├── courses
│ ├── chooseproblemset.html
│ ├── create.html
│ ├── index.html
│ ├── manage.html
│ ├── poseproblemset.html
│ ├── show.html
│ ├── solveproblem.html
│ └── solveproblemset.html
│ ├── description-to-nfa-problem
│ ├── applet.html
│ ├── create.html
│ ├── edit.html
│ └── solve.html
│ ├── description-to-regex-problem
│ ├── create.html
│ ├── edit.html
│ └── solve.html
│ ├── dfa-construction
│ ├── applet.html
│ ├── create.html
│ ├── edit.html
│ └── solve.html
│ ├── images
│ ├── ajax-loader.gif
│ └── trash_bin.png
│ ├── index.html
│ ├── index.html~
│ ├── javascript
│ ├── README.md
│ ├── alphabetUtil.js
│ ├── includeBuchiGame.js
│ ├── includeDFA.js
│ ├── includeDFANFA.js
│ ├── includeNFA.js
│ ├── includePL.js
│ ├── interface.js
│ ├── lib
│ │ ├── browser.js
│ │ ├── contextmenu.js
│ │ ├── d3.min.js
│ │ ├── jquery-2.1.1.min.js
│ │ ├── jquery-ui-timepicker-addon.js
│ │ ├── jquery-ui.js
│ │ └── jquery.contextmenu.js
│ └── pumpinglemma.js
│ ├── nfa-to-dfa-problem
│ ├── applet-nfa.html
│ ├── applet-nfa.html~
│ ├── applet.html
│ ├── applet.html~
│ ├── create.html
│ ├── create.html~
│ ├── edit.html
│ ├── solve.html
│ └── solve.html~
│ ├── practicesets
│ ├── index.html
│ └── solve.html
│ ├── preview
│ ├── index.html
│ └── solve.html
│ ├── problems
│ ├── create.html
│ ├── edit.html
│ ├── index.html
│ └── solve.html
│ ├── problemsets
│ ├── addproblem.html
│ ├── create.html
│ ├── edit.html
│ ├── index.html
│ └── poseproblem.html
│ ├── pumping-lemma-problem
│ ├── create.html
│ └── solve.html
│ ├── static
│ └── index.html
│ ├── stylesheets
│ ├── images
│ │ ├── ui-bg_diagonals-thick_18_b81900_40x40.png
│ │ ├── ui-bg_diagonals-thick_20_666666_40x40.png
│ │ ├── ui-bg_flat_10_000000_40x100.png
│ │ ├── ui-bg_glass_100_f6f6f6_1x400.png
│ │ ├── ui-bg_glass_100_fdf5ce_1x400.png
│ │ ├── ui-bg_glass_65_ffffff_1x400.png
│ │ ├── ui-bg_highlight-soft_100_eeeeee_1x100.png
│ │ ├── ui-bg_highlight-soft_75_cccccc_1x100.png
│ │ ├── ui-bg_highlight-soft_75_ffe45c_1x100.png
│ │ ├── ui-icons_222222_256x240.png
│ │ ├── ui-icons_228ef1_256x240.png
│ │ ├── ui-icons_454545_256x240.png
│ │ ├── ui-icons_ef8c08_256x240.png
│ │ ├── ui-icons_ffd27a_256x240.png
│ │ └── ui-icons_ffffff_256x240.png
│ ├── interface.css
│ ├── jquery-ui.css
│ ├── proto2.css
│ └── pumpinglemma.css
│ ├── templates-hidden
│ ├── default.html
│ └── wizard-all.html
│ └── users
│ ├── edit.html
│ └── index.html
└── test
├── resources
├── .keep
└── logback-test.xml
└── scala
└── com
└── automatatutor
├── BootedLiftScope.scala
├── lib
└── BinderTest.scala
├── model
├── CourseAttendanceTest.scala
└── UserValidationTest.scala
├── renderer
└── UserRendererTest.scala
└── snippet
└── UsersTest.scala
/.gitignore:
--------------------------------------------------------------------------------
1 | .cache-main
2 | .cache-tests
3 | .classpath
4 | .project
5 | .settings/
6 | console.devmode.log
7 | develop.db.mv.db
8 | project/project/
9 | project/target/
10 | target/
11 | src/main/resources/props/default.props
12 | src/main/resources/props/production.default.props
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Loris D'Antoni
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/build.sbt:
--------------------------------------------------------------------------------
1 | // Taken nearly verbatim from lift_blank example from https://github.com/lift/lift_25_sbt/tarball/master
2 | name := "AutomataTutor"
3 |
4 | version := "0.0.1"
5 |
6 | organization := "com.automatatutor"
7 |
8 | scalaVersion := "2.10.4"
9 |
10 | fork in Keys.test := true
11 |
12 | parallelExecution in Keys.test := false
13 |
14 | seq(webSettings :_*)
15 |
16 | unmanagedResourceDirectories in Test <+= (baseDirectory) { _ / "src/main/webapp" }
17 |
18 | scalacOptions ++= Seq("-deprecation", "-unchecked")
19 |
20 | libraryDependencies ++= {
21 | Seq(
22 | "net.liftweb" %% "lift-webkit" % "2.5.1" % "compile",
23 | "net.liftweb" %% "lift-mapper" % "2.5.1" % "compile",
24 | "net.liftweb" %% "lift-testkit" % "2.5.1" % "compile",
25 | "net.liftmodules" %% "lift-jquery-module_2.5" % "2.4",
26 | "org.eclipse.jetty" % "jetty-webapp" % "9.2.5.v20141112" % "container,test",
27 | "org.eclipse.jetty.orbit" % "javax.servlet" % "3.0.0.v201112011016" % "container,test" artifacts Artifact("javax.servlet", "jar", "jar"),
28 | "ch.qos.logback" % "logback-classic" % "1.1.2",
29 | "org.specs2" %% "specs2-core" % "2.4.15" % "test",
30 | "org.postgresql" % "postgresql" % "9.3-1102-jdbc41",
31 | "com.h2database" % "h2" % "1.4.182" % "container,test",
32 | "org.seleniumhq.selenium" % "selenium-java" % "2.44.0" % "test"
33 | )
34 | }
35 |
--------------------------------------------------------------------------------
/design/datamodel.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AutomataTutor/automatatutor-frontend/5d7b2f3639ec544bead7ca28995aecb87738c421/design/datamodel.pdf
--------------------------------------------------------------------------------
/design/db-layout.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AutomataTutor/automatatutor-frontend/5d7b2f3639ec544bead7ca28995aecb87738c421/design/db-layout.pdf
--------------------------------------------------------------------------------
/formatterPreferences.properties:
--------------------------------------------------------------------------------
1 | #Scalariform formatter preferences
2 | #Thu Jun 02 11:30:04 CEST 2016
3 | alignParameters=true
4 | formatXml=true
5 | preserveDanglingCloseParenthesis=true
6 | spaceInsideBrackets=false
7 | indentWithTabs=false
8 | spaceInsideParentheses=false
9 | multilineScaladocCommentsStartOnFirstLine=false
10 | alignSingleLineCaseStatements=true
11 | compactStringConcatenation=false
12 | placeScaladocAsterisksBeneathSecondAsterisk=false
13 | indentPackageBlocks=true
14 | compactControlReadability=false
15 | spacesWithinPatternBinders=false
16 | alignSingleLineCaseStatements.maxArrowIndent=40
17 | doubleIndentClassDeclaration=false
18 | preserveSpaceBeforeArguments=false
19 | spaceBeforeColon=true
20 | rewriteArrowSymbols=false
21 | indentLocalDefs=true
22 | indentSpaces=2
23 |
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=0.13.7
2 |
3 |
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "0.9.0")
2 |
3 | //Enable the sbt eclipse plugin
4 | addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.5.0")
5 |
--------------------------------------------------------------------------------
/sbt:
--------------------------------------------------------------------------------
1 | JAVA7PATH="/Library/Java/JavaVirtualMachines/jdk1.8.0_05.jdk/Contents/Home/bin/java" # Path to an installation of Java7. Later versions do not work with sbt0.12
2 | $JAVA7PATH -version
3 | $JAVA7PATH -Xmx1024M -Xss16M -XX:MaxPermSize=512m -XX:+CMSClassUnloadingEnabled -jar `dirname $0`/sbt-launch.jar "$@"
4 |
--------------------------------------------------------------------------------
/sbt-launch-0.12.1.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AutomataTutor/automatatutor-frontend/5d7b2f3639ec544bead7ca28995aecb87738c421/sbt-launch-0.12.1.jar
--------------------------------------------------------------------------------
/sbt-launch.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AutomataTutor/automatatutor-frontend/5d7b2f3639ec544bead7ca28995aecb87738c421/sbt-launch.jar
--------------------------------------------------------------------------------
/sbt.bat:
--------------------------------------------------------------------------------
1 | set SCRIPT_DIR=%~dp0
2 | java -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m -Xmx1024M -Xss2M -jar "%SCRIPT_DIR%\sbt-launch.jar" %*
3 |
--------------------------------------------------------------------------------
/src/main/resources/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AutomataTutor/automatatutor-frontend/5d7b2f3639ec544bead7ca28995aecb87738c421/src/main/resources/.keep
--------------------------------------------------------------------------------
/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
5 |
6 |
7 |
8 | console.devmode.log
9 | true
10 |
11 | %-4relative [%thread] %-5level %logger{35} - %msg%n
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/main/resources/props/default.props.template:
--------------------------------------------------------------------------------
1 | db.driver=org.h2.Driver
2 | db.url=jdbc:h2:./develop.db
3 | grader.url=http://automatagrader.com/Service1.asmx
4 | grader.methodnamespace=http://automatagrader.com
5 |
6 | mail.transport.protocol=smtp
7 | mail.smtp.host=email-smtp.us-west-2.amazonaws.com
8 | mail.smtp.port=25
9 | mail.smtp.starttls.enable=true
10 | mail.smtp.auth=true
11 | mail.user=
12 | mail.password=
13 |
14 | layout.usersperpage = 50
15 |
--------------------------------------------------------------------------------
/src/main/resources/props/production.default.props.template:
--------------------------------------------------------------------------------
1 | grader.url=http://automatagrader.com/Service1.asmx
2 | grader.methodnamespace=http://automatagrader.com
3 | admin.email=admin@automatatutor.com
4 | admin.password=Coshae2O
5 | admin.firstname=Donald
6 | admin.lastname=Knuth
7 | db.driver=org.postgresql.Driver
8 | db.url=jdbc:postgresql:dileepk_tutordb
9 | db.user=
10 | db.password=
11 |
12 | //Don’t copy these ones, they currently don’t work
13 | mail.transport.protocol=smtp
14 | mail.smtp.host=bobcat.arvixe.com
15 | mail.smtp.port=587
16 | mail.smtp.starttls.enable=true
17 | mail.smtp.auth=true
18 | mail.user=info@automatatutor.com
19 | mail.password=iwkdt336q1pz
20 |
--------------------------------------------------------------------------------
/src/main/resources/props/test.default.props:
--------------------------------------------------------------------------------
1 | db.driver=org.h2.Driver
2 | db.url=jdbc:h2:mem:
3 | grader.url=http://127.0.0.1:53861/Service1.asmx
4 | grader.methodnamespace=http://automatagrader.com
5 |
--------------------------------------------------------------------------------
/src/main/scala/bootstrap/liftweb/Boot.scala:
--------------------------------------------------------------------------------
1 | package bootstrap.liftweb
2 |
3 | import net.liftweb._
4 | import util._
5 | import Helpers._
6 | import common._
7 | import http._
8 | import sitemap._
9 | import Loc._
10 | import net.liftmodules.JQueryModule
11 | import net.liftweb.http.js.jquery._
12 | import net.liftweb.db.{DB,StandardDBVendor,DefaultConnectionIdentifier}
13 | import net.liftweb.mapper.Schemifier
14 | import com.automatatutor.model._
15 | import java.io.FileInputStream
16 | import net.liftweb.util.Mailer
17 | import javax.mail.{Authenticator,PasswordAuthentication}
18 | import com.automatatutor.lib.Config
19 |
20 | /**
21 | * A class that's instantiated early and run. It allows the application
22 | * to modify lift's environment
23 | */
24 | class Boot {
25 | def boot {
26 |
27 | Mailer.authenticator = Full( new Authenticator {
28 | override def getPasswordAuthentication =
29 | new PasswordAuthentication(Config.mail.user.get, Config.mail.password.get)
30 | } )
31 |
32 | // where to search snippet
33 | LiftRules.addToPackages("com.automatatutor")
34 |
35 | if(!DB.jndiJdbcConnAvailable_?) {
36 | DB.defineConnectionManager(DefaultConnectionIdentifier, Config.db.get)
37 | LiftRules.unloadHooks.append(Config.db.get.closeAllConnections_!)
38 | }
39 |
40 | Schemifier.schemify(true, Schemifier.infoF _,
41 | User, Attendance, Course, PosedProblem, PosedProblemSet, Problem, ProblemType,
42 | DFAConstructionProblem, DFAConstructionSolutionAttempt,
43 | NFAConstructionProblem, NFAConstructionSolutionAttempt,
44 | NFAToDFAProblem, NFAToDFASolutionAttempt,
45 | RegExConstructionProblem, RegexConstructionSolutionAttempt,
46 | PumpingLemmaProblem, PumpingLemmaSolutionAttempt,
47 | ProblemSet, Role, SolutionAttempt, Supervision)
48 |
49 | StartupHooks.hooks map (hook => hook())
50 |
51 | val loggedInPredicate = If(() => User.loggedIn_?, () => RedirectResponse("/index"))
52 | val notLoggedInPredicate = If(() => !User.loggedIn_?, () => RedirectResponse("/index"))
53 | val isInstructorPredicate = If(() => User.currentUser.map(_.hasInstructorRole) openOr false, () => { S.error("This page is only available to instructors"); RedirectResponse("/index") })
54 | val isAdminPredicate = If(() => User.currentUser.map(_.hasAdminRole) openOr false, () => { S.error("This page is only available to admins"); RedirectResponse("/index") })
55 |
56 | // Build SiteMap
57 | val entries = List(
58 | Menu.i("Home") / "index",
59 |
60 | Menu.i("Try it Now!") / "preview" / "index" >> notLoggedInPredicate submenus(
61 | Menu.i("Solve Preview") /"preview" / "solve" >> Hidden),
62 |
63 | Menu.i("Practice Problem Sets") / "practicesets" / "index" >> loggedInPredicate submenus(
64 | Menu.i("Solve Practice Set Problem") /"practicesets" / "solve" >> Hidden),
65 |
66 | Menu.i("Problems") / "problems" / "index" >> isInstructorPredicate submenus(
67 | Menu.i("Create Problem") / "problems" / "create" >> Hidden,
68 | Menu.i("Edit Problem") / "problems" / "edit" >> Hidden),
69 |
70 | Menu.i("Problem Sets") / "problemsets" / "index" >> isInstructorPredicate submenus(
71 | Menu.i("Create Problem Set") / "problemsets" / "create" >> Hidden,
72 | Menu.i("Edit Problem Set") / "problemsets" / "edit" >> Hidden,
73 | Menu.i("Append Problem to Problem Set") / "problemsets" / "addproblem" >> Hidden,
74 | Menu.i("Pose Problem to Problem Set") / "problemsets" / "poseproblem" >> Hidden),
75 |
76 | Menu.i("Courses") / "courses" / "index" >> loggedInPredicate submenus(
77 | Menu.i("Show Course") / "courses" / "show" >> Hidden,
78 | Menu.i("Solve Problem Set") / "courses" / "solveproblemset" >> Hidden,
79 | Menu.i("Solve Practice Problem") / "courses" / "solvepractice" >> Hidden,
80 | Menu.i("Solve Problem") / "courses" / "solveproblem" >> Hidden,
81 | Menu.i("Create Course") / "courses" / "create" >> Hidden,
82 | Menu.i("Manage Course") / "courses" / "manage" >> Hidden,
83 | Menu.i("Choose Problem Set To Add") / "courses" / "chooseproblemset" >> Hidden,
84 | Menu.i("Pose Problem Set") / "courses" / "poseproblemset" >> Hidden),
85 |
86 | Menu.i("Users") / "users" / "index" >> isAdminPredicate submenus(
87 | Menu.i("Edit User") / "users" / "edit" >> Hidden)
88 |
89 | ) ::: User.sitemap ::: List(
90 | Menu.i("About") / "about" / "index" submenus(
91 | Menu.i("Terms of service") / "about" / "terms-of-service", // >> Hidden,
92 | Menu.i("Privacy statement") / "about" / "privacy-statement") // >> Hidden )
93 | )
94 |
95 | // set the sitemap. Note if you don't want access control for
96 | // each page, just comment this line out.
97 | LiftRules.setSiteMap(SiteMap(entries : _*))
98 |
99 | //Show the spinny image when an Ajax call starts
100 | LiftRules.ajaxStart =
101 | Full(() => LiftRules.jsArtifacts.show("ajax-loader").cmd)
102 |
103 | // Make the spinny image go away when it ends
104 | LiftRules.ajaxEnd =
105 | Full(() => LiftRules.jsArtifacts.hide("ajax-loader").cmd)
106 |
107 | // Force the request to be UTF-8
108 | LiftRules.early.append(_.setCharacterEncoding("UTF-8"))
109 |
110 | // Use HTML5 for rendering
111 | LiftRules.htmlProperties.default.set((r: Req) =>
112 | new Html5Properties(r.userAgent))
113 |
114 | //Init the jQuery module, see http://liftweb.net/jquery for more information.
115 | LiftRules.jsArtifacts = JQueryArtifacts
116 | JQueryModule.InitParam.JQuery=JQueryModule.JQuery172
117 | JQueryModule.init()
118 |
119 | }
120 | }
121 |
122 | trait StartupHook {
123 | /* Since code in the trait is executed during the instantiation of the object carrying that trait, every object that has this trait
124 | * registers itself in the companion object and can easily be executed during
125 | */
126 | StartupHooks.hooks += this.onStartup
127 |
128 | def onStartup(): Unit
129 | }
130 |
131 | private object StartupHooks {
132 | val hooks = collection.mutable.ListBuffer[() => Unit]()
133 | }
--------------------------------------------------------------------------------
/src/main/scala/com/automatatutor/BuchiGame.scala:
--------------------------------------------------------------------------------
1 | package com.automatatutor
2 |
3 | import scala.xml.NodeSeq
4 | import com.automatatutor.lib.TemplateLoader
5 | import com.automatatutor.model.DFAConstructionProblem
6 | import com.automatatutor.model.Problem
7 | import com.automatatutor.model.SolutionAttempt
8 | import net.liftweb.http.SHtml
9 | import net.liftweb.mapper.IdPK
10 | import net.liftweb.mapper.LongKeyedMapper
11 | import net.liftweb.mapper.LongKeyedMetaMapper
12 | import net.liftweb.mapper.MappedLongForeignKey
13 | import net.liftweb.mapper.MappedText
14 | import net.liftweb.util.Helpers
15 | import net.liftweb.util.Helpers.strToSuperArrowAssoc
16 | import com.automatatutor.lib.Renderer
17 | import com.automatatutor.lib.Binding
18 | import com.automatatutor.lib.Binder
19 | import com.automatatutor.snippet.ProblemSnippet
20 | import java.util.Date
21 | import net.liftweb.common.Box
22 |
23 | object BuchiGameSolving {
24 | object SnippetAdapter extends ProblemSnippet {
25 | /** Should produce a NodeSeq that allows the user to create a new problem of
26 | * the type. This NodeSeq also has to handle creation of the unspecific
27 | * {@link Problem}. */
28 | def renderCreate( createUnspecificProb : (String, String) => Problem, returnFunc : () => Nothing ) : NodeSeq = BuchiGameSolving.renderCreate(createUnspecificProb, returnFunc)
29 |
30 | /** Should produce a NodeSeq that allows the user to edit the problem
31 | * associated with the given unspecific problem. */
32 | def renderEdit : Box[((Problem, () => Nothing) => NodeSeq)] = ???
33 |
34 | /** Should produce a NodeSeq that allows the user a try to solve the problem
35 | * associated with the given unspecific problem. The function
36 | * recordSolutionAttempt must be called once for every solution attempt
37 | * and expects the grade of the attempt (which must be <= maxGrade) and the
38 | * time the attempt was made. After finishing the solution attempt, the
39 | * snippet should send the user back to the overview of problems in the
40 | * set by calling returnToSet */
41 | def renderSolve ( problem : Problem, maxGrade : Long, lastAttempt : Box[SolutionAttempt],
42 | recordSolutionAttempt: (Int, Date) => SolutionAttempt,
43 | returnToSet : () => Unit , attemptsLeft : () => Int, bestGrade : () => Int) : NodeSeq = ???
44 |
45 | /** Is called before the given unspecific problem is deleted from the database.
46 | * This method should delete everything associated with the given unspecific
47 | * problem from the database */
48 | def onDelete( problem : Problem ) : Unit = ???
49 |
50 | }
51 | class Task extends LongKeyedMapper[Task] with IdPK {
52 | def getSingleton = Task
53 |
54 | protected object generalProblem extends MappedLongForeignKey(this, Problem)
55 | protected object arena extends MappedText(this)
56 |
57 | def setGeneralProblem ( problem : Problem) : Task = this.generalProblem(problem)
58 | def getGeneralProblem : Problem = this.generalProblem.obj openOrThrowException "Every Task must have a generalProblem"
59 |
60 | def setArena ( game : String ) = this.arena(game)
61 | def setArena ( game : NodeSeq ) = this.arena(game.mkString)
62 | def getArena : String = this.arena.get
63 | }
64 | object Task extends Task with LongKeyedMetaMapper[Task]
65 |
66 | class Solution extends LongKeyedMapper[Solution] with IdPK {
67 | def getSingleton = Solution
68 |
69 | protected object generalSolution extends MappedLongForeignKey(this, SolutionAttempt)
70 | protected object solution extends MappedText(this)
71 | }
72 | object Solution extends Solution with LongKeyedMetaMapper[Solution]
73 |
74 | def renderCreate( createUnspecificProb : (String, String) => Problem, returnFunc : () => Nothing ) : NodeSeq = {
75 | var shortDescription : String = ""
76 | var longDescription : String = ""
77 | var arena : String = ""
78 |
79 | object canvasRenderer extends Renderer { def render = {
80 | ++
81 | SHtml.hidden(arena = _, "", "id" -> "arenaField")
82 | } }
83 | object submitButtonRenderer extends Renderer { def render = {
84 | SHtml.submit("Create", create, "onClick" -> "document.getElementById('arenaField').value = Editor.canvas.exportAutomaton()")
85 | } }
86 | object shortDescRenderer extends Renderer { def render = {
87 | SHtml.text("", shortDescription = _)
88 | } }
89 | object longDescRenderer extends Renderer { def render = {
90 | SHtml.textarea("", longDescription = _, "cols" -> "80", "rows" -> "5")
91 | } }
92 |
93 | def create() = {
94 | val unspecificProblem = createUnspecificProb(shortDescription, longDescription)
95 |
96 | val specificProblem : Task = Task.create
97 | specificProblem.setGeneralProblem(unspecificProblem).setArena(arena)
98 | specificProblem.save
99 |
100 | returnFunc()
101 | }
102 |
103 | val arenaBinding = new Binding("arena", canvasRenderer)
104 | val submitBinding = new Binding("submit", submitButtonRenderer)
105 |
106 | val shortDescBinding = new Binding("shortdescription", shortDescRenderer)
107 | val longDescBinding = new Binding("longdescription", longDescRenderer)
108 |
109 | val template : NodeSeq = TemplateLoader.BuchiSolving.create
110 | new Binder("createform", arenaBinding, submitBinding, shortDescBinding, longDescBinding).bind(template)
111 | }
112 |
113 | }
--------------------------------------------------------------------------------
/src/main/scala/com/automatatutor/lib/Binder.scala:
--------------------------------------------------------------------------------
1 | package com.automatatutor.lib
2 |
3 | import scala.xml.NodeSeq
4 | import net.liftweb.util.Helpers
5 | import net.liftweb.util.Helpers.TheBindParam
6 | import net.liftweb.util.Helpers.BindParam
7 |
8 | /**
9 | * At several points during processing snippets, we need to create NodeSeqs to plug into templates.
10 | * These NodeSeqs are usually created ad hoc, querying whatever data they need and plugging them in using net.liftweb.util.Helpers.bind.
11 | * In order to move away from this ad-hoc-fashion, we implement this Binding-framework
12 | * that modularizes the individual parts of this task in the hope that we may divide the different concerns when rendering such NodeSeqs.
13 | *
14 | * There are three central parts to this framework: Renderers, Bindings, and the Binder.
15 | * The goal of the user is most often to create a Binder, on which they can then call the bind(...) method in our to obtain the template with the relevant parts replaced
16 | * Each Binder contains several Bindings telling it a) which elements to replace, and b) what to replace them with
17 | * One such pair of (what to replace) and (with what) is encapsulated in a Binding.
18 | * Each Binding contains a Renderer that tells the Binding what to replace the given location with.
19 | *
20 | * For more detail, please refer to the documentation of the individual classes.
21 | */
22 |
23 | abstract trait AbstractRenderer {
24 | /** Called by the Binding when it needs to produce a NodeSeq */
25 | def render( input : NodeSeq ) : NodeSeq
26 | }
27 |
28 | /**
29 | * The most general kind of renderer.
30 | * Is given the NodeSeq it shall replace.
31 | */
32 | trait DynamicRenderer extends AbstractRenderer {
33 | def render( input : NodeSeq ) : NodeSeq
34 | }
35 |
36 | /**
37 | * A static renderer that only returns a constant NodeSeq.
38 | * Useful, for example, to produce labels or buttons with constant label.
39 | * Should be used very rarely, however, since there should be as much freedom
40 | * as possible in the templates, so nearly every Renderer should react at
41 | * least to some parameters given in the template.
42 | */
43 | trait Renderer extends AbstractRenderer {
44 | protected def render : NodeSeq
45 | def render ( input : NodeSeq ) : NodeSeq = { this.render }
46 | }
47 |
48 | /**
49 | * Quite often it is necessary to render some data for the user.
50 | * This usually amounts to binding certain information of the data to the
51 | * template and doing so for each element of data found in the database.
52 | * This Renderer encapsulates this process by defining an abstract method
53 | * for implementation that is called for every data object in the given sequence.
54 | *
55 | * Can be used, for example, like
56 | * new DataRenderer(Seq(1,2,3)) { override def render ( template : NodeSeq, data : Int ) : NodeSeq { data } }
57 | * which would return the NodeSeq 1 2 3 , while ignoring the template
58 | */
59 | abstract class DataRenderer[T](data : Seq[T]) extends AbstractRenderer {
60 | protected def render ( template : NodeSeq, data : T ) : NodeSeq
61 | def render( template : NodeSeq ) : NodeSeq = data.flatMap { data => render(template, data) }
62 | }
63 |
64 | /**
65 | * Encapsulates the "where" and the "what" of binding content to a template.
66 | * In order to bind the result of `renderer` to the tag `target`, define a Binding like
67 | * new Binding("target", renderer)
68 | */
69 | class Binding(val target : String, val renderer : AbstractRenderer)
70 |
71 | /**
72 | * Encapsulates several bindings and binds them all to all tags that match that binding with a given prefix.
73 | * For example,
74 | * new Binder("testNs", new Binding("target", renderer)).bind( )
75 | * would return the result of renderer, while
76 | * new Binder("otherNs", new Binding("target", renderer)).bind( )
77 | * would return .
78 | */
79 | class Binder(prefix : String, bindings : Binding*) {
80 | def this(bindings : Binding*) = this("", bindings : _*)
81 |
82 | def bind(template : NodeSeq) : NodeSeq = {
83 | val bindParams : Seq[BindParam] = bindings map ( { binding =>
84 | val filteredTemplate = (template \\ binding.target).filter( _.prefix == prefix )
85 | new TheBindParam(binding.target, binding.renderer.render(filteredTemplate)).asInstanceOf[BindParam]
86 | } )
87 | Helpers.bind(prefix, template, bindParams : _*)
88 | }
89 | }
--------------------------------------------------------------------------------
/src/main/scala/com/automatatutor/lib/Config.scala:
--------------------------------------------------------------------------------
1 | package com.automatatutor.lib
2 |
3 | import net.liftweb.common.Box
4 | import net.liftweb.common.Empty
5 | import net.liftweb.common.Full
6 | import net.liftweb.util.Props
7 | import net.liftweb.db.StandardDBVendor
8 | import net.liftweb.db.ConnectionManager
9 |
10 | object Config {
11 |
12 | abstract class ConfigParam[T]() { def get: T }
13 |
14 | private case class StringParam(protected val accessor: Accessor) extends ConfigParam[String]() { def get = accessor.access }
15 | private case class IntParam(protected val accessor: Accessor) extends ConfigParam[Int]() { def get = accessor.access.toInt }
16 | private case class BoolParam(protected val accessor: Accessor) extends ConfigParam[Boolean]() { def get = accessor.access.toBoolean }
17 |
18 | private abstract class Accessor { def access: String }
19 | private case class Prop(id: String, fallback: Accessor) extends Accessor {
20 | def access = Props.get(id) match {
21 | case Full(prop) => prop
22 | case Empty => fallback.access
23 | case _ => fallback.access
24 | }
25 | }
26 | private case class Default(definition: String) extends Accessor { def access = definition }
27 | private case object Mandatory extends Accessor { def access = throw new Exception("Improper configuration") }
28 |
29 | object layout {
30 | val usersPerPage: ConfigParam[Int] = IntParam(Prop("layout.usersperpage", Default("50")))
31 | }
32 |
33 | object mail {
34 | val user: ConfigParam[String] = StringParam(Prop("mail.user", Default("")))
35 | val password: ConfigParam[String] = StringParam(Prop("mail.password", Default("")))
36 | val from: ConfigParam[String] = StringParam(Prop("mail.from", Default("noreply@automatatutor.com")))
37 | }
38 |
39 | object db extends ConfigParam[ConnectionManager] {
40 | private val driver: ConfigParam[String] = StringParam(Prop("db.driver", Mandatory))
41 | private val url: ConfigParam[String] = StringParam(Prop("db.url", Mandatory))
42 | private val user: ConfigParam[Box[String]] = new ConfigParam[Box[String]] { def get = Props.get("db.user") }
43 | private val password: ConfigParam[Box[String]] = new ConfigParam[Box[String]] { def get = Props.get("db.password") }
44 |
45 | def get = new StandardDBVendor(driver.get, url.get, user.get, password.get)
46 | }
47 |
48 | object admin {
49 | val firstname: ConfigParam[String] = StringParam(Prop("admin.firstname", Default("Admin")))
50 | val lastname: ConfigParam[String] = StringParam(Prop("admin.lastname", Default("Admin")))
51 | val password: ConfigParam[String] = StringParam(Prop("admin.password", Default("admin")))
52 | val email: ConfigParam[String] = StringParam(Prop("admin.email", Default("admin@automatatutor.com")))
53 | }
54 |
55 | object grader {
56 | val url: ConfigParam[String] = StringParam(Prop("grader.url", Mandatory))
57 | val methodnamespace: ConfigParam[String] = StringParam(Prop("grader.methodnamespace", Mandatory))
58 | }
59 |
60 | object buchiGameSolving {
61 | val enabled : ConfigParam[Boolean] = BoolParam(Prop("buchigamesolving.enabled", Default("false")))
62 | }
63 | }
--------------------------------------------------------------------------------
/src/main/scala/com/automatatutor/lib/DownloadHelper.scala:
--------------------------------------------------------------------------------
1 | package com.automatatutor.lib
2 |
3 | import java.io.ByteArrayInputStream
4 |
5 | import scala.xml.NodeSeq
6 |
7 | import net.liftweb.http.ResponseShortcutException
8 | import net.liftweb.http.SHtml
9 | import net.liftweb.http.StreamingResponse
10 |
11 | object DownloadHelper {
12 |
13 | private abstract class FileType(mimeType : String, fileSuffix : String) {
14 | def getMimeType = mimeType
15 | def getFileSuffix = fileSuffix
16 | }
17 | private case object CsvFile extends FileType("text/csv", ".csv")
18 | private case object XmlFile extends FileType("text/xml", ".xml")
19 | private case object XlsxFile extends FileType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ".xlsx")
20 |
21 | private def offerDownloadToUser(contents: String, filename: String, filetype: FileType) = {
22 | def buildDownloadResponse = {
23 | val contentAsBytes = contents.getBytes()
24 | val downloadSize = contentAsBytes.length
25 |
26 | def buildStream = {
27 | new ByteArrayInputStream(contentAsBytes)
28 | }
29 |
30 | def buildHeaders = {
31 | val filenameWithSuffix = filename + filetype.getFileSuffix
32 | List(
33 | "Content-type" -> filetype.getMimeType,
34 | "Content-length" -> downloadSize.toString,
35 | "Content-disposition" -> ("attachment; filename=" + filenameWithSuffix)
36 | )
37 | }
38 |
39 | val stream = buildStream
40 | val onEndCallback = () => {}
41 | val headers = buildHeaders
42 | val cookies = Nil
43 | val responseCode = 200
44 |
45 | new StreamingResponse(
46 | stream, onEndCallback, downloadSize, headers, cookies, responseCode
47 | )
48 | }
49 |
50 | throw new ResponseShortcutException(buildDownloadResponse)
51 | }
52 |
53 | def renderCsvDownloadLink ( contents : String, filename : String, linkBody : NodeSeq ) : NodeSeq = {
54 | return SHtml.link("ignored", () => offerDownloadToUser(contents, filename, CsvFile), linkBody)
55 | }
56 |
57 | def renderXmlDownloadLink ( contents : NodeSeq, filename : String, linkBody : NodeSeq ) : NodeSeq = {
58 | return SHtml.link("ignored", () => offerDownloadToUser(contents.toString, filename, XmlFile), linkBody)
59 | }
60 |
61 | def renderXlsxDownloadLink ( contents : String, filename : String, linkBody : NodeSeq ) : NodeSeq = {
62 | return SHtml.link("ignored", () => offerDownloadToUser(contents.toString, filename, XlsxFile), linkBody)
63 | }
64 | }
--------------------------------------------------------------------------------
/src/main/scala/com/automatatutor/lib/TableHelper.scala:
--------------------------------------------------------------------------------
1 | package com.automatatutor.lib
2 |
3 | import scala.xml.{NodeSeq, Text, Elem, Null, TopScope, Node}
4 |
5 | object TableHelper {
6 | private def renderTableHeader( headings : Seq[String] ) : Node = {
7 | val headingsXml : NodeSeq = headings.map(heading =>
{ heading } )
8 | return { headingsXml }
9 | }
10 |
11 | private def renderSingleRow[T] ( datum : T , displayFuncs : Seq[T => NodeSeq]) : Node = {
12 | return { displayFuncs.map(func => { func(datum) } ) }
13 | }
14 |
15 | private def renderTableBody[T] ( data : Seq[T], displayFuncs : Seq[T => NodeSeq]) : NodeSeq = {
16 | return data.map(renderSingleRow(_, displayFuncs))
17 | }
18 |
19 | def renderTable[T] (data : Seq[T], displayFuncs : (T => NodeSeq)*) : NodeSeq = {
20 | val dataRows = renderTableBody(data, displayFuncs)
21 |
22 | return
23 | }
24 |
25 | def renderTableWithHeader[T] (data : Seq[T], colSpec : (String, (T => NodeSeq))*) : NodeSeq = {
26 | val headings = colSpec.map(_._1)
27 | val headerRow = renderTableHeader(headings)
28 |
29 | val displayFuncs = colSpec.map(_._2)
30 | val dataRows = renderTableBody(data, displayFuncs)
31 |
32 | return { headerRow ++ dataRows }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/scala/com/automatatutor/lib/TemplateLoader.scala:
--------------------------------------------------------------------------------
1 | package com.automatatutor.lib
2 |
3 | import net.liftweb.http.Templates
4 | import scala.xml.NodeSeq
5 | import net.liftweb.common.Full
6 | import net.liftweb.common.Empty
7 |
8 | object TemplateLoader {
9 | private class TemplateLocation(loc : String, addLoc : String*) {
10 | def getPath : List[String] = { List(loc) ++ List(addLoc : _*) }
11 | }
12 |
13 | private def load(loc : TemplateLocation) : NodeSeq = Templates(loc.getPath) match {
14 | case Full(template) => template
15 | case Empty => NodeSeq.Empty
16 | case _ => NodeSeq.Empty
17 | }
18 |
19 | object BuchiSolving {
20 | def create = load(new TemplateLocation("buchi-solving", "create"))
21 | def edit = load(new TemplateLocation("buchi-solving", "edit"))
22 | def solve = load(new TemplateLocation("buchi-solving", "solve"))
23 | }
24 | }
--------------------------------------------------------------------------------
/src/main/scala/com/automatatutor/model/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AutomataTutor/automatatutor-frontend/5d7b2f3639ec544bead7ca28995aecb87738c421/src/main/scala/com/automatatutor/model/.keep
--------------------------------------------------------------------------------
/src/main/scala/com/automatatutor/model/Attendance.scala:
--------------------------------------------------------------------------------
1 | package com.automatatutor.model
2 |
3 | import net.liftweb.mapper._
4 | import net.liftweb.common.Box
5 |
6 | class Attendance extends LongKeyedMapper[Attendance] with IdPK {
7 | def getSingleton = Attendance
8 |
9 | protected object userId extends MappedLongForeignKey(this, User)
10 | protected object courseId extends MappedLongForeignKey(this, Course)
11 | protected object email extends MappedEmail(this, 100)
12 |
13 | def getUser : User = this.userId.obj openOrThrowException "Every Attendance must have a User"
14 | def setUser ( user : User ) = this.userId(user)
15 |
16 | def getCourse : Box[Course] = this.courseId.obj
17 | def setCourse ( course : Course ) = this.courseId(course)
18 | def setCourse ( course: Box[Course] ) = this.courseId(course)
19 |
20 | def getEmail = this.email.get
21 | def setEmail ( email : String ) = this.email(email)
22 | }
23 |
24 | object Attendance extends Attendance with LongKeyedMetaMapper[Attendance] {
25 | def deleteByCourse( course : Course ) = this.bulkDelete_!!(By(Attendance.courseId, course))
26 | def finalizePreliminaryEnrollments ( user : User ) = this.findAllByEmail(user.email.is).map(attendance => attendance.email(null).userId(user).save)
27 | def createPreliminaryEnrollment ( course : Course, email : String ) = this.create.courseId(course).email(email).save
28 | def findAllByEmail ( email : String ) = this.findAll(By(Attendance.email, email))
29 | def findAllFinalEnrollments ( course : Course ) : Seq[User] = {
30 | this.findAll(By(Attendance.courseId, course), NotNullRef(Attendance.userId)).map(_.userId.obj openOrThrowException "Invariant violated: Both Attendance.userId and Attendance.email are null")
31 | }
32 | def findAllPreliminaryEnrollments ( course : Course ) : Seq[String] = this.findAll(By(Attendance.courseId, course), NullRef(Attendance.userId)).map(_.email.is)
33 |
34 | def findAllByUser(user : User) : List[Attendance] =
35 | this.findAll(By(Attendance.userId, user))
36 | def deleteAllByUser(user : User) : Unit =
37 | this.bulkDelete_!!(By(Attendance.userId, user))
38 |
39 | def findByUserAndCourse(user : User, course : Course) : Box[Attendance] =
40 | this.find(By(Attendance.userId, user), By(Attendance.courseId, course))
41 | def deleteByUserAndCourse(user : User, course : Course) : Unit =
42 | this.bulkDelete_!!(By(Attendance.userId, user), By(Attendance.courseId, course))
43 |
44 | def findByEmailAndCourse(email : String, course : Course) : Box[Attendance] =
45 | this.find(By(Attendance.email, email), By(Attendance.courseId, course))
46 | def deleteByUserAndCourse(email : String, course : Course) : Unit =
47 | this.bulkDelete_!!(By(Attendance.email, email), By(Attendance.courseId, course))
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/scala/com/automatatutor/model/DFAConstructionProblem.scala:
--------------------------------------------------------------------------------
1 | package com.automatatutor.model
2 |
3 | import net.liftweb.mapper.MappedString
4 | import net.liftweb.mapper.LongKeyedMapper
5 | import net.liftweb.mapper.LongKeyedMetaMapper
6 | import net.liftweb.mapper.MappedLongForeignKey
7 | import net.liftweb.mapper.IdPK
8 | import net.liftweb.mapper.By
9 | import net.liftweb.mapper.MappedText
10 | import scala.xml.XML
11 | import scala.xml.NodeSeq
12 | import bootstrap.liftweb.StartupHook
13 |
14 | class DFAConstructionProblem extends LongKeyedMapper[DFAConstructionProblem] with IdPK with SpecificProblem[DFAConstructionProblem] {
15 | def getSingleton = DFAConstructionProblem
16 |
17 | protected object problemId extends MappedLongForeignKey(this, Problem)
18 | protected object automaton extends MappedText(this)
19 |
20 | def getGeneralProblem = this.problemId.obj openOrThrowException "Every DFAConstructionProblem must have a ProblemId"
21 | override def setGeneralProblem(problem : Problem) : DFAConstructionProblem = this.problemId(problem)
22 |
23 | def getAutomaton = this.automaton.is
24 | def setAutomaton(automaton : String) = this.automaton(automaton)
25 | def setAutomaton(automaton : NodeSeq) = this.automaton(automaton.mkString)
26 |
27 | def getXmlDescription : NodeSeq = XML.loadString(this.automaton.is)
28 |
29 | def getAlphabet : Seq[String] = (getXmlDescription \ "alphabet" \ "symbol").map(_.text)
30 |
31 | override def copy(): DFAConstructionProblem = {
32 | val retVal = new DFAConstructionProblem
33 | retVal.problemId(this.problemId.get)
34 | retVal.automaton(this.automaton.get)
35 | return retVal
36 | }
37 | }
38 |
39 | object DFAConstructionProblem extends DFAConstructionProblem with LongKeyedMetaMapper[DFAConstructionProblem] {
40 | def findByGeneralProblem(generalProblem : Problem) : DFAConstructionProblem =
41 | find(By(DFAConstructionProblem.problemId, generalProblem)) openOrThrowException("Must only be called if we are sure that generalProblem is a DFAConstructionProblem")
42 |
43 | def deleteByGeneralProblem(generalProblem : Problem) : Boolean =
44 | bulkDelete_!!(By(DFAConstructionProblem.problemId, generalProblem))
45 | }
--------------------------------------------------------------------------------
/src/main/scala/com/automatatutor/model/NFAConstructionProblem.scala:
--------------------------------------------------------------------------------
1 | package com.automatatutor.model
2 |
3 | import scala.xml.NodeSeq
4 | import scala.xml.XML
5 | import net.liftweb.mapper.By
6 | import net.liftweb.mapper.IdPK
7 | import net.liftweb.mapper.LongKeyedMapper
8 | import net.liftweb.mapper.LongKeyedMetaMapper
9 | import net.liftweb.mapper.MappedString
10 | import net.liftweb.mapper.MappedText
11 | import net.liftweb.mapper.MappedLongForeignKey
12 | import bootstrap.liftweb.StartupHook
13 |
14 | class NFAConstructionProblem extends LongKeyedMapper[NFAConstructionProblem] with IdPK with SpecificProblem[NFAConstructionProblem] {
15 | def getSingleton = NFAConstructionProblem
16 |
17 | protected object problemId extends MappedLongForeignKey(this, Problem)
18 | protected object automaton extends MappedText(this)
19 |
20 | def getGeneralProblem = this.problemId.obj openOrThrowException "Every NFAConstructionProblem must have a ProblemId"
21 | override def setGeneralProblem(newProblem: Problem) = this.problemId(newProblem)
22 |
23 | def getAutomaton = this.automaton.get
24 | def setAutomaton(automaton : String) = this.automaton(automaton)
25 | def setAutomaton(automaton : NodeSeq) = this.automaton(automaton.mkString)
26 |
27 | def getXmlDescription : NodeSeq = XML.loadString(this.automaton.is)
28 |
29 | // Since we have ε in the alphabet, we have to remove it before handing out the alphabet
30 | def getAlphabet : Seq[String] = (getXmlDescription \ "alphabet" \ "symbol").map(_.text)
31 |
32 | def getEpsilon : Boolean =
33 | (getXmlDescription \ "epsilon").isEmpty ||
34 | (getXmlDescription \ "epsilon").map(_.text).head.replaceAll(" ", "").toBoolean
35 |
36 | override def copy(): NFAConstructionProblem = {
37 | val retVal = new NFAConstructionProblem
38 | retVal.problemId(this.problemId.get)
39 | retVal.automaton(this.automaton.get)
40 | return retVal
41 | }
42 | }
43 |
44 | object NFAConstructionProblem extends NFAConstructionProblem with LongKeyedMetaMapper[NFAConstructionProblem] {
45 | def findByGeneralProblem(generalProblem : Problem) : NFAConstructionProblem =
46 | find(By(NFAConstructionProblem.problemId, generalProblem)) openOrThrowException("Must only be called if we are sure that generalProblem is a NFAConstructionProblem")
47 |
48 | def deleteByGeneralProblem(generalProblem : Problem) : Boolean =
49 | this.bulkDelete_!!(By(NFAConstructionProblem.problemId, generalProblem))
50 | }
--------------------------------------------------------------------------------
/src/main/scala/com/automatatutor/model/NFAToDFAProblem.scala:
--------------------------------------------------------------------------------
1 | package com.automatatutor.model
2 |
3 | import net.liftweb.mapper.MappedString
4 | import net.liftweb.mapper.LongKeyedMapper
5 | import net.liftweb.mapper.LongKeyedMetaMapper
6 | import net.liftweb.mapper.MappedLongForeignKey
7 | import net.liftweb.mapper.IdPK
8 | import net.liftweb.mapper.By
9 | import net.liftweb.mapper.MappedText
10 | import scala.xml.XML
11 | import scala.xml.NodeSeq
12 |
13 | class NFAToDFAProblem extends LongKeyedMapper[NFAToDFAProblem] with IdPK with SpecificProblem[NFAToDFAProblem] {
14 | def getSingleton = NFAToDFAProblem
15 |
16 | protected object problemId extends MappedLongForeignKey(this, Problem)
17 | protected object automaton extends MappedText(this)
18 |
19 | def getGeneralProblem = this.problemId.obj openOrThrowException "Every NFAToDFAProblem must have a ProblemId"
20 | override def setGeneralProblem(problem : Problem) = this.problemId(problem)
21 |
22 | def getAutomaton = this.automaton.get
23 | def setAutomaton( automaton : String ) = this.automaton(automaton)
24 | def setAutomaton( automaton : NodeSeq ) = this.automaton(automaton.mkString)
25 |
26 | def getXmlDescription : NodeSeq = XML.loadString(this.automaton.is)
27 |
28 | def getAlphabet : Seq[String] = (getXmlDescription \ "alphabet" \ "symbol").map(_.text)
29 |
30 | override def copy(): NFAToDFAProblem = {
31 | val retVal = new NFAToDFAProblem
32 | retVal.problemId(this.problemId.get)
33 | retVal.automaton(this.automaton.get)
34 | return retVal
35 | }
36 | }
37 |
38 | object NFAToDFAProblem extends NFAToDFAProblem with LongKeyedMetaMapper[NFAToDFAProblem] {
39 | def findByGeneralProblem(generalProblem : Problem) : NFAToDFAProblem =
40 | find(By(NFAToDFAProblem.problemId, generalProblem)) openOrThrowException("Must only be called if we are sure that generalProblem is a DFAConstructionProblem")
41 |
42 | def deleteByGeneralProblem(generalProblem : Problem) : Boolean =
43 | NFAToDFAProblem.bulkDelete_!!(By(NFAToDFAProblem.problemId, generalProblem))
44 | }
--------------------------------------------------------------------------------
/src/main/scala/com/automatatutor/model/PosedProblem.scala:
--------------------------------------------------------------------------------
1 | package com.automatatutor.model
2 |
3 | import net.liftweb.mapper._
4 | import net.liftweb.common.Full
5 | import net.liftweb.common.Empty
6 | import net.liftweb.common.Box
7 |
8 | class PosedProblem extends LongKeyedMapper[PosedProblem] with IdPK {
9 | def getSingleton = PosedProblem
10 |
11 | protected object allowedAttempts extends MappedLong(this)
12 | protected object maxGrade extends MappedLong(this)
13 | protected object problemId extends MappedLongForeignKey(this, Problem)
14 | protected object nextPosedProblemId extends MappedLongForeignKey(this, PosedProblem)
15 |
16 | def getAllowedAttempts : Long = this.allowedAttempts.is
17 | def setAllowedAttempts ( attempts : Long ) : PosedProblem = this.allowedAttempts(attempts)
18 |
19 | def getMaxGrade : Long = this.maxGrade.is
20 | def setMaxGrade ( maxGrade : Long ) = this.maxGrade(maxGrade)
21 |
22 | def getProblem : Problem = this.problemId.obj openOrThrowException "Every PosedProblem must have a Problem"
23 | def setProblem ( problem : Problem ) = this.problemId(problem)
24 |
25 | def getNextPosedProblem : Box[PosedProblem] = this.nextPosedProblemId.obj
26 | def setNextPosedProblem ( posedProblem : PosedProblem ) : PosedProblem = this.nextPosedProblemId(posedProblem)
27 | def setNextPosedProblem ( posedProblem : Box[PosedProblem] ) : PosedProblem = this.nextPosedProblemId(posedProblem)
28 |
29 | override def delete_! : Boolean = {
30 | val superSucceeded = super.delete_!
31 |
32 | if (!superSucceeded) return false
33 |
34 | // If this is not the first PosedProblem in a list, fix the previous list pointer to point at the next one
35 | PosedProblem.findByNextPosedProblem(this).map { previousPosedProblem =>
36 | previousPosedProblem.setNextPosedProblem(this.nextPosedProblemId.obj)
37 | if (!previousPosedProblem.save()) return false
38 | }
39 | // If this is the first PosedProblem in a list, fix the head pointer to point to the next
40 | ProblemSet.findByFirstPosedProblem(this).map { problemSet =>
41 | problemSet.setPosedProblem(this.nextPosedProblemId.obj)
42 | if (!problemSet.save()) return false
43 | }
44 | return true
45 | }
46 |
47 | def deleteRecursively : Unit = {
48 | this.nextPosedProblemId.obj match {
49 | case Full(nextPosedProblem) => nextPosedProblem.deleteRecursively
50 | case _ => {} // We've reached the end of recursion
51 | }
52 | this.delete_! // Delete self
53 | return ()
54 | }
55 |
56 | def getListRecursively : List[PosedProblem] = {
57 | return this :: ( this.nextPosedProblemId.obj match {
58 | case Full(nextProblem) => nextProblem.getListRecursively
59 | case _ => Nil
60 | } )
61 | }
62 |
63 | def appendProblemRecursively ( toAppend : PosedProblem ) : Unit = {
64 | this.nextPosedProblemId.obj match {
65 | case Full(nextPosedProblem) => nextPosedProblem.appendProblemRecursively(toAppend)
66 | case _ => this.nextPosedProblemId(toAppend).save
67 | }
68 | }
69 |
70 | def getAttempts ( user : User, problemSet : PosedProblemSet ) : Seq[SolutionAttempt] = {
71 | return SolutionAttempt.findAll(
72 | By(SolutionAttempt.userId, user),
73 | By(SolutionAttempt.posedProblemSetId, problemSet),
74 | By(SolutionAttempt.posedProblemId, this))
75 | }
76 |
77 | def getNumberAttempts( user : User, problemSet : PosedProblemSet ) : Int = {
78 | this.getAttempts(user, problemSet).size
79 | }
80 |
81 | def getNumberAttemptsRemaining(user : User, problemSet : PosedProblemSet ) : Long = {
82 |
83 | if(this.isPracticeProblem) {
84 | return 1
85 | } else {
86 | return this.allowedAttempts.is - this.getNumberAttempts(user, problemSet)
87 | }
88 | }
89 |
90 | def getGrade ( user : User, problemSet : PosedProblemSet ) : Int = {
91 | val grades = this.getAttempts(user, problemSet).map(_.grade.is)
92 | if(grades.isEmpty) {
93 | return 0
94 | } else {
95 | return grades.max
96 | }
97 | }
98 |
99 |
100 | /**
101 | * A posed problem is defined as open if either the user has used all attempts
102 | * or if they have reached the maximal possible grade in one attempt
103 | */
104 | def isOpen ( user : User, problemSet : PosedProblemSet ) : Boolean = {
105 | val allowedAttempts = this.allowedAttempts.is
106 | val takenAttempts = this.getNumberAttempts(user, problemSet)
107 | val maxGrade = this.maxGrade.is
108 | val userGrade = this.getGrade(user, problemSet)
109 |
110 | return isPracticeProblem || (takenAttempts < allowedAttempts) //&& userGrade < maxGrade)
111 | }
112 |
113 | def isPracticeProblem = allowedAttempts == 0
114 | }
115 |
116 | object PosedProblem extends PosedProblem with LongKeyedMetaMapper[PosedProblem] {
117 | def existsForProblem(problem : Problem) : Boolean = this.find(By(PosedProblem.problemId, problem)) match {
118 | case Empty => false case Full(_) => true case _ => false}
119 | def findByProblem(problem : Problem) : Seq[PosedProblem] = this.findAll(By(PosedProblem.problemId, problem))
120 | def findByNextPosedProblem(nextProblem : PosedProblem) : Box[PosedProblem] = this.find(By(PosedProblem.nextPosedProblemId, nextProblem))
121 | }
122 |
--------------------------------------------------------------------------------
/src/main/scala/com/automatatutor/model/Problem.scala:
--------------------------------------------------------------------------------
1 | package com.automatatutor.model
2 |
3 | import com.automatatutor.snippet.DFAConstructionSnippet
4 | import com.automatatutor.snippet.NFAProblemSnippet
5 | import com.automatatutor.snippet.NFAToDFAProblemSnippet
6 | import com.automatatutor.snippet.ProblemSnippet
7 | import com.automatatutor.snippet.RegExConstructionSnippet
8 | import com.automatatutor.snippet.PumpingLemmaProblemSnippet
9 | import net.liftweb.common.Empty
10 | import net.liftweb.common.Full
11 | import net.liftweb.mapper.By
12 | import net.liftweb.mapper.IdPK
13 | import net.liftweb.mapper.LongKeyedMapper
14 | import net.liftweb.mapper.LongKeyedMetaMapper
15 | import net.liftweb.mapper.MappedLong
16 | import net.liftweb.mapper.MappedString
17 | import net.liftweb.mapper.MappedText
18 | import net.liftweb.mapper.MappedLongForeignKey
19 | import bootstrap.liftweb.StartupHook
20 | import com.automatatutor.lib.Config
21 | import com.automatatutor.BuchiGameSolving
22 |
23 | class ProblemType extends LongKeyedMapper[ProblemType] with IdPK {
24 | def getSingleton = ProblemType
25 |
26 | val DFAConstructionTypeName = "English to DFA"
27 | val NFAConstructionTypeName = "English to NFA"
28 | val NFAToDFATypeName = "NFA to DFA"
29 | val EnglishToRegExTypeName = "English to Regular Expression"
30 | //val PLTypeName = "Pumping Lemma Proof"
31 | val BuchiSolvingTypeName = "Buchi Game Solving"
32 |
33 | val knownProblemTypes : Map[String, ProblemSnippet] = Map(
34 | DFAConstructionTypeName -> DFAConstructionSnippet,
35 | NFAConstructionTypeName -> NFAProblemSnippet,
36 | NFAToDFATypeName -> NFAToDFAProblemSnippet,
37 | EnglishToRegExTypeName -> RegExConstructionSnippet
38 | ) ++
39 | (if(Config.buchiGameSolving.enabled.get) { Map(BuchiSolvingTypeName -> BuchiGameSolving.SnippetAdapter) } else { Map[String, ProblemSnippet]() })
40 |
41 | protected object problemTypeName extends MappedString(this, 200)
42 |
43 | def getProblemTypeName = this.problemTypeName.is
44 | def setProblemTypeName(problemTypeName : String) = this.problemTypeName(problemTypeName)
45 |
46 | def getProblemSnippet() : ProblemSnippet = knownProblemTypes(this.problemTypeName.is)
47 |
48 | def getSpecificProblem(generalProblem: Problem): SpecificProblem[_] = this.problemTypeName.get match {
49 | case DFAConstructionTypeName => DFAConstructionProblem.findByGeneralProblem(generalProblem)
50 | case NFAConstructionTypeName => NFAConstructionProblem.findByGeneralProblem(generalProblem)
51 | case NFAToDFATypeName => NFAToDFAProblem.findByGeneralProblem(generalProblem)
52 | case EnglishToRegExTypeName => RegExConstructionProblem.findByGeneralProblem(generalProblem)
53 | }
54 | }
55 |
56 | object ProblemType extends ProblemType with LongKeyedMetaMapper[ProblemType] with StartupHook {
57 |
58 | def onStartup = knownProblemTypes.map(entry => assertExists(entry._1))
59 |
60 | def assertExists(typeName : String) : Unit = if (!exists(typeName)) { ProblemType.create.problemTypeName(typeName).save }
61 | def exists (typeName : String) : Boolean = !findAll(By(ProblemType.problemTypeName, typeName)).isEmpty
62 | }
63 |
64 | class Problem extends LongKeyedMapper[Problem] with IdPK {
65 | def getSingleton = Problem
66 |
67 | protected object problemType extends MappedLongForeignKey(this, ProblemType)
68 | protected object visibility extends MappedLong(this)
69 | protected object createdBy extends MappedLongForeignKey(this, User)
70 | protected object shortDescription extends MappedText(this)
71 | protected object longDescription extends MappedText(this)
72 |
73 | def getProblemType = this.problemType.obj openOrThrowException "Every Problem must have a ProblemType"
74 | def setProblemType(problemType : ProblemType) = this.problemType(problemType)
75 |
76 | def getTypeName() : String = (problemType.obj.map(_.getProblemTypeName)) openOr ""
77 |
78 | def isPublic = this.visibility.is == 0
79 | def isPrivate = this.visibility.is == 1
80 |
81 | def makePublic = this.visibility(0)
82 | def makePrivate = this.visibility(1)
83 |
84 | def toggleVisibility = if(this.isPublic) { makePrivate } else { makePublic }
85 |
86 | def getCreator : User = this.createdBy.obj openOrThrowException "Every Problem must have a CreatedBy"
87 | def setCreator( creator : User ) = this.createdBy(creator)
88 |
89 | def getShortDescription = this.shortDescription.is
90 | def setShortDescription( description : String ) = this.shortDescription(description)
91 |
92 | def getLongDescription = this.longDescription.is
93 | def setLongDescription( description : String ) = this.longDescription(description)
94 |
95 | def isPosed : Boolean = PosedProblem.existsForProblem(this)
96 |
97 | def canBeDeleted : Boolean = return !(this.isPosed || this.isPublic)
98 | def getDeletePreventers : Seq[String] = {
99 | return (if(this.isPosed) { List("Problem is posed in some problem set")} else { List() }) ++ (if(this.isPublic) { List("Problem is public") } else { List() })
100 | }
101 | override def delete_! : Boolean = if(!this.canBeDeleted) { return false } else {
102 | return super.delete_!
103 | }
104 |
105 | def shareWithUserByEmail(email: String) : Boolean = {
106 | val otherUser = User.findByEmail(email) match {
107 | case Full(user) => user
108 | case _ => return false
109 | }
110 |
111 | val copiedGeneralProblem = new Problem
112 | copiedGeneralProblem.problemType(this.problemType.get)
113 | copiedGeneralProblem.makePrivate
114 | copiedGeneralProblem.createdBy(otherUser)
115 | copiedGeneralProblem.shortDescription(this.shortDescription.get)
116 | copiedGeneralProblem.longDescription(this.longDescription.get)
117 | copiedGeneralProblem.save()
118 |
119 | val copiedSpecificProblem: SpecificProblem[_] = this.problemType.obj.openOrThrowException("Every problem must have an associated type").getSpecificProblem(this).copy()
120 | copiedSpecificProblem.setGeneralProblem(copiedGeneralProblem)
121 | copiedSpecificProblem.save()
122 |
123 | return true
124 | }
125 | }
126 |
127 | object Problem extends Problem with LongKeyedMetaMapper[Problem] {
128 | def findAllByCreator(creator : User) : List[Problem] = findAll(By(Problem.createdBy, creator))
129 | def findPublicProblems : List[Problem] = findAll(By(Problem.visibility, 0))
130 | }
131 |
132 | abstract trait SpecificProblem[T] {
133 | /// Does not save the modified problem. Caller has to do that manually by calling save()
134 | def setGeneralProblem(newProblem: Problem) : T
135 | def save(): Boolean
136 | def copy(): SpecificProblem[T]
137 | }
--------------------------------------------------------------------------------
/src/main/scala/com/automatatutor/model/ProblemSet.scala:
--------------------------------------------------------------------------------
1 | package com.automatatutor.model
2 |
3 | import net.liftweb.mapper._
4 | import net.liftweb.common.Full
5 | import net.liftweb.common.Empty
6 | import net.liftweb.common.Box
7 |
8 | class ProblemSet extends LongKeyedMapper[ProblemSet] with IdPK {
9 | def getSingleton = ProblemSet
10 |
11 | protected object posedProblem extends MappedLongForeignKey(this, PosedProblem)
12 | protected object createdBy extends MappedLongForeignKey(this, User)
13 | protected object name extends MappedString(this, 100)
14 | protected object practiceSet extends MappedBoolean(this)
15 |
16 | def getPosedProblem : Box[PosedProblem] = this.posedProblem.obj
17 | def setPosedProblem ( posedProblem : PosedProblem ) = this.posedProblem(posedProblem)
18 | def setPosedProblem ( posedProblem : Box[PosedProblem] ) = this.posedProblem(posedProblem)
19 |
20 | def getCreatedBy = this.createdBy.obj openOrThrowException "Every ProblemSet must have a CreatedBy"
21 | def setCreatedBy ( user : User ) = this.createdBy(user)
22 |
23 | def getName : String = this.name.is
24 | def setName ( name : String ) = this.name(name)
25 |
26 | def getPracticeSet : Boolean = this.practiceSet.is
27 | def setPracticeSet ( practiceSet : Boolean ) = this.practiceSet(practiceSet)
28 |
29 | def deleteWithProblems = {
30 | //Remove the containing posed problem set
31 | for (posedProbSet <- PosedProblemSet.findByProblemSet(this))
32 | posedProbSet.getCourse.removePosedProblemSet(posedProbSet)
33 |
34 | // Make sure that we also delete the posed problems from the database
35 | this.posedProblem.obj match {
36 | case Full(problem) => problem.deleteRecursively // This will recursively remove all the posed problems
37 | case _ => {} // We do not need to remove anything
38 | }
39 | this.delete_!
40 | }
41 |
42 | def getPosedProblems : Seq[PosedProblem] = {
43 | this.posedProblem.obj match {
44 | case Full(problem) => problem.getListRecursively
45 | case _ => List()
46 | }
47 | }
48 |
49 | def getPosedProblemsInOrder ( inRandomOrder : Boolean, seed : Int ) : Seq[PosedProblem] = {
50 | var posedProbList = getPosedProblems
51 | if (!inRandomOrder){
52 | return posedProbList;
53 | }
54 | var length = posedProbList.length
55 | val random = new scala.util.Random(seed)
56 | var randomList = List[PosedProblem]()
57 | var curLength = 0;
58 | for ( curLength <- (1 to length).reverse){
59 | var ind =random.nextInt(curLength)
60 | var nextEl = posedProbList(ind)
61 | posedProbList= posedProbList.filter(!_.equals(nextEl))
62 | randomList = nextEl :: randomList
63 | }
64 | return randomList
65 |
66 | }
67 |
68 |
69 |
70 | def getNumberOfProblems : Int = this.getPosedProblems.size
71 |
72 | def removeProblem( toRemove : PosedProblem ) = {
73 | this.getPosedProblems.filter { _.equals(toRemove) }.map { _.delete_! }
74 | }
75 |
76 | def appendProblem( problem : Problem, numberOfAttempts : Int, maxGrade : Int ) = {
77 | val newPosedProblem = PosedProblem.create.setProblem(problem).setAllowedAttempts(numberOfAttempts).setMaxGrade(maxGrade)
78 | newPosedProblem.save
79 | this.posedProblem.obj match {
80 | case Full(problem) => problem.appendProblemRecursively(newPosedProblem)
81 | case _ => this.posedProblem(newPosedProblem).save
82 | }
83 | }
84 |
85 | def makePracticeSet = { this.practiceSet(true).save }
86 | def makeNonPracticeSet = { this.practiceSet(false).save }
87 | def isPracticeSet = { this.practiceSet.is }
88 | def isNonPracticeSet = { !this.practiceSet.is }
89 | def togglePracticeSet = { if (this.practiceSet.is) this.makeNonPracticeSet else this.makePracticeSet }
90 |
91 | def isPosed : Boolean = PosedProblemSet.existsForProblemSet(this)
92 |
93 | def canBeEdited : Boolean = !this.isPosed
94 | def getEditPreventers : Seq[String] = if(this.isPosed) { List("Problem set is posed in some course") } else { List() }
95 |
96 | def canBeDeleted : Boolean = !(this.isPosed || this.isPracticeSet)
97 | def getDeletePreventers : Seq[String] = (if(this.isPosed) { List("Problem set is posed in some course") } else { List() }) ++ (if(this.isPracticeSet) { List("Problem set is practice set") } else { List() })
98 | override def delete_! : Boolean = if(!this.canBeDeleted) { return false } else { return super.delete_! }
99 | }
100 |
101 | object ProblemSet extends ProblemSet with LongKeyedMetaMapper[ProblemSet] {
102 | def getByCreator( creator : User ) : Seq[ProblemSet] = findAll(By(ProblemSet.createdBy, creator))
103 | def getPracticeSets : Seq[ProblemSet] = findAll(By(ProblemSet.practiceSet, true))
104 |
105 | def findByFirstPosedProblem ( posedProblem : PosedProblem ) = find(By(ProblemSet.posedProblem, posedProblem))
106 | }
107 |
--------------------------------------------------------------------------------
/src/main/scala/com/automatatutor/model/PumpingLemmaProblem.scala:
--------------------------------------------------------------------------------
1 | package com.automatatutor.model
2 |
3 | import net.liftweb.mapper.MappedString
4 | import net.liftweb.mapper.LongKeyedMapper
5 | import net.liftweb.mapper.LongKeyedMetaMapper
6 | import net.liftweb.mapper.MappedLongForeignKey
7 | import net.liftweb.mapper.IdPK
8 | import net.liftweb.mapper.By
9 | import net.liftweb.mapper.MappedText
10 | import scala.xml.XML
11 | import scala.xml.NodeSeq
12 |
13 | class PumpingLemmaProblem extends LongKeyedMapper[PumpingLemmaProblem] with IdPK with SpecificProblem[PumpingLemmaProblem] {
14 | def getSingleton = PumpingLemmaProblem
15 |
16 | object problemId extends MappedLongForeignKey(this, Problem)
17 | object language extends MappedText(this)
18 | object constraint extends MappedText(this)
19 | object alphabet extends MappedText(this)
20 | object pumpingString extends MappedText(this)
21 |
22 | def getAlphabet : Seq[String] = this.alphabet.is.split(" ")
23 |
24 | override def copy(): PumpingLemmaProblem = {
25 | val retVal = new PumpingLemmaProblem
26 | retVal.problemId(this.problemId.get)
27 | retVal.language(this.language.get)
28 | retVal.constraint(this.constraint.get)
29 | retVal.alphabet(this.alphabet.get)
30 | retVal.pumpingString(this.pumpingString.get)
31 | return retVal
32 | }
33 |
34 | override def setGeneralProblem(newProblem: Problem) = this.problemId(newProblem)
35 | }
36 |
37 | object PumpingLemmaProblem extends PumpingLemmaProblem with LongKeyedMetaMapper[PumpingLemmaProblem] {
38 | def findByGeneralProblem(generalProblem : Problem) : PumpingLemmaProblem =
39 | find(By(PumpingLemmaProblem.problemId, generalProblem)) openOrThrowException("Must only be called if we are sure that generalProblem is a PumpingLemmaProblem")
40 | }
--------------------------------------------------------------------------------
/src/main/scala/com/automatatutor/model/RegExConstructionProblem.scala:
--------------------------------------------------------------------------------
1 | package com.automatatutor.model
2 |
3 | import net.liftweb.mapper.MappedString
4 | import net.liftweb.mapper.LongKeyedMapper
5 | import net.liftweb.mapper.LongKeyedMetaMapper
6 | import net.liftweb.mapper.MappedLongForeignKey
7 | import net.liftweb.mapper.IdPK
8 | import net.liftweb.mapper.By
9 | import net.liftweb.mapper.MappedText
10 | import scala.xml.XML
11 | import scala.xml.NodeSeq
12 |
13 | class RegExConstructionProblem extends LongKeyedMapper[RegExConstructionProblem] with IdPK with SpecificProblem[RegExConstructionProblem] {
14 | def getSingleton = RegExConstructionProblem
15 |
16 | object problemId extends MappedLongForeignKey(this, Problem)
17 | object regEx extends MappedText(this)
18 | object alphabet extends MappedText(this)
19 |
20 | def getAlphabet = this.alphabet.is
21 | def getRegex = this.regEx.is
22 |
23 | def setRegex(regEx : String) = this.regEx(regEx)
24 | def setAlphabet(alphabet : String) = this.alphabet(alphabet)
25 |
26 | override def copy(): RegExConstructionProblem = {
27 | val retVal = new RegExConstructionProblem
28 | retVal.problemId(this.problemId.get)
29 | retVal.regEx(this.regEx.get)
30 | retVal.alphabet(this.alphabet.get)
31 | return retVal
32 | }
33 |
34 | override def setGeneralProblem(newProblem: Problem) = this.problemId(newProblem)
35 |
36 | }
37 |
38 | object RegExConstructionProblem extends RegExConstructionProblem with LongKeyedMetaMapper[RegExConstructionProblem] {
39 | def findByGeneralProblem(generalProblem : Problem) : RegExConstructionProblem =
40 | find(By(RegExConstructionProblem.problemId, generalProblem)) openOrThrowException("Must only be called if we are sure that generalProblem is a RegExConstructionProblem")
41 | }
--------------------------------------------------------------------------------
/src/main/scala/com/automatatutor/model/Role.scala:
--------------------------------------------------------------------------------
1 | package com.automatatutor.model
2 |
3 | import net.liftweb.mapper._
4 |
5 | class Role extends LongKeyedMapper[Role] with IdPK {
6 | def getSingleton = Role
7 |
8 | object roleId extends MappedLong(this)
9 | object userId extends MappedLongForeignKey(this, User)
10 |
11 | def isStudentRole() = this.roleId.is == 1
12 | def isInstructorRole() = this.roleId.is == 2
13 | def isAdminRole() = this.roleId.is == 3
14 |
15 | override def toString() =
16 | this.roleId.is match {
17 | case 1 => "Student"
18 | case 2 => "Instructor"
19 | case 3 => "Admin"
20 | }
21 | }
22 |
23 | object Role extends Role with LongKeyedMetaMapper[Role] {
24 | def createStudentRoleFor(user : User) : Boolean = this.create.roleId(1).userId(user).save
25 | def createInstructorRoleFor(user : User) : Boolean = this.create.roleId(2).userId(user).save
26 | def createAdminRoleFor(user : User) : Boolean = this.create.roleId(3).userId(user).save
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/scala/com/automatatutor/model/SolutionAttempt.scala:
--------------------------------------------------------------------------------
1 | package com.automatatutor.model
2 |
3 | import net.liftweb.mapper._
4 | import net.liftweb.common.Box
5 | import net.liftweb.common.Empty
6 | import net.liftweb.common.Full
7 |
8 | class SolutionAttempt extends LongKeyedMapper[SolutionAttempt] with IdPK {
9 | def getSingleton = SolutionAttempt
10 |
11 | object dateTime extends MappedDateTime(this)
12 | object userId extends MappedLongForeignKey(this, User)
13 | object posedProblemSetId extends MappedLongForeignKey(this, PosedProblemSet)
14 | object posedProblemId extends MappedLongForeignKey(this, PosedProblem)
15 | object grade extends MappedInt(this)
16 | }
17 |
18 | object SolutionAttempt extends SolutionAttempt with LongKeyedMetaMapper[SolutionAttempt] {
19 | def getLatestAttempt(user : User, posedProblemSet : PosedProblemSet, posedProblem : PosedProblem) : Box[SolutionAttempt] = {
20 | val allAttempts = this.findAll(By(SolutionAttempt.userId, user), By(SolutionAttempt.posedProblemSetId, posedProblemSet), By(SolutionAttempt.posedProblemId, posedProblem))
21 | return if (allAttempts.isEmpty) { Empty } else { Full(allAttempts.maxBy(attempt => attempt.dateTime.is.getTime())) }
22 | }
23 | }
24 |
25 | class DFAConstructionSolutionAttempt extends LongKeyedMapper[DFAConstructionSolutionAttempt] with IdPK {
26 | def getSingleton = DFAConstructionSolutionAttempt
27 |
28 | object solutionAttemptId extends MappedLongForeignKey(this, SolutionAttempt)
29 | object attemptAutomaton extends MappedText(this)
30 | }
31 |
32 | object DFAConstructionSolutionAttempt extends DFAConstructionSolutionAttempt with LongKeyedMetaMapper[DFAConstructionSolutionAttempt] {
33 | def getByGeneralAttempt ( generalAttempt : SolutionAttempt ) : DFAConstructionSolutionAttempt = {
34 | return this.find(By(DFAConstructionSolutionAttempt.solutionAttemptId, generalAttempt)) openOrThrowException "Must only be called if we are sure that the general attempt also has a DFA construction attempt"
35 | }
36 | }
37 |
38 | class NFAConstructionSolutionAttempt extends LongKeyedMapper[NFAConstructionSolutionAttempt] with IdPK {
39 | def getSingleton = NFAConstructionSolutionAttempt
40 |
41 | object solutionAttemptId extends MappedLongForeignKey(this, SolutionAttempt)
42 | object attemptAutomaton extends MappedText(this)
43 | }
44 |
45 | object NFAConstructionSolutionAttempt extends NFAConstructionSolutionAttempt with LongKeyedMetaMapper[NFAConstructionSolutionAttempt] {
46 |
47 | }
48 |
49 | class NFAToDFASolutionAttempt extends LongKeyedMapper[NFAToDFASolutionAttempt] with IdPK {
50 | def getSingleton = NFAToDFASolutionAttempt
51 |
52 | object solutionAttemptId extends MappedLongForeignKey(this, SolutionAttempt)
53 | object attemptAutomaton extends MappedText(this)
54 | }
55 |
56 | object NFAToDFASolutionAttempt extends NFAToDFASolutionAttempt with LongKeyedMetaMapper[NFAToDFASolutionAttempt] {
57 |
58 | }
59 |
60 | class RegexConstructionSolutionAttempt extends LongKeyedMapper[RegexConstructionSolutionAttempt] with IdPK {
61 | def getSingleton = RegexConstructionSolutionAttempt
62 |
63 | object solutionAttemptId extends MappedLongForeignKey(this, SolutionAttempt)
64 | object attemptRegex extends MappedText(this)
65 | }
66 |
67 | object RegexConstructionSolutionAttempt extends RegexConstructionSolutionAttempt with LongKeyedMetaMapper[RegexConstructionSolutionAttempt] {
68 |
69 | }
70 |
71 | class PumpingLemmaSolutionAttempt extends LongKeyedMapper[PumpingLemmaSolutionAttempt] with IdPK {
72 | def getSingleton = PumpingLemmaSolutionAttempt
73 |
74 | object solutionAttemptId extends MappedLongForeignKey(this, SolutionAttempt)
75 | object attemptPL extends MappedText(this)
76 | }
77 |
78 | object PumpingLemmaSolutionAttempt extends PumpingLemmaSolutionAttempt with LongKeyedMetaMapper[PumpingLemmaSolutionAttempt] {
79 |
80 | }
--------------------------------------------------------------------------------
/src/main/scala/com/automatatutor/model/User.scala:
--------------------------------------------------------------------------------
1 | package com.automatatutor.model
2 |
3 | import net.liftweb.mapper._
4 | import net.liftweb.common.{Full,Box}
5 | import net.liftweb.http.SessionVar
6 | import net.liftweb.util.StringHelpers
7 | import net.liftweb.util.Mailer
8 | import net.liftweb.common.Empty
9 | import net.liftweb.util.FieldError
10 | import scala.xml.Text
11 | import net.liftweb.util.Mailer
12 | import javax.mail.{Authenticator,PasswordAuthentication}
13 | import bootstrap.liftweb.StartupHook
14 | import com.automatatutor.lib.Config
15 |
16 | class User extends MegaProtoUser[User] {
17 |
18 | override def getSingleton = User
19 |
20 | def hasStudentRole = Role.findAll(By(Role.userId, this)).exists(role => role.isStudentRole)
21 | def hasInstructorRole = Role.findAll(By(Role.userId, this)).exists(role => role.isInstructorRole)
22 | def hasAdminRole = Role.findAll(By(Role.userId, this)).exists(role => role.isAdminRole)
23 |
24 | def addStudentRole = Role.createStudentRoleFor(this)
25 | def addInstructorRole = Role.createInstructorRoleFor(this)
26 | def addAdminRole = Role.createAdminRoleFor(this)
27 |
28 | def removeStudentRole = Role.findAll(By(Role.userId, this)).filter(_.isStudentRole).map(_.delete_!)
29 | def removeInstructorRole = Role.findAll(By(Role.userId, this)).filter(_.isInstructorRole).map(_.delete_!)
30 | def removeAdminRole = Role.findAll(By(Role.userId, this)).filter(_.isAdminRole).map(_.delete_!)
31 |
32 | def supervisesCourses : Boolean = !Supervision.findByInstructor(this).isEmpty
33 | def getSupervisedCourses : Seq[Course] = Supervision.findByInstructor(this).map(_.getCourse)
34 |
35 | def attendsCourses : Boolean = !Attendance.findAllByUser(this).isEmpty
36 | def getAttendedCourses : List[Course] = Attendance.findAllByUser(this).map(_.getCourse.openOrThrowException("User should not attend inexistent courses"))
37 |
38 | def canBeDeleted : Boolean = !(attendsCourses || supervisesCourses || hasAdminRole)
39 | def getDeletePreventers : Seq[String] = {
40 | val attendancePreventers : Seq[String] = if (this.attendsCourses) List("User still attends courses") else List()
41 | val supervisionPreventers : Seq[String] = if (this.supervisesCourses) List("User still supervises courses") else List()
42 | val adminPreventers : Seq[String] = if (this.hasAdminRole) List("User is Admin") else List()
43 | val preventers = attendancePreventers ++ supervisionPreventers ++ adminPreventers
44 | return preventers
45 | }
46 |
47 | override def delete_! : Boolean = {
48 | if (!canBeDeleted) {
49 | return false;
50 | } else {
51 | return super.delete_!
52 | }
53 | }
54 |
55 | override def validate = (this.validateFirstName) ++ (this.validateLastName) ++ (super.validate)
56 |
57 | private def validateFirstName : List[FieldError] = {
58 | if (this.firstName == null || this.firstName.is.isEmpty()) {
59 | return List[FieldError](new FieldError(this.firstName, Text("First name must be set")))
60 | } else {
61 | return List[FieldError]()
62 | }
63 | }
64 |
65 | private def validateLastName : List[FieldError] = {
66 | if (this.lastName == null || this.lastName.is.isEmpty()) {
67 | return List[FieldError](new FieldError(this.lastName, Text("Last name must be set")))
68 | } else {
69 | return List[FieldError]()
70 | }
71 | }
72 | }
73 |
74 | object User extends User with MetaMegaProtoUser[User] with StartupHook {
75 | // Don't send out emails to users after registration. Remember to set this to false before we go into production
76 | override def skipEmailValidation = false
77 |
78 | // this overridse the noreply@... address that is set by default right now
79 | // for this to work the correct properties must
80 | //Mailer.authenticator.map(_.user) openOr
81 | override def emailFrom = Config.mail.from.get
82 |
83 | // Display the standard template around the User-defined pages
84 | override def screenWrap = Full( )
85 |
86 | // Only query given name, family name, email address and password on signup
87 | override def signupFields = List(firstName, lastName, email, password)
88 |
89 | // Only display given name, family name and email address for editing
90 | override def editFields = List(firstName, lastName)
91 |
92 | override def afterCreate = List(
93 | (user : User) => { user.addStudentRole },
94 | (user : User) => { Attendance.finalizePreliminaryEnrollments(user) }
95 | )
96 |
97 | def onStartup = {
98 | val adminEmail = Config.admin.email.get
99 | val adminPassword = Config.admin.password.get
100 | val adminFirstName = Config.admin.firstname.get
101 | val adminLastName = Config.admin.lastname.get
102 |
103 | /* Delete all existing admin accounts, in case there are any leftover from
104 | * previous runs */
105 | val adminAccounts = User.findAll(By(User.email, adminEmail))
106 |
107 | //User.bulkDelete_!!(By(User.email, adminEmail))
108 |
109 | // Create new admin only if the user in the config does not exists
110 | if (adminAccounts.isEmpty){
111 | val adminUser = User.create
112 | adminUser.firstName(adminFirstName).lastName(adminLastName).email(adminEmail).password(adminPassword).validated(true).save
113 | adminUser.addAdminRole
114 | adminUser.addInstructorRole
115 | } else {
116 | // otherwise just change the password for his account to the one in the config
117 | var user = adminAccounts.head
118 | var passwordList = List(adminPassword,adminPassword)
119 | passwordList
120 | user.setPasswordFromListString(passwordList)
121 | user.save
122 | }
123 | }
124 |
125 | def findByEmail(email : String) : Box[User] = {
126 | val users = User.findAll(By(User.email, email))
127 | if(users.size == 1) {
128 | return Full(users.head)
129 | } else {
130 | return Empty
131 | }
132 | }
133 |
134 | def currentUser_! : User = {
135 | this.currentUser openOrThrowException "This method may only be called if we are certain that a user is logged in"
136 | }
137 |
138 | def currentUserIdInt : Int = {
139 | this.currentUserId match {
140 | case Full(myId) => myId.toInt;
141 | case _ => 0
142 | }
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/src/main/scala/com/automatatutor/renderer/CourseRenderer.scala:
--------------------------------------------------------------------------------
1 | package com.automatatutor.renderer
2 |
3 | import scala.xml.NodeSeq
4 | import scala.xml.Text
5 | import com.automatatutor.model.Course
6 | import com.automatatutor.model.PosedProblemSet
7 | import com.automatatutor.snippet.CourseReqVar
8 | import net.liftweb.http.SHtml
9 | import net.liftweb.http.SHtml.ElemAttr.pairToBasic
10 | import net.liftweb.http.js.JE.JsRaw
11 | import net.liftweb.http.js.JsCmd
12 | import net.liftweb.http.js.JsCmds
13 | import net.liftweb.http.js.JsCmds.jsExpToJsCmd
14 | import com.automatatutor.model.User
15 |
16 | class CourseRenderer(course : Course) {
17 | def renderShowLink : NodeSeq = {
18 | return SHtml.link("/courses/show", () => CourseReqVar(course), Text("Show"))
19 | }
20 |
21 | def renderContactLink : NodeSeq = {
22 | SHtml.link("mailto:" + course.getContact, () => (), Text(course.getContact))
23 | }
24 |
25 | def renderManageLink : NodeSeq = {
26 | if(course.getInstructors.contains(User.currentUser openOr null)) {
27 | SHtml.link("/courses/manage", () => CourseReqVar(course), Text("Manage course"))
28 | } else {
29 | NodeSeq.Empty
30 | }
31 | }
32 |
33 | def renderDeleteLink : NodeSeq = {
34 | val target = "/courses/index"
35 | def function() = course.delete_!
36 | val label = if(course.canBeDeleted) { Text("Delete") } else { Text("Cannot delete course") }
37 | val onclick : JsCmd = if(course.canBeDeleted) {
38 | JsRaw("return confirm('Are you sure you want to delete this course?')")
39 | } else {
40 | JsCmds.Alert("Cannot delete this course:\n" + course.getDeletePreventers.mkString("\n")) & JsRaw("return false")
41 | }
42 |
43 | return SHtml.link(target, function, label, "onclick" -> onclick.toJsCmd)
44 | }
45 |
46 | def renderRemoveProblemSetLink(posedProblemSet : PosedProblemSet) : NodeSeq = {
47 | val target = "/courses/manage"
48 | def function() = { CourseReqVar(course); course.removePosedProblemSet(posedProblemSet) }
49 | val label = if(posedProblemSet.canBeRemoved) { Text("Remove Problem Set") } else { Text("Cannot remove Problem Set") }
50 | val onclick : JsCmd = if(posedProblemSet.canBeRemoved) {
51 | JsRaw("return confirm('Are you sure you want to remove this problem set?')")
52 | } else {
53 | JsCmds.Alert("Cannot remove this problem set:\n" + posedProblemSet.getRemovePreventers.mkString("\n")) & JsRaw("return false")
54 | }
55 |
56 | return SHtml.link(target, function, label, "onclick" -> onclick.toJsCmd)
57 | }
58 | }
--------------------------------------------------------------------------------
/src/main/scala/com/automatatutor/renderer/ProblemRenderer.scala:
--------------------------------------------------------------------------------
1 | package com.automatatutor.renderer
2 |
3 | import scala.xml.NodeSeq
4 | import scala.xml.Text
5 |
6 | import com.automatatutor.model.Problem
7 | import com.automatatutor.model.User
8 | import com.automatatutor.snippet.chosenProblem
9 |
10 | import net.liftweb.common.Empty
11 | import net.liftweb.common.Full
12 | import net.liftweb.http.S
13 | import net.liftweb.http.SHtml
14 | import net.liftweb.http.js.JE.JsRaw
15 | import net.liftweb.http.js.JsCmd
16 | import net.liftweb.http.js.JsCmds
17 | import net.liftweb.http.js.JsCmds._
18 |
19 | class ProblemRenderer(problem : Problem) {
20 | def renderDeleteLink : NodeSeq = {
21 | val target = "/problems/index"
22 | def function() = { problem.getProblemType.getProblemSnippet().onDelete(problem); problem.delete_! }
23 | val label = if(problem.canBeDeleted) { Text("Delete") } else { Text("Cannot delete Problem") }
24 | val onclick : JsCmd = if(problem.canBeDeleted) {
25 | JsRaw("return confirm('Are you sure you want to delete this problem?')")
26 | } else {
27 | JsCmds.Alert("Cannot delete this problem:\n" + problem.getDeletePreventers.mkString("\n")) & JsRaw("return false")
28 | }
29 |
30 | return SHtml.link(target, function, label, "onclick" -> onclick.toJsCmd)
31 | }
32 |
33 | def renderTogglePublicLink : NodeSeq = {
34 | if(User.currentUser_!.hasAdminRole) {
35 | val linkText = if(problem.isPublic) { Text("Make private") } else {Text("Make public")}
36 | return SHtml.link("/problems/index", () => problem.toggleVisibility.save(), linkText)
37 | } else {
38 | return NodeSeq.Empty
39 | }
40 | }
41 |
42 | def renderSharingWidget : NodeSeq = {
43 | def shareWithFeedback(email: String) = {
44 | if (problem.shareWithUserByEmail(email)) { S.notice("Successfully shared with " + email) }
45 | else { S.error("Could not find user " + email) }
46 | }
47 |
48 | }
49 |
50 | def renderEditLink : NodeSeq = {
51 | problem.getProblemType.getProblemSnippet().renderEdit match {
52 | case Full(_) => SHtml.link("/problems/edit", () => chosenProblem(problem), Text("Edit"))
53 | case Empty => NodeSeq.Empty
54 | case _ => S.error("Error when retrieving editing function"); S.redirectTo("/problems/index")
55 | }
56 | }
57 | }
--------------------------------------------------------------------------------
/src/main/scala/com/automatatutor/renderer/ProblemSetRenderer.scala:
--------------------------------------------------------------------------------
1 | package com.automatatutor.renderer
2 |
3 | import scala.xml.NodeSeq
4 | import scala.xml.Text
5 |
6 | import com.automatatutor.model.ProblemSet
7 | import com.automatatutor.model.User
8 | import com.automatatutor.snippet.ProblemSetToEdit
9 |
10 | import net.liftweb.http.SHtml
11 | import net.liftweb.http.SHtml.ElemAttr.pairToBasic
12 | import net.liftweb.http.js.JE.JsRaw
13 | import net.liftweb.http.js.JsCmd
14 | import net.liftweb.http.js.JsCmds
15 | import net.liftweb.http.js.JsCmds.jsExpToJsCmd
16 |
17 | class ProblemSetRenderer(problemSet : ProblemSet) {
18 | def renderEditLink : NodeSeq = {
19 | val target = "/problemsets/edit"
20 | def function() = ProblemSetToEdit(problemSet)
21 | val label = if(problemSet.canBeEdited) { Text("Edit") } else { Text("Cannot edit Problem Set") }
22 | val onclick : JsCmd = if(!problemSet.canBeEdited) {
23 | JsCmds.Alert("Cannot edit this problem set:\n" + problemSet.getEditPreventers.mkString("\n")) & JsRaw("return false")
24 | } else {
25 | JsCmds.Noop
26 | }
27 |
28 | return SHtml.link(target, function, label, "onclick" -> onclick.toJsCmd)
29 | }
30 |
31 | def renderDeleteLink : NodeSeq = {
32 | val target = "/problemsets/index"
33 | def function() = problemSet.delete_!
34 | val label = if(problemSet.canBeDeleted) { Text("Delete") } else { Text("Cannot delete Problem Set") }
35 | val onclick : JsCmd = if(problemSet.canBeDeleted) {
36 | JsRaw("return confirm('Are you sure you want to delete this problem set?')")
37 | } else {
38 | JsCmds.Alert("Cannot delete this problem set:\n" + problemSet.getDeletePreventers.mkString("\n")) & JsRaw("return false")
39 | }
40 |
41 | return SHtml.link(target, function, label, "onclick" -> onclick.toJsCmd)
42 | }
43 |
44 | def renderTogglePracticeSetLink : NodeSeq = {
45 | if(User.currentUser_!.hasAdminRole) {
46 | val linkText = if(problemSet.isPracticeSet) { "Make Nonpractice Set" } else { "Make Practice Set" }
47 | return SHtml.link("/problemsets/index", () => problemSet.togglePracticeSet, Text(linkText))
48 | } else {
49 | return NodeSeq.Empty
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/scala/com/automatatutor/renderer/UserRenderer.scala:
--------------------------------------------------------------------------------
1 | package com.automatatutor.renderer
2 |
3 | import net.liftweb.http.js.JE.JsRaw
4 | import net.liftweb.http.js.JsCmd
5 | import net.liftweb.http.SHtml
6 | import scala.xml.NodeSeq
7 | import net.liftweb.http.js.JsCmds
8 | import net.liftweb.http.js.JsCmds._
9 | import scala.xml.Text
10 | import com.automatatutor.model.User
11 |
12 | class UserRenderer(user : User) {
13 |
14 | def renderDeleteLink : NodeSeq = {
15 | val target = "/users/index"
16 | def function() = user.delete_!
17 | val label = if(user.canBeDeleted) { Text("Delete") } else { Text("Cannot delete user") }
18 | val onclick : JsCmd = if(user.canBeDeleted) {
19 | JsRaw("return confirm('Are you sure you want to delete this user?')")
20 | } else {
21 | JsCmds.Alert("Cannot delete this user:\n" + user.getDeletePreventers.mkString("\n")) & JsRaw("return false")
22 | }
23 |
24 | return SHtml.link(target, function, label, "onclick" -> onclick.toJsCmd)
25 | }
26 |
27 | }
--------------------------------------------------------------------------------
/src/main/scala/com/automatatutor/snippet/Identity.scala:
--------------------------------------------------------------------------------
1 | package com.automatatutor.snippet
2 |
3 | import scala.xml.NodeSeq
4 |
5 | class Identity {
6 | def render ( xhtml : NodeSeq ) : NodeSeq = xhtml
7 | }
--------------------------------------------------------------------------------
/src/main/scala/com/automatatutor/snippet/PracticeSets.scala:
--------------------------------------------------------------------------------
1 | package com.automatatutor.snippet
2 |
3 | import scala.xml.NodeSeq
4 | import scala.xml.Text
5 |
6 | import com.automatatutor.lib.TableHelper
7 | import com.automatatutor.model.PosedProblem
8 | import com.automatatutor.model.ProblemSet
9 |
10 | import net.liftweb.http.SHtml
11 |
12 | class Practicesets {
13 |
14 | def renderindex ( ignored : NodeSeq ) : NodeSeq = {
15 | val practiceSets = ProblemSet.getPracticeSets
16 | val practiceSetsNodeSeq = {
17 | val practiceSetsTable = TableHelper.renderTableWithHeader(practiceSets,
18 | ("Name", (problemSet : ProblemSet) => Text(problemSet.getName)),
19 | ("Problems", (problemSet : ProblemSet) => Text(problemSet.getPosedProblems.size.toString)))
20 |
21 |
22 | val problemSetsHeaderRow = (practiceSetsTable \ "tr").head
23 | val problemSetsDataRows : NodeSeq = (practiceSetsTable \ "tr").tail
24 | val problemSetsWithRows = practiceSets.zip(problemSetsDataRows)
25 | val problemSetsWithProblemsRows = problemSetsWithRows.flatMap(tuple => {
26 | val practiceSet = tuple._1
27 | val practiceSetRow = tuple._2
28 | val problemsTable : NodeSeq = renderSolvePracticeSetNoHeader(practiceSet)
29 | practiceSetRow ++ { problemsTable }
30 | })
31 |
32 | { problemSetsHeaderRow } { problemSetsWithProblemsRows }
33 | }
34 |
35 | return practiceSetsNodeSeq
36 | }
37 |
38 | private def renderSolvePracticeSetNoHeader( practiceSet : ProblemSet ) : NodeSeq = {
39 | val posedProblems = practiceSet.getPosedProblems
40 |
41 | return TableHelper.renderTable(posedProblems,
42 | (posedProblem : PosedProblem) => Text(posedProblem.getProblem.getShortDescription),
43 | (posedProblem : PosedProblem) => Text(posedProblem.getProblem.getTypeName),
44 | (posedProblem : PosedProblem) => SHtml.link("/practicesets/solve", () => { PosedProblemReqVar(posedProblem) }, Text("solve")))
45 | }
46 |
47 | }
--------------------------------------------------------------------------------
/src/main/scala/com/automatatutor/snippet/ProblemSets.scala:
--------------------------------------------------------------------------------
1 | package com.automatatutor.snippet
2 |
3 | import scala.xml.NodeSeq
4 | import scala.xml.Text
5 | import com.automatatutor.lib.TableHelper
6 | import com.automatatutor.model.PosedProblem
7 | import com.automatatutor.model.Problem
8 | import com.automatatutor.model.ProblemSet
9 | import com.automatatutor.model.User
10 | import com.automatatutor.renderer.ProblemSetRenderer
11 | import net.liftweb.http.RequestVar
12 | import net.liftweb.http.S
13 | import net.liftweb.http.SHtml
14 | import net.liftweb.util.Helpers
15 | import net.liftweb.util.Helpers._
16 | import com.automatatutor.lib.Binding
17 | import com.automatatutor.lib.Renderer
18 | import com.automatatutor.lib.Binder
19 | import com.automatatutor.lib.DataRenderer
20 |
21 |
22 | object ProblemSetToEdit extends RequestVar[ProblemSet](null)
23 | object ProblemToPose extends RequestVar[Problem](null)
24 |
25 | class Problemsets {
26 | def renderindex ( template : NodeSeq ) : NodeSeq = {
27 |
28 | val problemSets : Seq[ProblemSet] = ProblemSet.getByCreator(User.currentUser openOrThrowException "Lift only lets logged in users onto this site")
29 |
30 | object problemSetRenderer extends DataRenderer(problemSets) {
31 | def render( template : NodeSeq, problemSet : ProblemSet ) : NodeSeq = {
32 | object nameRenderer extends Renderer { def render : NodeSeq = Text(problemSet.getName) }
33 | object togglePracticeSetLinkRenderer extends Renderer { def render : NodeSeq = new ProblemSetRenderer(problemSet).renderTogglePracticeSetLink }
34 | object editLinkRenderer extends Renderer { def render : NodeSeq = new ProblemSetRenderer(problemSet).renderEditLink }
35 | object deleteLinkRenderer extends Renderer { def render : NodeSeq = new ProblemSetRenderer(problemSet).renderDeleteLink }
36 |
37 | val nameBinding = new Binding("name", nameRenderer)
38 | val togglePracticeSetLinkBinding = new Binding("togglepracticesetlink", togglePracticeSetLinkRenderer)
39 | val editLinkBinding = new Binding("editlink", editLinkRenderer)
40 | val deleteLinkBinding = new Binding("deletelink", deleteLinkRenderer)
41 |
42 | return new Binder("problemset", nameBinding, togglePracticeSetLinkBinding, editLinkBinding, deleteLinkBinding).bind(template)
43 | }
44 | }
45 |
46 | Helpers.bind("problemsets", template,
47 | "foreach" -> { template : NodeSeq => problemSetRenderer.render(template) },
48 | "createnewlink" -> { template : NodeSeq => SHtml.link("/problemsets/create", () => {}, Text(template(0).text)) } )
49 | }
50 |
51 | def rendercreate ( xhtml : NodeSeq ) : NodeSeq = {
52 | var name : String = ""
53 |
54 | def create() = {
55 | ProblemSet.create.setName(name).setCreatedBy(User.currentUser_!).save
56 | }
57 |
58 | object nameFieldRenderer extends Renderer { def render = {
59 | SHtml.text("", name = _, "maxlength" -> "50")
60 | } }
61 | object createButtonRenderer extends Renderer { def render = {
62 | SHtml.submit("Create", () => { create(); S.redirectTo("/problemsets/index") })
63 | } }
64 | val nameFieldBinding = new Binding("namefield", nameFieldRenderer)
65 | val createButtonBinding = new Binding("createbutton", createButtonRenderer)
66 |
67 | new Binder("problemsetcreate", nameFieldBinding, createButtonBinding).bind(xhtml)
68 | }
69 |
70 | def renderedit ( xhtml : NodeSeq ) : NodeSeq = {
71 | val problemSetToEdit : ProblemSet = ProblemSetToEdit.is
72 | val posedProblems = problemSetToEdit.getPosedProblems
73 |
74 | val posedProblemsTable = TableHelper.renderTableWithHeader(posedProblems,
75 | ("Short Description", (posedProblem : PosedProblem) => Text(posedProblem.getProblem.getShortDescription)),
76 | ("Problem Type", (posedProblem : PosedProblem) => Text(posedProblem.getProblem.getTypeName)),
77 | ("Allowed Attempts", (posedProblem : PosedProblem) => Text(posedProblem.getAllowedAttempts.toString() + " Attempts allowed")),
78 | ("", (posedProblem : PosedProblem) => SHtml.link( "/problemsets/index", () => { problemSetToEdit.removeProblem(posedProblem) }, Text("Remove problem from set")
79 | )
80 | )
81 | )
82 |
83 | val addProblemLink = SHtml.link("/problemsets/addproblem", () => ProblemSetToEdit(problemSetToEdit), Text("Append new problem"))
84 |
85 | return posedProblemsTable ++ addProblemLink
86 | }
87 |
88 | def renderaddproblem ( xhtml : NodeSeq ) : NodeSeq = {
89 | val problemSetToEdit = ProblemSetToEdit.is
90 | val currentUser = User.currentUser openOrThrowException "Lift prevents non-logged in users from getting here"
91 | val problems : Seq[Problem] = Problem.findAllByCreator(currentUser)
92 |
93 | return TableHelper.renderTableWithHeader(problems,
94 | ("Short Description", (problem : Problem) => Text(problem.getShortDescription)),
95 | ("Problem Type", (problem : Problem) => Text(problem.getTypeName)),
96 | ("", (problem : Problem) => SHtml.link("/problemsets/poseproblem", () => { ProblemSetToEdit(problemSetToEdit); ProblemToPose(problem) }, Text("Pose this problem"))))
97 | }
98 |
99 | def renderposeproblem ( xhtml : NodeSeq ) : NodeSeq = {
100 | val problemSetToEdit = ProblemSetToEdit.is
101 | val problemToPose = ProblemToPose.is
102 |
103 | var attempts = ""
104 | var maxGrade = ""
105 |
106 | def poseProblem = {
107 | def redirectToSelf( exception : Exception ) = {
108 | S.error(exception.getMessage())
109 | S.redirectTo("/problemsets/poseproblem", () => { ProblemSetToEdit(problemSetToEdit); ProblemToPose(problemToPose) } )
110 | }
111 | val numAttempts = try { attempts.toInt } catch { case e : Exception => redirectToSelf(e) }
112 | val numMaxGrade = try { maxGrade.toInt } catch { case e : Exception => redirectToSelf(e) }
113 | problemSetToEdit.appendProblem( problemToPose, numAttempts, numMaxGrade )
114 | S.redirectTo("/problemsets/edit", () => ProblemSetToEdit(problemSetToEdit))
115 | }
116 |
117 | object maxGradeFieldRenderer extends Renderer { def render = {
118 | SHtml.text("10", maxGrade = _)
119 | } }
120 | object attemptsFieldRenderer extends Renderer { def render = {
121 | SHtml.text("3", attempts = _)
122 | } }
123 | object submitButtonRenderer extends Renderer { def render = {
124 | SHtml.submit("Pose Problem", () => poseProblem)
125 | } }
126 | val maxGradeFieldBinding = new Binding("maxgradefield", maxGradeFieldRenderer)
127 | val attemptsFieldBinding = new Binding("attemptsfield", attemptsFieldRenderer)
128 | val poseButtonBinding = new Binding("posebutton", submitButtonRenderer)
129 |
130 | new Binder("poseproblemform", maxGradeFieldBinding, attemptsFieldBinding, poseButtonBinding).bind(xhtml)
131 | }
132 | }
--------------------------------------------------------------------------------
/src/main/scala/com/automatatutor/snippet/ProblemSnippet.scala:
--------------------------------------------------------------------------------
1 | package com.automatatutor.snippet
2 |
3 | import java.util.Date
4 | import scala.xml.NodeSeq
5 | import scala.xml.Text
6 | import com.automatatutor.lib.TableHelper
7 | import com.automatatutor.model.Problem
8 | import com.automatatutor.model.ProblemType
9 | import com.automatatutor.model.SolutionAttempt
10 | import com.automatatutor.model.User
11 | import com.automatatutor.renderer.ProblemRenderer
12 | import net.liftweb.common.Box
13 | import net.liftweb.common.Empty
14 | import net.liftweb.http.RequestVar
15 | import net.liftweb.http.S
16 | import net.liftweb.http.SHtml
17 | import net.liftweb.util.AnyVar.whatVarIs
18 | import net.liftweb.common.Full
19 |
20 | object chosenProblem extends RequestVar[Problem](null)
21 | object chosenProblemType extends RequestVar[ProblemType](null)
22 |
23 | class Problems {
24 | def renderindex ( ignored : NodeSeq ) : NodeSeq = {
25 |
26 | val usersProblems = Problem.findAllByCreator(User.currentUser openOrThrowException("We should only be on this page if there is a user logged in"))
27 |
28 | val completeTable = TableHelper.renderTableWithHeader(usersProblems,
29 | ("Problem Type", (problem : Problem) => Text(problem.getTypeName)),
30 | ("Description", (problem : Problem) => Text(problem.getShortDescription)),
31 | ("", (problem : Problem) => new ProblemRenderer(problem).renderSharingWidget),
32 | ("", (problem : Problem) => new ProblemRenderer(problem).renderTogglePublicLink),
33 | ("", (problem : Problem) => new ProblemRenderer(problem).renderEditLink),
34 | ("", (problem : Problem) => new ProblemRenderer(problem).renderDeleteLink))
35 |
36 | val creationLink = SHtml.link("/problems/create", () => {}, Text("Create new problem"))
37 |
38 | return completeTable ++ creationLink
39 | }
40 |
41 | def rendercreate( ignored : NodeSeq ) : NodeSeq = {
42 | val problemType = chosenProblemType.is
43 |
44 | def createUnspecificProb ( shortDesc : String, longDesc : String) : Problem = {
45 | val createdBy : User = User.currentUser openOrThrowException "Lift protects this page against non-logged-in users"
46 |
47 | val unspecificProblem : Problem = Problem.create.setCreator(createdBy).makePrivate
48 | unspecificProblem.setShortDescription(shortDesc).setLongDescription(longDesc).setProblemType(problemType)
49 | unspecificProblem.save
50 |
51 | return unspecificProblem
52 | }
53 |
54 | def returnFunc() = {
55 | S.redirectTo("/problems/index")
56 | }
57 |
58 | if(problemType == null) {
59 | return renderTypeMenu()
60 | } else {
61 | return problemType.getProblemSnippet().renderCreate(createUnspecificProb, returnFunc)
62 | }
63 | }
64 |
65 | def renderedit( ignored : NodeSeq ) : NodeSeq = {
66 | if(chosenProblem == null) {
67 | S.warning("Please choose a problem to edit first")
68 | return S.redirectTo("/problems/index")
69 | } else {
70 | val problem : ProblemType = (chosenProblem.getProblemType)
71 | val problemSnippet : ProblemSnippet = problem.getProblemSnippet()
72 | problemSnippet.renderEdit match {
73 | case Full(renderFunc) => renderFunc(chosenProblem, () => S.redirectTo("/problems/index"))
74 | case Empty => S.error("Editing not implemented for this problem type"); S.redirectTo("/problems/index")
75 | case _ => S.error("Error when retrieving editing function"); S.redirectTo("/problems/index")
76 | }
77 | }
78 | }
79 |
80 | def renderTypeMenu() : NodeSeq = {
81 | val headerLine = Choose Problem Type
82 |
83 | val knownProblemTypes = ProblemType.findAll.map(problemType => {
84 | SHtml.link("/problems/create", () => chosenProblemType(problemType), Text(problemType.getProblemTypeName))
85 | }
86 | )
87 | val knownProblemTypesList =
88 |
89 | val returnLink = SHtml.link("/problems/index", () => {}, Text("Back to index"))
90 |
91 | return headerLine ++ knownProblemTypesList ++ returnLink
92 | }
93 |
94 | def renderpublicproblems( ignored : NodeSeq ) : NodeSeq = {
95 | val publicProblems = Problem.findPublicProblems
96 |
97 | return TableHelper.renderTableWithHeader(publicProblems,
98 | ("Short Description", (problem : Problem) => Text(problem.getShortDescription)),
99 | ("Category", (problem : Problem) => Text(problem.getTypeName)),
100 | ("", (problem : Problem) => SHtml.link("/preview/solve", () => chosenProblem.set(problem), Text("Solve"))))
101 | }
102 |
103 | def rendersolvepublicproblem( ignored : NodeSeq ) : NodeSeq = {
104 | val problem = chosenProblem.is
105 | if(problem == null) {
106 | S.redirectTo("/preview/index",
107 | () => S.notice("Please login. We temporarily disabled the Try it Now problems."))
108 | }
109 | val snippet = problem.getProblemType.getProblemSnippet
110 | return snippet.renderSolve(problem, 10, Empty,
111 | (date, grade) => SolutionAttempt,
112 | () => S.redirectTo("/preview/index"),
113 | () => 1,
114 | () => 100)
115 | }
116 | }
117 |
118 | trait ProblemSnippet {
119 | /** Should produce a NodeSeq that allows the user to create a new problem of
120 | * the type. This NodeSeq also has to handle creation of the unspecific
121 | * {@link Problem}. */
122 | def renderCreate( createUnspecificProb : (String, String) => Problem,
123 | returnFunc : () => Nothing ) : NodeSeq
124 |
125 | /** Should produce a NodeSeq that allows the user to edit the problem
126 | * associated with the given unspecific problem. */
127 | def renderEdit : Box[((Problem, () => Nothing) => NodeSeq)]
128 |
129 | /** Should produce a NodeSeq that allows the user a try to solve the problem
130 | * associated with the given unspecific problem. The function
131 | * recordSolutionAttempt must be called once for every solution attempt
132 | * and expects the grade of the attempt (which must be <= maxGrade) and the
133 | * time the attempt was made. After finishing the solution attempt, the
134 | * snippet should send the user back to the overview of problems in the
135 | * set by calling returnToSet */
136 | def renderSolve ( problem : Problem, maxGrade : Long, lastAttempt : Box[SolutionAttempt],
137 | recordSolutionAttempt: (Int, Date) => SolutionAttempt,
138 | returnToSet : () => Unit , attemptsLeft : () => Int, bestGrade : () => Int) : NodeSeq
139 |
140 | /** Is called before the given unspecific problem is deleted from the database.
141 | * This method should delete everything associated with the given unspecific
142 | * problem from the database */
143 | def onDelete( problem : Problem ) : Unit
144 | }
--------------------------------------------------------------------------------
/src/main/scala/com/automatatutor/snippet/Users.scala:
--------------------------------------------------------------------------------
1 | package com.automatatutor.snippet
2 |
3 | import scala.xml.NodeSeq
4 | import scala.xml.Text
5 | import com.automatatutor.lib.TableHelper
6 | import com.automatatutor.model.Attendance
7 | import com.automatatutor.model.Course
8 | import com.automatatutor.model.DFAConstructionProblem
9 | import com.automatatutor.model.DFAConstructionSolutionAttempt
10 | import com.automatatutor.model.NFAConstructionProblem
11 | import com.automatatutor.model.NFAConstructionSolutionAttempt
12 | import com.automatatutor.model.NFAToDFAProblem
13 | import com.automatatutor.model.NFAToDFASolutionAttempt
14 | import com.automatatutor.model.PosedProblem
15 | import com.automatatutor.model.PosedProblemSet
16 | import com.automatatutor.model.Problem
17 | import com.automatatutor.model.ProblemSet
18 | import com.automatatutor.model.ProblemType
19 | import com.automatatutor.model.Role
20 | import com.automatatutor.model.SolutionAttempt
21 | import com.automatatutor.model.Supervision
22 | import com.automatatutor.model.User
23 | import com.automatatutor.renderer.UserRenderer
24 | import net.liftweb.http.RequestVar
25 | import net.liftweb.http.S
26 | import net.liftweb.http.SHtml
27 | import net.liftweb.mapper.By
28 | import net.liftweb.util.Helpers
29 | import net.liftweb.util.Helpers.strToSuperArrowAssoc
30 | import net.liftweb.http.PaginatorSnippet
31 | import net.liftweb.mapper.StartAt
32 | import net.liftweb.mapper.MaxRows
33 | import com.automatatutor.lib.Config
34 |
35 | object userToEdit extends RequestVar[User](null)
36 |
37 | class Users extends PaginatorSnippet[User] {
38 | override def count = User.findAll().size
39 | override def itemsPerPage = Config.layout.usersPerPage.get
40 | override def page = User.findAll(StartAt(curPage*itemsPerPage), MaxRows(itemsPerPage))
41 |
42 | def showuser (xhtml : NodeSeq) : NodeSeq = {
43 | val user = userToEdit.is
44 |
45 | def editSubmit() = {
46 | user.save
47 | S.redirectTo("/users/index")
48 | }
49 |
50 | def firstNameField = SHtml.text(user.firstName.is, user.firstName(_))
51 | def lastNameField = SHtml.text(user.lastName.is, user.lastName(_))
52 | def emailField = SHtml.text(user.email.is, user.email(_))
53 |
54 | def studentRoleLink =
55 | if(user.hasStudentRole) {
56 | SHtml.link("/users/edit", () => { userToEdit(user); user.removeStudentRole }, Text("Remove student role"))
57 | } else {
58 | SHtml.link("/users/edit", () => { userToEdit(user); user.addStudentRole }, Text("Make student"))
59 | }
60 |
61 | def instructorRoleLink =
62 | if(user.hasInstructorRole) {
63 | SHtml.link("/users/edit", () => { userToEdit(user); user.removeInstructorRole }, Text("Remove instructor role"))
64 | } else {
65 | SHtml.link("/users/edit", () => { userToEdit(user); user.addInstructorRole }, Text("Make instructor"))
66 | }
67 |
68 | def adminRoleLink =
69 | if(user.hasAdminRole) {
70 | SHtml.link("/users/edit", () => { userToEdit(user); user.removeAdminRole }, Text("Remove admin role"))
71 | } else {
72 | SHtml.link("/users/edit", () => { userToEdit(user); user.addAdminRole }, Text("Make admin"))
73 | }
74 |
75 | def submitButton =
76 | SHtml.submit("Submit", editSubmit)
77 |
78 | Helpers.bind("userdisplay", xhtml,
79 | "firstname" -> firstNameField,
80 | "lastname" -> lastNameField,
81 | "email" -> emailField,
82 | "studentrolechange" -> studentRoleLink,
83 | "instructorrolechange" -> instructorRoleLink,
84 | "adminrolechange" -> adminRoleLink,
85 | "submitbutton" -> submitButton)
86 | }
87 |
88 | def showall(ignored : NodeSeq) : NodeSeq = {
89 | val users = page
90 |
91 | def userToDeleteLink(user : User) : NodeSeq = {
92 | def deleteUser(user : User) = {
93 | user.delete_!
94 | Attendance.deleteAllByUser(user)
95 | Supervision.deleteAllByInstructor(user)
96 | }
97 | SHtml.link("/users/index", () => deleteUser(user), Text("Delete user"))
98 | }
99 |
100 |
101 | def userToEditLink(user : User) : NodeSeq =
102 | SHtml.link("/users/edit", () => userToEdit(user), Text("Edit user"))
103 |
104 | val userTable = TableHelper.renderTableWithHeader(users,
105 | ("First Name", (user : User) => Text(user.firstName.is)),
106 | ("Last Name", (user : User) => Text(user.lastName.is)),
107 | ("Email", (user : User) => Text(user.email.is)),
108 | ("Roles", (user : User) => Text(Role.findAll(By(Role.userId, user)).mkString(", "))),
109 | ("", (user : User) => userToEditLink(user)),
110 | ("", (user : User) => (new UserRenderer(user)).renderDeleteLink))
111 |
112 | return userTable
113 | }
114 |
115 | def resetlink(ignored : NodeSeq) : NodeSeq = {
116 | def resetDatabase() = {
117 | List(Attendance, Course, PosedProblem, PosedProblemSet, Problem, ProblemType,
118 | DFAConstructionProblem, NFAConstructionProblem,
119 | NFAToDFAProblem, NFAToDFASolutionAttempt,
120 | ProblemSet, SolutionAttempt, Supervision, DFAConstructionSolutionAttempt, NFAConstructionSolutionAttempt).map(_.bulkDelete_!!())
121 | }
122 |
123 | val resetLink = SHtml.link("/users/index", () => resetDatabase, Text("Reset Database"))
124 |
125 | return resetLink
126 | }
127 |
128 | }
--------------------------------------------------------------------------------
/src/main/webapp/WEB-INF/web.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 | LiftFilter
10 | Lift Filter
11 | The Filter that intercepts lift calls
12 | net.liftweb.http.LiftFilter
13 |
14 |
15 |
16 |
17 | LiftFilter
18 | /*
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/main/webapp/about/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | General
4 |
5 |
6 | The goal of the Automata Tutor is to help students learn basic concepts in automata
7 | theory and to help teachers preparing and grading exercises and problem sets.
8 | Automata Tutor currently supports
9 | DFA ,
10 | NFA ,
11 | NFA to DFA, and
12 | regular expression
13 | constructions.
14 |
15 |
16 | The tool provides a set of problems which you can attempt
17 | by signing up and logging in. When you login you will find Practice Problems on the left;
18 |
19 | When submitting an incorrect solution, Automata Tutor will provide a grade and feedback
20 | message to help you progress.
21 |
22 | If you are an instructor please send us an email at loris
23 | at cs.wisc.edu so that we can enable course managament for you.
24 |
25 |
26 |
27 |
28 |
29 | We open-sourced the frontend
30 | and backend
31 | of Automata Tutor. Feel free to contribute, address issues, and add features.
32 |
33 |
34 |
35 |
36 | Contact
37 |
38 | If you have questions/ comments / suggestions/ feature requests, please contact us at loris
39 | at cs.wisc.edu .
40 |
41 |
42 | Team
43 |
44 | A first version of the tutor was designed by
45 | Pavol Cerny ,
46 | Arjun Radhakrishna , and
47 | Damien Zufferey .
48 | The new Automata Tutor is designed, developed, and maintained by
49 | Rajeev Alur ,
50 | Loris D'Antoni ,
51 | Björn Hartmann ,
52 | Sumit Gulwani ,
53 | Dileep Kini ,
54 | Mahesh Viswanathan ,
55 | Matthew Weaver and
56 | Alexander Weinert .
57 | The project involves the following institutes:
58 | University of Pennsylvania ,
59 | Microsoft Research ,
60 | University of Illinois , and
61 | University of California Berkeley .
62 |
63 |
64 | Papers
65 |
66 | If you want to learn more about how Automata Tutor works check these papers:
67 |
68 |
69 | Automated Grading of DFA Constructions ,
70 | R. Alur, L. D'Antoni, S. Gulwani, D. Kini, and M. Viswanathan, IJCAI 2013
71 |
72 |
73 |
74 | How Can Automatic Feedback Help Students Construct Automata? ,
75 | L. D'Antoni, D. Kini, R. Alur, S. Gulwani, M. Viswanathan, B. Hartmann, TOCHI 2015
76 |
77 |
78 |
79 |
80 | Support
81 |
82 | Our work is supported by the National Science Foundation under the following grants:
83 |
84 |
85 | 1138996: ExCape expedition on Program Synthesis.
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/src/main/webapp/about/privacy-statement.html:
--------------------------------------------------------------------------------
1 |
2 | Automata Tutor Privacy Statement
3 |
4 |
5 | We are committed to protecting your privacy. This privacy
6 | statement explains many of the data collection and use practices of
7 | Automata Tutor.
8 |
9 |
10 | Collection and Use of Your Information
11 |
12 |
13 | Some information we collect may be used for further
14 | research, in collaboration with other researchers.
15 | Specifically, the information we collect from you can be used for
16 |
17 | to analyze and improve Automata Tutor,
18 | for further research on automata theory and education,
19 | to publish statistical information and results.
20 |
21 | We will not publish any data about individual users.
22 |
23 |
24 |
25 | In order to access certain Automata Tutor online services, you will be
26 | asked to enter your name, e-mail address, and a password. We will not share your
27 | email and password with any third parties, nor will we
28 | publish this information in any way.
29 |
30 |
31 | Changes to this privacy statement
32 |
33 |
34 | We will occasionally update this privacy statement to reflect changes in Automata Tutor.
35 | When we post changes, we will revise the "last updated" date at the bottom of this statement.
36 | We encourage you to periodically review this statement.
37 |
38 |
39 |
40 | Last updated: August 2013
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/src/main/webapp/about/terms-of-service.html:
--------------------------------------------------------------------------------
1 |
2 | Terms of service
3 | This service is provided "as is" and any express or implied warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed.
4 | In no event shall the contributors be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this service, even if advised of the possibility of such damage.
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/main/webapp/applets/buchi-game.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
23 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/main/webapp/buchi-solving/applet.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
23 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/main/webapp/buchi-solving/create.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Create a new Buchi-game to solve
4 |
5 |
6 |
7 |
8 |
9 |
10 | Short Description:
11 |
12 |
13 |
14 | Long Description:
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/main/webapp/courses/chooseproblemset.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Home
4 |
5 |
6 |
7 |
Choose problem set to pose
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/main/webapp/courses/create.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Home
4 |
5 |
6 |
7 |
Create new Course
8 |
9 |
10 |
12 |
13 |
14 | Name of the Course:
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/main/webapp/courses/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Home
4 |
5 |
6 |
7 |
8 |
9 |
10 | Enroll in course
11 |
12 |
13 |
14 | Course ID:
15 |
16 |
17 |
18 | Course Password:
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/main/webapp/courses/manage.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Home
4 |
5 |
6 |
7 |
Manage Course
8 |
9 |
10 |
12 |
13 |
14 | Name of the Course:
15 |
16 |
17 |
18 | Contact Email:
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
Posed Problem Sets
29 |
30 |
31 |
32 | Enrolled Students
33 |
34 |
35 |
36 | Enroll Students
37 |
38 |
39 |
40 | Course ID:
41 |
42 |
43 |
44 | Password:
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/src/main/webapp/courses/poseproblemset.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Home
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
Configure problem set for posing
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Start date (must be parsable by java.util.DateFormat#parse):
23 |
24 |
25 |
26 | End date (must be parsable by java.util.DateFormat#parse):
27 |
28 |
29 |
30 | Pose questions in random order:
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/src/main/webapp/courses/show.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Home
4 |
5 |
6 |
7 |
8 |
9 |
10 | Active Problemsets
11 |
12 |
13 |
14 | Expired Problemsets
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/main/webapp/courses/solveproblem.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Home
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/main/webapp/courses/solveproblemset.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Home
4 |
5 |
6 |
7 |
Solve Problem Set
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/main/webapp/description-to-nfa-problem/applet.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
23 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/main/webapp/description-to-nfa-problem/create.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
14 |
15 |
Create a new description to NFA problem
16 |
17 |
24 |
25 |
26 |
27 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | Short Description:
41 |
42 |
43 |
44 | Long Description (will appear in the problem in the form of "Construct a NFA that recognizes the following language: {long description}"):
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/src/main/webapp/description-to-nfa-problem/edit.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
14 |
Edit NFA-construction problem
15 |
16 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | Short Description:
33 |
34 |
35 |
36 | Long Description (will appear in the problem in the form of "Construct an NFA that recognizes the following language: {long description}"):
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/src/main/webapp/description-to-nfa-problem/solve.html:
--------------------------------------------------------------------------------
1 |
2 |
Solve NFA construction problem
3 | Construct an automaton that recognizes the following language of strings over the alphabet
:
4 |
5 |
6 |
7 |
15 |
16 |
17 |
Grade:
18 |
Feedback:
19 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/main/webapp/description-to-regex-problem/create.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Create a new Description to Regex problem
6 |
7 |
Regular Expression Syntax
8 |
9 | The union is expressed as R|R, star as R*, plus as R+, concatenation as RR.
10 | Epsilon is not supported but you can write R? for the regex (R|epsilon).
11 |
12 |
Problem Definition
13 |
14 |
15 |
16 |
17 | Alphabet (separated by spaces):
18 |
19 |
20 |
21 | Regular Expression:
22 |
23 |
24 | Short Description:
25 |
26 |
27 |
28 | Long Description (will appear in the problem in the form of "Construct a regular expression that recognizes the following language: {long description}"):
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
Parsing Error:
39 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/main/webapp/description-to-regex-problem/edit.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Edit Description to Regex problem
6 |
7 |
Regular Expression Syntax
8 |
9 | The union is expressed as R|R, star as R*, plus as R+, concatenation as RR.
10 | Epsilon is not supported but you can write R? for the regex (R|epsilon).
11 |
12 |
Problem Definition
13 |
14 |
15 |
16 |
17 | Alphabet (separated by spaces):
18 |
19 |
20 |
21 | Regular Expression:
22 |
23 |
24 | Short Description:
25 |
26 |
27 |
28 | Long Description (will appear in the problem in the form of "Construct a regular expression that recognizes the following language: {long description}"):
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
Parsing Error:
39 |
42 |
43 |
--------------------------------------------------------------------------------
/src/main/webapp/description-to-regex-problem/solve.html:
--------------------------------------------------------------------------------
1 |
2 |
Solve Description to Regular Expression problem
3 | Construct a regular expression that recognizes the following language of strings over the alphabet
:
4 |
5 |
Syntax
6 | The union is expressed as R|R, star as R*, plus as R+, concatenation as RR.
7 | Epsilon is not supported but you can write R? for the regex (R|epsilon).
8 |
9 |
10 |
11 |
17 |
18 |
19 |
Grade:
20 |
Feedback:
21 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/main/webapp/dfa-construction/applet.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
23 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/main/webapp/dfa-construction/create.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
14 |
Create a new DFA-construction problem
15 |
16 |
21 |
22 |
23 |
24 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | Short Description:
38 |
39 |
40 |
41 | Long Description (will appear in the problem in the form of "Construct a DFA that recognizes the following language: {long description}"):
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/src/main/webapp/dfa-construction/edit.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
14 |
Edit DFA-construction problem
15 |
16 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | Short Description:
33 |
34 |
35 |
36 | Long Description (will appear in the problem in the form of "Construct a DFA that recognizes the following language: {long description}"):
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/src/main/webapp/dfa-construction/solve.html:
--------------------------------------------------------------------------------
1 |
2 |
Solve DFA construction problem
3 |
4 | Construct an automaton that recognizes the following language of strings over the alphabet
5 | :
6 |
7 |
8 |
9 |
10 |
11 |
12 |
20 |
21 |
22 |
Grade:
23 |
Feedback:
24 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/main/webapp/images/ajax-loader.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AutomataTutor/automatatutor-frontend/5d7b2f3639ec544bead7ca28995aecb87738c421/src/main/webapp/images/ajax-loader.gif
--------------------------------------------------------------------------------
/src/main/webapp/images/trash_bin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AutomataTutor/automatatutor-frontend/5d7b2f3639ec544bead7ca28995aecb87738c421/src/main/webapp/images/trash_bin.png
--------------------------------------------------------------------------------
/src/main/webapp/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Home
4 |
5 |
6 |
7 |
8 |
9 |
Welcome to Automata Tutor
10 |
11 | The goal of the automata tutor is to help students learn automata
12 | theory and to help teachers prepare exercises and problem sets.
13 |
14 |
15 |
16 | Using the links on the left you can try solving several problems
17 | while being assisted by Automata Tutor's personalized feedback.
18 |
19 |
20 |
21 | By signing up and logging in you can find more problems and
22 | access courses.
23 |
24 |
25 |
26 | If you are an instructor please send us an email
27 | at loris
28 | at cs.wisc.edu
29 | after registering so that
30 | we can enable course management for you.
31 |
32 |
33 |
34 | IMPORTANT: If after registering you do not receive a confirmation
35 | email please use the Lost Password link
36 | to resend the confirmation email.
37 | If you still experience problems, please contact us at
38 | loris
39 | at cs.wisc.edu
40 |
41 |
42 |
43 | JOIN THE TEAM:
44 | Do you want to help us adding new features to AutomataTutor? Send an email to
45 | loris
46 | at cs.wisc.edu
47 |
48 |
49 |
50 | Please read the
terms of service and the
51 |
privacy statement .
52 |
53 | If you have questions/ comments / suggestions/ feature requests,
54 | please contact us
55 | at
loris
56 | at cs.wisc.edu
57 |
58 |
59 |
60 |
Requirements
61 |
62 | Automata tutor makes heavy use of javascript.
63 | Please make sure that the javascript support of your browser is enabled.
64 | The website have been tested and developed using the web browsers Firefox and Chrome.
65 | It is very likely that bugs might show up with other browsers, we apologise for that.
66 | We are working on supporting other browsers, but in the mean time you have been warned.
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/src/main/webapp/index.html~:
--------------------------------------------------------------------------------
1 |
2 |
3 | Home
4 |
14 |
15 |
16 |
17 |
18 |
Welcome to Automata Tutor
19 |
20 | The goal of the automata tutor is to help students learn automata
21 | theory and to help teachers prepare exercises and problem sets.
22 |
23 |
24 |
25 | Using the links on the left you can try solving several problems
26 | while being assisted by Automata Tutor's personalized feedback.
27 |
28 |
29 |
30 | By signing up and logging in you can find more problems and
31 | access courses.
32 |
33 |
34 |
35 | If you are an instructor please send us an email
36 | at info
37 | at automatatutor.com
38 | after registering so that
39 | we can enable course management for you.
40 |
41 |
42 |
43 | IMPORTANT: If after registering you do not receive a confirmation
44 | email please use the Lost Password link
45 | to resend the confirmation email.
46 | If you still experience problems, please contact us at
47 | info
48 | at automatatutor.com
49 |
50 |
51 |
52 | JOIN THE TEAM:
53 | Do you want to help us adding new features to AutomataTutor? Send an email to
54 | info
55 | at automatatutor.com .
56 |
57 |
58 |
59 | Please read the
terms of service and the
60 |
privacy statement .
61 |
62 | If you have questions/ comments / suggestions/ feature requests,
63 | please contact us
64 | at
info
65 | at automatatutor.com .
66 |
67 |
68 |
69 |
Requirements
70 |
71 | Automata tutor makes heavy use of javascript.
72 | Please make sure that the javascript support of your browser is enabled.
73 | The website have been tested and developed using the web browsers Firefox and Chrome.
74 | It is very likely that bugs might show up with other browsers, we apologise for that.
75 | We are working on supporting other browsers, but in the mean time you have been warned.
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/src/main/webapp/javascript/README.md:
--------------------------------------------------------------------------------
1 | AutomataTutor Automaton Interface
2 | =========================
3 |
4 | A Quick Overview of interface.js
5 | ---------------------------
6 |
7 | The interface is made using [d3.js](http://d3js.org) to generate and manipulate SVG elements.
8 |
9 |
10 | The automaton is described by the following:
11 |
12 | `alphabet` is an array of strings denoting the alphabet of the automaton
13 |
14 | `nodes` is an array of records with the following fields (each denoting a state):
15 | `id` -- an integer that is both the id and label displayed for the node
16 | `initial` -- a boolean with value `true` if the state is the initial states
17 | `accepting` -- a boolean with value `true` if the state is a final state
18 | `reflexiveNum` -- an integer with value of how many reflexive transitions the state has associated with it
19 | `flip` -- a boolean with value `true` if the reflexive transitions are displayed below (instead of above) the state
20 | `menuVisible` -- a boolean with value `true` if the hover menu is being displayed (only used for NFAs)
21 |
22 | `links` is an array of records with the following fields (each denoting a transition):
23 | `source` -- node record denoting the source of the transition
24 | `target` -- node record denoting the target of the tranisiton
25 | `reflexive` -- boolean with value `true` if the transition is reflexive
26 | `trans` -- string array denoting what subset of the alphabet the transition is over
27 |
28 |
29 | Two functions control the majority of the interface's behavior:
30 |
31 | `tick` controls how the automaton is displayed, updating the location of the various SVG elements
32 |
33 | `restart` controls what automaton is displayed. Thus it handles most of the user interaction with the
34 | interface and updates the variables accordingly. This includes adding/removing state and transitions, etc...
35 |
--------------------------------------------------------------------------------
/src/main/webapp/javascript/alphabetUtil.js:
--------------------------------------------------------------------------------
1 | function sanitizeStringArray(stringArray) {
2 | var sanitizedArray = new Array();
3 | for(var i = 0; i < stringArray.length; i++){
4 | if (stringArray[i]){
5 | sanitizedArray.push(stringArray[i]);
6 | }
7 | }
8 | return sanitizedArray;
9 | }
10 |
11 | function parseAlphabet() {
12 | var rawInput = document.getElementById('alphabetField').value;
13 | //var inputWithoutSpaces = rawInput.replace(/\s+/g, '');
14 | var splitInput = rawInput.split(" ");
15 | var alphabetWithoutEmptyEntries = sanitizeStringArray(splitInput)
16 | return alphabetWithoutEmptyEntries;
17 | }
18 |
19 | function parseAlphabetByFieldName(fieldName) {
20 | var rawInput = document.getElementById(fieldName).value;
21 | //var inputWithoutSpaces = rawInput.replace(/\s+/g, '');
22 | var splitInput = rawInput.split(" ");
23 | var alphabetWithoutEmptyEntries = sanitizeStringArray(splitInput)
24 | return alphabetWithoutEmptyEntries;
25 | }
26 |
27 | function alphabetChecks(stringArray){
28 | var tmpArray = [];
29 | for(var i = 0; i < stringArray.length; i++){
30 | var elem = stringArray[i];
31 | if (elem.length>1){
32 | alert("'"+elem+"' is not a character.\nThe alphabet cannot contain strings.");
33 | return false;
34 | }
35 | if(tmpArray.indexOf(elem)>=0){
36 | alert("The character '"+elem+"' is repeated twice.");
37 | return false;
38 | }
39 | tmpArray.push(elem);
40 | }
41 | return true;
42 | }
--------------------------------------------------------------------------------
/src/main/webapp/javascript/includeBuchiGame.js:
--------------------------------------------------------------------------------
1 | var Editor = {
2 | curConfig: {
3 | dimensions: [740,480]
4 | }
5 | };
6 |
7 | function initCanvas() {
8 | if(Editor.canvas)
9 | return;
10 | Editor.canvas = new $.SvgCanvas("#svgcanvasbuchigame", Editor.curConfig, 'buchigame');
11 | Editor.canvas.setAlphabet(['i,\u03B5'])
12 | }
13 |
14 | $(document).ready(function() {
15 | initCanvas();
16 | });
17 |
18 |
--------------------------------------------------------------------------------
/src/main/webapp/javascript/includeDFA.js:
--------------------------------------------------------------------------------
1 | var Editor = {
2 | curConfig: {
3 | dimensions: [740,480]
4 | }
5 | };
6 |
7 | function initCanvas() {
8 | if(Editor.canvas)
9 | return;
10 | Editor.canvas = new $.SvgCanvas("#svgcanvasdfa", Editor.curConfig, 'detaut');
11 | }
12 |
13 | $(document).ready(function() {
14 | initCanvas();
15 | });
16 |
17 |
--------------------------------------------------------------------------------
/src/main/webapp/javascript/includeDFANFA.js:
--------------------------------------------------------------------------------
1 | var Editor = {
2 | curConfigNfa: {
3 | dimensions: [740,480]
4 | },
5 | curConfigDfa: {
6 | dimensions: [740,480]
7 | }
8 | };
9 |
10 | function initCanvas() {
11 | if(!Editor.canvasNfa) {
12 | Editor.canvasNfa = new $.SvgCanvas("#svgcanvasnfa", Editor.curConfigNfa, 'nondetaut');
13 | }
14 | if(!Editor.canvasDfa) {
15 | Editor.canvasDfa = new $.SvgCanvas("#svgcanvasdfa", Editor.curConfigDfa, 'detaut');
16 | }
17 | }
18 |
19 | $(document).ready(function() {
20 | initCanvas();
21 | });
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/main/webapp/javascript/includeNFA.js:
--------------------------------------------------------------------------------
1 | var Editor = {
2 | curConfig: {
3 | dimensions: [740,480]
4 | }
5 | };
6 |
7 | function initCanvas() {
8 | if(Editor.canvas)
9 | return;
10 | Editor.canvas = new $.SvgCanvas("#svgcanvasnfa", Editor.curConfig, 'nondetaut');
11 | }
12 |
13 | $(document).ready(function() {
14 | initCanvas();
15 | });
16 |
17 |
--------------------------------------------------------------------------------
/src/main/webapp/javascript/includePL.js:
--------------------------------------------------------------------------------
1 | var Editor = {
2 | curConfig: {
3 | dimensions: [730,100]
4 | }
5 | };
6 |
7 | function initCanvas(canvasName, xmlString) {
8 | /*if(Editor.canvas)
9 | return;*/
10 | Editor.canvas = new $.SvgCanvas('#'+canvasName, Editor.curConfig);
11 | var example = "0 10 a^p 10 20 b^p ";
12 | example += "0 10 x 10 15 y 15 20 z ";
13 | example += " "
14 | Editor.canvas.readXML(xmlString);
15 | }
16 |
17 | $(document).ready(function() {
18 | initCanvas();
19 | });
20 |
21 |
--------------------------------------------------------------------------------
/src/main/webapp/javascript/lib/contextmenu.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Package: svgedit.contextmenu
3 | *
4 | * Licensed under the Apache License, Version 2
5 | *
6 | * Author: Adam Bender
7 | */
8 | // Dependencies:
9 | // 1) jQuery (for dom injection of context menus)
10 | var svgedit = svgedit || {};
11 | (function() {
12 | var self = this;
13 | if (!svgedit.contextmenu) {
14 | svgedit.contextmenu = {};
15 | }
16 | self.contextMenuExtensions = {}
17 | var addContextMenuItem = function(menuItem) {
18 | // menuItem: {id, label, shortcut, action}
19 | if (!menuItemIsValid(menuItem)) {
20 | console
21 | .error("Menu items must be defined and have at least properties: id, label, action, where action must be a function");
22 | return;
23 | }
24 | if (menuItem.id in self.contextMenuExtensions) {
25 | console.error('Cannot add extension "' + menuItem.id
26 | + '", an extension by that name already exists"');
27 | return;
28 | }
29 | // Register menuItem action, see below for deferred menu dom injection
30 | console.log("Registed contextmenu item: {id:"+ menuItem.id+", label:"+menuItem.label+"}");
31 | self.contextMenuExtensions[menuItem.id] = menuItem;
32 | //TODO: Need to consider how to handle custom enable/disable behavior
33 | }
34 | var hasCustomHandler = function(handlerKey) {
35 | return self.contextMenuExtensions[handlerKey] && true;
36 | }
37 | var getCustomHandler = function(handlerKey) {
38 | return self.contextMenuExtensions[handlerKey].action;
39 | }
40 | var injectExtendedContextMenuItemIntoDom = function(menuItem) {
41 | if (Object.keys(self.contextMenuExtensions).length == 0) {
42 | // all menuItems appear at the bottom of the menu in their own container.
43 | // if this is the first extension menu we need to add the separator.
44 | $("#cmenu_canvas").append("");
45 | }
46 | var shortcut = menuItem.shortcut || "";
47 | $("#cmenu_canvas").append(" "
48 | + menuItem.label + ""
49 | + shortcut + " ");
50 | }
51 |
52 | var menuItemIsValid = function(menuItem) {
53 | return menuItem && menuItem.id && menuItem.label && menuItem.action && typeof menuItem.action == 'function';
54 | }
55 |
56 | // Defer injection to wait out initial menu processing. This probably goes away once all context
57 | // menu behavior is brought here.
58 | /*svgEditor.ready(function() {
59 | for (menuItem in contextMenuExtensions) {
60 | injectExtendedContextMenuItemIntoDom(contextMenuExtensions[menuItem]);
61 | }
62 | });*/
63 | svgedit.contextmenu.resetCustomMenus = function(){self.contextMenuExtensions = {}}
64 | svgedit.contextmenu.add = addContextMenuItem;
65 | svgedit.contextmenu.hasCustomHandler = hasCustomHandler;
66 | svgedit.contextmenu.getCustomHandler = getCustomHandler;
67 | })();
68 |
--------------------------------------------------------------------------------
/src/main/webapp/javascript/pumpinglemma.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file Interface for drawing pumping lemma symbolic string splits
3 | *
4 | * @author Loris D'Antoni [mweaver223@gmail.com]
5 | */
6 | $.SvgCanvas = function(container, config) {
7 |
8 | var Utils = this.Utils = function() {
9 |
10 | var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
11 | }();
12 |
13 |
14 | // set up SVG for D3
15 | width = config.dimensions[0];
16 | height = config.dimensions[1];
17 |
18 | var svg = d3.select(container)
19 | .append('svg')
20 | .attr('width', width)
21 | .attr('width', width)
22 | .attr('width', width)
23 | .attr('height', height);
24 |
25 | maxWidth=50;
26 | lineColor = 'gray';
27 | brColor = 'blue';
28 | linesYCoord = 40;
29 | vertLineOff = 10;
30 | bracketsVertCoord = 62; // Is y cord when flipped
31 | //var bracketsYCoord = 80; // Is y cord when flipped
32 | emptySpace = 15;
33 | measureUnit = ((width-(emptySpace*2))/maxWidth);
34 | textOffsetBr=22;
35 | textOffsetLn=10;
36 |
37 | scaleBracketHeight = 4;
38 |
39 | //Default, needs to be set
40 | //example();
41 |
42 | // Draws a symbolic string split from two sequences of JSon Objects
43 | // The first sequence contains the symbolic representation of the pumped string (e.g. a^p b^p as two separate strings)
44 | // The second sequence contains the coordinates of the points x y z
45 | function example(){
46 | drawLine(0,25,'a^p');
47 | drawLine(25,50,'b^p');
48 | drawBrLine(0,30, 'x');
49 | drawBrLine(30,40, 'y');
50 | drawBrLine(40,50,'z');
51 | }
52 |
53 | function drawBrLine(relFrom, relTo, label){
54 |
55 | //The plus one avoid overlapping of brackets
56 | var absFrom = emptySpace+relFrom*measureUnit+1;
57 | var absTo = emptySpace+relTo*measureUnit-1;
58 |
59 | var half = ((absTo-absFrom)/2)+absFrom;
60 |
61 | svgHorLine(absFrom+vertLineOff,half-vertLineOff, bracketsVertCoord, brColor);
62 | svgHorLine(half+vertLineOff,absTo-vertLineOff, bracketsVertCoord, brColor);
63 |
64 | pointBracket(half, bracketsVertCoord, brColor);
65 | circLine(absFrom , bracketsVertCoord, brColor, false);
66 | circLine(absTo , bracketsVertCoord, brColor, true);
67 | svgText(half,bracketsVertCoord+textOffsetBr,label);
68 | }
69 |
70 | function drawLine(relFrom, relTo, label){
71 |
72 | var absFrom = emptySpace+relFrom*measureUnit;
73 | var absTo = emptySpace+relTo*measureUnit;
74 |
75 | var half = ((absTo-absFrom)/2)+absFrom;
76 |
77 | svgHorLine(absFrom,absTo, linesYCoord, lineColor);
78 | svgVerLine(absFrom , linesYCoord, lineColor);
79 | svgVerLine(absTo , linesYCoord, lineColor);
80 | svgText(half,linesYCoord-textOffsetLn,label);
81 | }
82 |
83 | function svgHorLine(f, t, vcord, color){
84 | svg.append('line')
85 | .attr('stroke', color)
86 | .attr('x1', f)
87 | .attr('x2', t)
88 | .attr('y1', vcord)
89 | .attr('y2', vcord);
90 | }
91 |
92 | function circLine(c, vcord, color, isC){
93 | var strwidth=1;
94 | if(isC)
95 | svg.append('path')
96 | .attr('stroke', color)
97 | .attr('stroke-width',strwidth)
98 | .attr('d', 'M'+c+' '+(vcord-vertLineOff)+' Q '+c+' '+vcord+' '+(c-vertLineOff)+' '+vcord+'')
99 | .attr('fill', 'transparent');
100 | else
101 | svg.append('path')
102 | .attr('stroke', color)
103 | .attr('stroke-width',strwidth)
104 | .attr('d', 'M'+c+' '+(vcord-vertLineOff)+' Q '+c+' '+vcord+' '+(c+vertLineOff)+' '+vcord+'')
105 | .attr('fill', 'transparent');
106 | }
107 | function pointBracket(c, vcord, color){
108 | var strwidth=1;
109 | svg.append('path')
110 | .attr('stroke', color)
111 | .attr('stroke-width',strwidth)
112 | .attr('d', 'M'+(c-vertLineOff)+' '+vcord+' Q '+c+' '+vcord+' '+c+' '+(vcord+vertLineOff)+'')
113 | .attr('fill', 'transparent');
114 | svg.append('path')
115 | .attr('stroke', color)
116 | .attr('stroke-width',strwidth)
117 | .attr('d', 'M'+(c+vertLineOff)+' '+vcord+' Q '+c+' '+vcord+' '+c+' '+(vcord+vertLineOff)+'')
118 | .attr('fill', 'transparent');
119 | }
120 |
121 | function svgVerLine(c, vcord, color){
122 |
123 | svg.append('line')
124 | .attr('stroke', color)
125 | .attr('x1', c)
126 | .attr('x2', c)
127 | .attr('y1', vcord-vertLineOff)
128 | .attr('y2', vcord+vertLineOff);
129 | }
130 |
131 |
132 | function svgText(x, y, text){
133 | svg.append('text')
134 | .attr('x', x)
135 | .attr('y', y)
136 | .attr('text-anchor','middle')
137 | .text(text);
138 | }
139 |
140 | /**
141 | * Draws a single instance of a symbolic string from XML
142 | */
143 | this.readXML = function (xml) {
144 |
145 | var xmlDoc = $(''+xml+' ');
146 | maxWidth=0;
147 |
148 | xmlDoc.find('string').each(
149 | function(){
150 | var t = parseInt($(this).find("to").text());
151 | if(t>maxWidth)
152 | maxWidth=t;
153 | }
154 | );
155 |
156 | measureUnit =((width-(emptySpace*2))/maxWidth);
157 |
158 | xmlDoc.find('string').each(
159 | function(){
160 | var f = parseInt($(this).find("from").text());
161 | var t = parseInt($(this).find("to").text());
162 | var label = $(this).find("label").text();
163 | drawLine(f, t, label);
164 | }
165 | );
166 |
167 | xmlDoc.find('split').each(
168 | function(){
169 | var f = parseInt($(this).find("from").text());
170 | var t = parseInt($(this).find("to").text());
171 | var label = $(this).find("label").text();
172 | drawBrLine(f, t, label);
173 | }
174 | );
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/src/main/webapp/nfa-to-dfa-problem/applet-nfa.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/main/webapp/nfa-to-dfa-problem/applet-nfa.html~:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/main/webapp/nfa-to-dfa-problem/applet.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
19 |
20 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/main/webapp/nfa-to-dfa-problem/applet.html~:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
19 |
20 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/main/webapp/nfa-to-dfa-problem/create.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
24 |
25 |
Create a new NFA to DFA problem
26 |
27 |
32 |
33 |
34 |
35 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | Short Description:
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/src/main/webapp/nfa-to-dfa-problem/create.html~:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
24 |
25 |
Create a new NFA to DFA problem
26 |
27 |
32 |
33 |
34 |
35 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | Short Description:
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/src/main/webapp/nfa-to-dfa-problem/edit.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
24 |
25 |
Create a new NFA to DFA problem
26 |
27 |
32 |
33 |
34 |
35 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | Short Description:
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/src/main/webapp/nfa-to-dfa-problem/solve.html:
--------------------------------------------------------------------------------
1 |
2 |
Solve NFA to DFA problem
3 | Construct a DFA that recognizes the language accepted by the following NFA
4 |
:
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
28 |
29 |
30 |
Grade:
31 |
Feedback:
32 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/main/webapp/nfa-to-dfa-problem/solve.html~:
--------------------------------------------------------------------------------
1 |
2 |
Solve NFA to DFA problem
3 |
Solve NFA to DFA problem
4 | Construct a DFA that recognizes the language accepted by the following NFA
5 |
:
6 |
7 |
8 |
9 |
10 |
11 |
12 |
16 |
17 |
18 |
Grade:
19 |
Feedback:
20 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/main/webapp/practicesets/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Home
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/main/webapp/practicesets/solve.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Home
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/main/webapp/preview/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Home
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/main/webapp/preview/solve.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Home
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/main/webapp/problems/create.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Home
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/main/webapp/problems/edit.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Home
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/main/webapp/problems/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Home
4 |
5 |
6 |
Problems created by you
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/main/webapp/problems/solve.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Home
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/main/webapp/problemsets/addproblem.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Home
4 |
5 |
6 |
Problems created by you
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/main/webapp/problemsets/create.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Home
4 |
5 |
6 |
Create new problem set
7 |
8 |
9 |
10 | Name:
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/main/webapp/problemsets/edit.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Home
4 |
5 |
6 |
Edit problem set
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/main/webapp/problemsets/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Problemsets created by you
4 |
5 |
6 |
7 | Short description
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | Create new problemset
21 |
22 |
23 |
24 |
5 |
6 |
Pose a problem
7 |
8 |
9 |
10 | Max Grade:
11 |
12 |
13 |
14 | Number of attempts allowed:
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/main/webapp/problemsets/poseproblem.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |